Simple solution:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let newRange = textField.text?.range(from: range), let result = textField.text?.replacingCharacters(in: newRange, with: string) else { return true }
if result.endsWithString("@letschat") {
return true
} else {
textField.text = result + "@letschat"
let position = textField.position(from: textField.beginningOfDocument, offset: result.characters.count)!
textField.selectedTextRange = textField.textRange(from: position, to: position)
return false
}
}
With helper extension:
extension String {
func range(from oldOne: NSRange) -> Range<String.Index>? {
guard
let from16 = utf16.index(utf16.startIndex, offsetBy: oldOne.location, limitedBy: utf16.endIndex),
let to16 = utf16.index(utf16.startIndex, offsetBy: oldOne.location + oldOne.length, limitedBy: utf16.endIndex),
let from = from16.samePosition(in: self),
let to = to16.samePosition(in: self)
else { return nil }
return from ..< to
}
func endsWithString(_ string: String) -> Bool {
guard characters.count >= string.characters.count else { return false }
let index = self.index(startIndex, offsetBy: characters.count - string.characters.count)
let substring = self.substring(from: index)
return substring == string
}
}
Difficult but clear solution is to create your own UIControl
-subclass with UITextField
and UILabel
children views:
+-----------+ +-----------+
| textfield | -(3px)- | @letschat |
+-----------+ +-----------+
Use autolayout to keep the distance of 3 pixels between it.
Don't forget to configure your class to send all the incoming actions to the textfield. You can use different font colours for these controls so user won't be confused about efforts to change label's value.