Implementing AI-Powered Text Rewriting in Mobile Applications
Text rewriting in mobile is narrow in scope: user selects fragment, hits button, gets rewritten version. Simple idea, but implementation pitfalls abound.
Working with text selection
Hardest part isn't AI—it's correct handling of selectedRange on replacement. Wrong NSRange replacement means cursor jumps to start, selection vanishes, undo history breaks.
// iOS: safe selected text replacement with undo preservation
func replaceSelection(with newText: String) {
guard let textView = self.textView,
let selectedRange = Range(textView.selectedRange, in: textView.text) else { return }
// Register undo before change
textView.undoManager?.registerUndo(withTarget: self) { [oldText = textView.text, oldRange = textView.selectedRange] target in
target.restoreText(oldText, cursorAt: oldRange)
}
textView.textStorage.beginEditing()
textView.textStorage.replaceCharacters(
in: textView.selectedRange,
with: NSAttributedString(string: newText, attributes: textView.typingAttributes)
)
textView.textStorage.endEditing()
// Set cursor to end of inserted text
let newCursorPos = textView.selectedRange.location + newText.utf16.count
textView.selectedRange = NSRange(location: newCursorPos, length: 0)
}
Android equivalent via Editable.replace() + Selection.setSelection(). In Compose: via TextFieldState in new API (available from Compose BOM 2024.06).
Prompts for different rewrite scenarios
No universal prompt. Each mode has its own:
enum RewriteMode {
case simplify, formalize, casual, shorten, expand, fix
var systemPrompt: String {
switch self {
case .simplify:
return "Rewrite the text using simpler words and shorter sentences. Preserve all meaning. Same language as input."
case .formalize:
return "Rewrite in formal business style. Remove colloquialisms. Preserve all key information."
case .casual:
return "Rewrite in a friendly, conversational tone. Natural language, not stiff."
case .shorten:
return "Shorten by 40-60%. Keep only essential information. No filler."
case .expand:
return "Expand with relevant details and examples. Add 50-100% more content. Stay on topic."
case .fix:
return "Fix grammar, spelling, and awkward phrasing. Minimal changes to preserve the original voice."
}
}
}
Key line in all prompts: "Same language as input." Without it, GPT sometimes switches to English, especially if text has technical terms.
UI pattern: before/after
User must see original alongside rewrite and easily revert. Don't hide the source.
@Composable
fun RewriteResultView(
original: String,
rewritten: String,
onAccept: () -> Unit,
onDiscard: () -> Unit,
onRetry: () -> Unit
) {
Column(modifier = Modifier.fillMaxWidth()) {
Text("Original", style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
Text(
text = original,
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.surfaceVariant, RoundedCornerShape(8.dp))
.padding(12.dp),
style = MaterialTheme.typography.bodyMedium.copy(
color = MaterialTheme.colorScheme.onSurfaceVariant
)
)
Spacer(Modifier.height(8.dp))
Text("Result", style = MaterialTheme.typography.labelSmall)
Text(
text = rewritten,
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.primaryContainer, RoundedCornerShape(8.dp))
.padding(12.dp)
)
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
TextButton(onClick = onDiscard) { Text("Cancel") }
TextButton(onClick = onRetry) { Text("Another option") }
Button(onClick = onAccept) { Text("Accept") }
}
}
}
"Another option" button is important—first rewrite isn't always suitable, but user doesn't want to fiddle with prompts.
Diff highlighting of changes
For fix mode (grammar correction), show exactly what changed. Simple client-side diff without server:
// Simplified word-level diff
func computeDiff(original: String, rewritten: String) -> [DiffChunk] {
let origWords = original.split(separator: " ").map(String.init)
let newWords = rewritten.split(separator: " ").map(String.init)
// LCS-based diff, implement via standard algorithm
return lcs(origWords, newWords)
}
Android: DiffUtil from androidx.recyclerview works for lists; for text, implement own LCS or use java-diff-utils library.
Timeline estimates
Basic rewriting (one mode, no diff)—2–4 days. Full implementation with multiple modes, diff highlighting, correct undo, and variant history—1.5–2 weeks.







