Формат текста UITextField без перемещения курсора в конец
Я пытаюсь применить стили NSAttributedString к UITextField после обработки новой текстовой записи нажатием клавиши нажатием клавиши. Проблема в том, что в любое время, когда я заменяю текст, курсор перескакивает с самого конца при каждом изменении. Вот мой нынешний подход...
Получить и отобразить изменение текста немедленно (та же проблема применяется, когда я возвращаю false и выполняю ручную замену текста)
func textField(textField: UITextField!, shouldChangeCharactersInRange range: NSRange, replacementString string: String!) -> Bool {
let range:NSRange = NSRange(location: range.location, length: range.length)
let newString = (textField.text as NSString).stringByReplacingCharactersInRange(range, withString: string);
return true
}
Я подписался на уведомление UITextFieldTextDidChangeNotification. Это вызывает стилизацию. Когда текст изменен, я заменяю его (NSString) форматированной версией ( NSAttributedString) с помощью некоторой функции format().
func textFieldTextDidChangeNotification(userInfo:NSDictionary) {
var attributedString:NSMutableAttributedString = NSMutableAttributedString(string: fullString)
attributedString = format(textField.text) as NSMutableAttributedString
textField.attributedText = attributedString
}
Стилирование отлично работает. Однако после каждой замены текста курсор переходит к концу строки. Как я могу отключить это поведение или вручную переместить курсор туда, где он начал редактирование?... или есть лучшее решение для стилизации текста во время редактирования?
Ответы
Ответ 1
Способ выполнения этой работы - захватить местоположение курсора, обновить содержимое поля и затем заменить курсор на его исходное положение. Я не уверен в точном эквиваленте в Swift, но следующим я хотел бы сделать это в Obj-C.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
UITextPosition *beginning = textField.beginningOfDocument;
UITextPosition *cursorLocation = [textField positionFromPosition:beginning offset:(range.location + string.length)];
textField.text = [textField.text stringByReplacingCharactersInRange:range withString:string];
/* MAKE YOUR CHANGES TO THE FIELD CONTENTS AS NEEDED HERE */
// cursorLocation will be (null) if you're inputting text at the end of the string
// if already at the end, no need to change location as it will default to end anyway
if(cursorLocation)
{
// set start/end location to same spot so that nothing is highlighted
[textField setSelectedTextRange:[textField textRangeFromPosition:cursorLocation toPosition:cursorLocation]];
}
return NO;
}
Ответ 2
Это старый вопрос, но у меня была аналогичная проблема и разрешил его со следующим Swift:
// store the current cursor position as a range
var preAttributedRange: NSRange = textField.selectedRange
// apply attributed string
var attributedString:NSMutableAttributedString = NSMutableAttributedString(string: fullString)
attributedString = format(textField.text) as NSMutableAttributedString
textField.attributedText = attributedString
// reapply the range
textField.selectedRange = preAttributedRange
Это работает в контексте моего приложения, надеюсь, это полезно для кого-то!
Ответ 3
Спасибо @Stonz2 по коду в Objective-C. Отлично работает! Я использовал это в своем проекте Swift.
Тот же код в Swift:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let positionOriginal = textField.beginningOfDocument
let cursorLocation = textField.position(from: positionOriginal, offset: (range.location + NSString(string: string).length))
/* MAKE YOUR CHANGES TO THE FIELD CONTENTS AS NEEDED HERE */
if let cursorLoc = cursorLocation {
textField.selectedTextRange = textField.textRange(from: cursorLoc, to: cursorLoc)
}
return false
}
Ответ 4
Чтобы дополнить другие правильные ответы в этой теме, здесь приведены некоторые фрагменты кода для Swift 4.2 и для UITextField и UITextView
UITextField
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// Update Cursor
let positionOriginal = textField.beginningOfDocument
let cursorLocation = textField.position(from: positionOriginal, offset: (range.location + string.count))
if let cursorLocation = cursorLocation {
textField.selectedTextRange = textField.textRange(from: cursorLocation, to: cursorLocation)
}
return false
}
UITextView
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
// Update Cursor
let positionOriginal = textView.beginningOfDocument
let cursorLocation = textView.position(from: positionOriginal, offset: (range.location + text.count))
if let cursorLocation = cursorLocation {
textView.selectedTextRange = textView.textRange(from: cursorLocation, to: cursorLocation)
}
return false
}
Ответ 5
Это код, который работает для быстрого 2. Наслаждайтесь!
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let beginnig: UITextPosition = textField.beginningOfDocument
if let txt = textField.text {
textField.text = NSString(string: txt).stringByReplacingCharactersInRange(range, withString: string)
}
if let cursorLocation: UITextPosition = textField.positionFromPosition(beginnig, offset: (range.location + string.characters.count) ) {
textField.selectedTextRange = textField.textRangeFromPosition(cursorLocation, toPosition: cursorLocation)
}
return false
}
Обратите внимание, что последний "if let" должен всегда оставаться в конце кода.
Ответ 6
Чтобы добавить ответ на другой вопрос здесь: fooobar.com/info/15755065/..., вам не следует изменять текст в shouldChangeCharactersInRange
, так как этот метод делегата предназначен только для того, чтобы поле знало, разрешать ли изменение или нет ' не должен мутировать.
Вместо этого мне нравится обрабатывать изменение текста, подписываясь на изменение значения, например:
textField.addTarget(self, action: #selector(handleTextChanged), for: .editingChanged)
// elsewhere in the file
func handleTextChanged(_ textField: UITextField) {
textField.sanitizeText(map: formatText)
}
где реализация sanitizeText
выглядит следующим образом:
extension UITextField {
// Use this to filter out or change the text value without
// losing the current selection
func sanitizeText(map: ((String) -> String)) {
guard let text = self.text,
let selection = selectedTextRange else {
return
}
let newText = map(text)
// only execute below if text is different
guard newText != text else { return }
// determine where new cursor position should start
// so the cursor doesnt get sent to the end
let diff = text.count - newText.count
let cursorPosition = offset(from: beginningOfDocument, to: selection.start) - diff
self.text = newText
// notify the value changed (to ensure delegate methods get triggered)
sendActions(for: .valueChanged)
// update selection afterwards
if let newPosition = position(from: beginningOfDocument, offset: cursorPosition) {
selectedTextRange = textRange(from: newPosition, to: newPosition)
}
}
}