5

(programming in swift 2)

I have a UITextField that when the user types into it should be automatically converted to lower case WHILE typing (so NOT after form validation).

I have gotten this far:

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
    //only convert to lowercase for username field
    if textField == textFieldUsername {
        //let stringRange = NSRange(location: range.location, length: range.length)
        let completedString = (textField.text ?? "" as NSString).stringByReplacingCharactersInRange(range, withString: string)
        //convert the while thing to lowercase and assign back to the textfield
        textField.text = completedString.lowercaseString
        //return false to indicate that the "system" itself should not do anychanges anymore, as we did them
        return false
    }
    //return to the "system" that it can do the changes itself
    return true
}

Problem is that (1)when the user presses and holds on the UITextField to (2)move the cursor to somewhere halfway inside the string and (3)start typing that (4)the cursor jumps back to the end of the already inputted string.

Does the cursor position needs to be restored after textField: shouldChangeCharactersInRange is called maybe?

HixField
  • 3,538
  • 1
  • 28
  • 54

4 Answers4

6

Swift 4

lowercaseString has changed to lowercased(). Also you can do this via target action instead of NotificationCenter if you prefer.

@IBOutlet fileprivate weak var textField: UITextField!

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    textField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
}

@objc func textFieldDidChange(textField: UITextField) {
    textField.text = textField.text?.lowercased()
} 
Eli Burke
  • 2,729
  • 27
  • 25
  • This works perfectly. Characters are all lowercase as you type. Never see the uppercase even if you type them (they are converted to lowercase in realtime). I like it better than the notification center approach. Not sure which is more performant or if it even matters, but this is a bit more concise and to me memorable, and a great use of the events generated target/selector pattern. – clearlight May 31 '22 at 21:29
4

I have taken your code and tried this.

I can replace the cursor position wherever the text is changed.

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {

    let start = textField.positionFromPosition(textField.beginningOfDocument, offset:range.location)

    let cursorOffset = textField.offsetFromPosition(textField.beginningOfDocument, toPosition:start!) + string.characters.count


    textField.text = (textField.text! as NSString).stringByReplacingCharactersInRange(range, withString: string).lowercaseString

    let newCursorPosition = textField.positionFromPosition(textField.beginningOfDocument, offset:cursorOffset)

    let newSelectedRange = textField.textRangeFromPosition(newCursorPosition!, toPosition:newCursorPosition!)

    textField.selectedTextRange = newSelectedRange

    return false
}
UIResponder
  • 785
  • 9
  • 15
  • Though the easiest way is as mentioned by @Pradeep K, i improved your code, since you already have it. as mentioned by Pradeep K – UIResponder Feb 23 '16 at 12:20
  • Its not a good practice to change the text in shouldChangeCharactersInRange delegate callback. Depending on what you are trying to do you will get unexpected results. This delegate should be used only to check if the chars in a range can be changed or not. But the actual text should be changed in UITextFieldDidChangeNotification. – Pradeep K Feb 24 '16 at 08:53
3

Easier method is this.

  1. Register for the UITextFieldTextDidChangeNotification

    NSNotificationCenter.defaultCenter().addObserver(self, selector: "textFieldDidChange:", name: UITextFieldTextDidChangeNotification, object: textField)

  2. Change the case in the notification callback.

    func textFieldDidChange(notification:NSNotification) { textField.text = textField.text?.lowercaseString }

Pradeep K
  • 3,671
  • 1
  • 11
  • 15
1

A solution if you are using RxSwift 4.0 :

Just call this :

emailField.rx.controlEvent(.editingChanged)
    .asObservable()
    .subscribe(self.onEditingChanged)
    .disposed(by: self.disposeBag)

With this callback :

private func onEditingChanged(event : Event<Void>){
    self.emailField.text = self.emailField.text?.lowercased()
}
Kevin ABRIOUX
  • 16,507
  • 12
  • 93
  • 99