Dennis van Mazijk
Dennis van Mazijk

Reputation: 205

Swift - Automatically jump to next text field when not empty

I am working on a game in which the user has to type out the past tense of a verb. My view contains small textfield boxes that only accept one character. As of now I am trying to automatically jump to the next textfield when the former contains a letter.

I want to keep doing this until all the boxes are filled. The user should also be able to go back one box using the return button on the keyboard.

Below is the code I am currently using, but it is not jumping to the next textfield. What am I doing wrong?

 var game: Game? {
    didSet {

        if var answerContent = game?.answer {

            let views = (0..<answerContent.characters.count).map { _ in UITextField(frame: CGRect(x: 0, y: 0, width: 40, height: 40)) }

            for textField in views {
                textField.backgroundColor = UIColor.white
                textField.textColor = Constants.MAIN_THEME_COLOR
                textField.textAlignment = NSTextAlignment.center
                textField.delegate = self
                textField.returnKeyType = .next
                textField.tag = views.index(of: textField)! + 1
                self.container.addArrangedSubview(textField)

                views.first?.becomeFirstResponder()
            }
        }
    }
}

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    guard let text = textField.text else { return true }
    let textLength = text.characters.count + string.characters.count - range.length

    return textLength <= 1
}

func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        if let nextField = textField.superview?.viewWithTag(textField.tag + 1) as? UITextField {
            print("Test")
            nextField.becomeFirstResponder()
        } else {
            print("Test2")
            textField.resignFirstResponder()
        }

    return false
}

Updated code (24-04-2017) - Returns nil when trying to jump to the next textField

var game: Game? {
    didSet {

if var answerContent = game?.answer {

            let views = (0..<answerContent.characters.count).map { _ in UITextField(frame: CGRect(x: 0, y: 0, width: 40, height: 40)) }

            for textField in views {
                textField.backgroundColor = UIColor.white
                textField.textColor = Constants.MAIN_THEME_COLOR
                textField.textAlignment = NSTextAlignment.center
                textField.delegate = self
                textField.addTarget(self, action: #selector(textChanged), for: .editingChanged)
                textField.tag = views.index(of: textField)! + 1
                self.container.addArrangedSubview(textField)
            }

            views.first?.becomeFirstResponder()
        }
    }
}

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    guard let text = textField.text else { return true }
    let textLength = text.characters.count + string.characters.count - range.length

    return textLength <= 1
}

func textChanged(sender: UITextField) {
    if (sender.text?.characters.count)! > 0 {
        print("Entered")
        let nextField = textField?.superview?.viewWithTag(textField.tag + 1) as UIResponder!

        if (nextField != nil) {
            nextField?.becomeFirstResponder()
        } else {
            print("Error: nil found")
        }
    } else {
        print("Removed")
        textField?.resignFirstResponder()
    }
}

Answer:

var index: NSInteger = 0
for textField in views {
    textField.backgroundColor = UIColor.white
    textField.textColor = Constants.MAIN_THEME_COLOR
    textField.textAlignment = NSTextAlignment.center
    textField.autocapitalizationType = UITextAutocapitalizationType.none
    textField.delegate = self
    textField.addTarget(self, action: #selector(textChanged), for: .editingChanged)
    textField.tag = index
    self.container.addArrangedSubview(textField)

    index+=1
}

func textChanged(sender: UITextField) {
    if (sender.text?.characters.count)! > 0 {
        let nextField = sender.superview?.viewWithTag(sender.tag + 1) as UIResponder!
        nextField?.becomeFirstResponder()
    } else {
        sender.resignFirstResponder()
    }
}

Upvotes: 3

Views: 3859

Answers (3)

ch1maera
ch1maera

Reputation: 1447

Update for Swift 4

In viewDidLoad() for each text field,

textField.addTarget(self, action: #selector(textChanged), for: .editingChanged)

Then, add this function,

@objc func textChanged(sender: UITextField) {
    if (sender.text?.count)! > 0 {
        let nextField = self.view.viewWithTag(sender.tag + 1) as? UITextField
        nextField?.becomeFirstResponder()
    }
}

Upvotes: 1

Akshay Kumar Both
Akshay Kumar Both

Reputation: 382

Follow the code:

import UIKit

// used this to set max characters of UITextField in the storyboard
private var __maxLengths = [UITextField: Int]()
extension UITextField {
    @IBInspectable var maxLength: Int {
        get {
            guard let l = __maxLengths[self] else {
                return 150 // (global default-limit. or just, Int.max)
            }
            return l
        }
        set {
            __maxLengths[self] = newValue
            addTarget(self, action: #selector(fix), for: .editingChanged)
        }
    }
    func fix(textField: UITextField) {
        let t = textField.text
        textField.text = t?.safelyLimitedTo(length: maxLength)
    }
}

extension String
{
    func safelyLimitedTo(length n: Int)->String {
        let c = self.characters
        if (c.count <= n) { return self }
        return String( Array(c).prefix(upTo: n) )
    }
}


class ViewController: UIViewController {

  @IBOutlet var input1: UITextField!
  @IBOutlet var input2: UITextField!
  @IBOutlet var input3: UITextField!
  @IBOutlet var input4: UITextField!
  @IBOutlet var input5: UITextField!
  @IBOutlet var input6: UITextField!

  override func viewDidLoad() {
        super.viewDidLoad()

        setup()
        // Do any additional setup after loading the view.
  }

  func setup() {
        input1.tag = 1
        input2.tag = 2
        input3.tag = 3
        input4.tag = 4
        input5.tag = 5
        input6.tag = 6

        input1.addTarget(self, action: #selector(textChanged), for: .editingChanged)
        input2.addTarget(self, action: #selector(textChanged), for: .editingChanged)
        input3.addTarget(self, action: #selector(textChanged), for: .editingChanged)
        input4.addTarget(self, action: #selector(textChanged), for: .editingChanged)
        input5.addTarget(self, action: #selector(textChanged), for: .editingChanged)
        input6.addTarget(self, action: #selector(textChanged), for: .editingChanged)
  }

  func textChanged(sender: UITextField) {
        if (sender.text?.characters.count)! ==  1 {

            let nextField = sender.superview?.viewWithTag(sender.tag + 1) as UIResponder!
            nextField?.becomeFirstResponder()
        } else if (sender.text?.characters.count)! ==  0 {
            let nextField = sender.superview?.viewWithTag(sender.tag - 1) as UIResponder!
            nextField?.becomeFirstResponder()
        }


    }


}

Upvotes: 2

Sweeper
Sweeper

Reputation: 271040

I suggest that you should add a target to the control event .valueChanged:

// for each text field
textField.addTarget(self, action: #selector(textChanged), for: .valueChanged)

Implement textChanged as follows:

func textChanged(sender: UITextField) {
    if sender.text.characters.length > 0 {
        let nextField = textField.superview?.viewWithTag(textField.tag + 1) as? UITextField
        nextField?.becomeFistResponder()
    }
}

Upvotes: 4

Related Questions