UITextField, automatically move to next after 1 character

后端 未结 6 622

Scenario: I have 4 UITextFields that only accept 1 character. Easy.

Problem: After I enter the 1 character, I want the next TextField to become active automatically with

相关标签:
6条回答
  • I arrived at a solution by modifying some code I found here: http://www.thepensiveprogrammer.com/2010/03/customizing-uitextfield-formatting-for.html

    First set the your view controller to be the delegate of the textfields.

    Then do something like this:

    - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
    {   
        BOOL shouldProcess = NO; //default to reject
        BOOL shouldMoveToNextField = NO; //default to remaining on the current field
    
        int insertStringLength = [string length];
        if(insertStringLength == 0){ //backspace
            shouldProcess = YES; //Process if the backspace character was pressed
        }
        else {
            if([[textField text] length] == 0) {
                shouldProcess = YES; //Process if there is only 1 character right now
            }
        }
    
        //here we deal with the UITextField on our own
        if(shouldProcess){
            //grab a mutable copy of what's currently in the UITextField
            NSMutableString* mstring = [[textField text] mutableCopy];
            if([mstring length] == 0){
                //nothing in the field yet so append the replacement string
                [mstring appendString:string];
    
                shouldMoveToNextField = YES;
            }
            else{
                //adding a char or deleting?
                if(insertStringLength > 0){
                    [mstring insertString:string atIndex:range.location];
                }
                else {
                    //delete case - the length of replacement string is zero for a delete
                    [mstring deleteCharactersInRange:range];
                }
            }
    
            //set the text now
            [textField setText:mstring];
    
            [mstring release];
    
            if (shouldMoveToNextField) {
                //
                //MOVE TO NEXT INPUT FIELD HERE
                //
            }
        }
    
        //always return no since we are manually changing the text field
        return NO;
    }
    
    0 讨论(0)
  • 2021-01-31 12:07

    The following is for Swift 5 and handles textfields as an array instead of individual fields.

    import UIKit
    
    class MyViewController: UIViewController, UITextFieldDelegate {
    
    @IBOutlet var digitFields: [UITextField]!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        digitFields.forEach {
            configureDigitField($0)
        }
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        digitFields[0].becomeFirstResponder()
    }
    
    fileprivate func configureDigitField(_ digitField: UITextField) {
        digitField.delegate = (self as UITextFieldDelegate)
        digitField.addTarget(self, action: #selector(self.textFieldDidChange(textField:)), for: UIControl.Event.editingChanged)
    }
    
    // Move to next field in digit fields if the value is populated
    @objc fileprivate func textFieldDidChange(textField: UITextField) {
        if textField.text?.count == 1 {
            let remaining = digitFields.filter { $0.text?.count == 0 }
            if remaining.count > 0 {
                remaining[0].becomeFirstResponder()
            } else {
                digitFields.forEach { $0.resignFirstResponder() }
            }
        }
    }
    

    Results in:

    This is dependent on the textfields being grouped in an array. This can be achieved in interface builder by configuring the collection of fields in the Outlet configuration screen:

    which can be reached from the view controller properties on the last tab item

    Note that you need to manually add the

    @IBOutlet var digitFields: [UITextField]!

    to your view controller before you can add the text fields to it.

    Summary of Code Behaviour

    • The view controller needs to be a UITextFieldDelegate to allow it to receive textfield events.
    • In the viewDidLoad function, each of the text fields in the array are initialised in the configureDigitField method
    • In the viewWillAppear method the first field in the array is readied to handle input (i.e. the first entry will take place in it)
    • The configureDigitalField function sets this view controller to receive events from the textfield (each of them as it is called for each textfield)
    • It also sets up a selector to call the textFieldDidChange function on result of a textfield edit changed event
    • textFieldDidChange method checks if the length of the text in the field is 1, and if so
    • checks for the remaining text fields where there is no value entered
    • takes the first remaining text field and sets it up to receive the next input
    • if no remaining fields are empty, resigns its position as first responder, so any more keypresses will not occur in any of the digit fields
    0 讨论(0)
  • 2021-01-31 12:10

    Although it is an old question, I just came cross it and I came with simpler solution. assuming we doing this for pass code so each box (UITextField) max length is one char.

    - (BOOL) textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
        if (![string isEqualToString:@""]) {
            textField.text = string;
            if ([textField isEqual:self.txtField1]) {
                [self.txtField2 becomeFirstResponder];
            }else if ([textField isEqual:self.txtField2]){
                [self.txtField3 becomeFirstResponder];
            }else if ([textField isEqual:self.txtField3]){
                [self.txtField4 becomeFirstResponder];
            }else{
                [textField resignFirstResponder];
            }
            return NO;
        }
        return YES;
    }
    
    -(BOOL)textFieldShouldBeginEditing:(UITextField *)textField{
        if (textField.text.length > 0) {
            textField.text = @"";
        }
        return YES;
    }
    
    0 讨论(0)
  • 2021-01-31 12:12

    UPDATED CODE FOR SWIFT 3

    @IBOutlet weak var tf1: UITextField!
    @IBOutlet weak var tf2: UITextField!
    @IBOutlet weak var tf3: UITextField!
    @IBOutlet weak var tf4: UITextField!
    override func viewDidLoad() {
        super.viewDidLoad()
        tf1.delegate = self
        tf2.delegate = self
        tf3.delegate = self
        tf4.delegate = self
    
    
        tf1.addTarget(self, action: #selector(self.textFieldDidChange(textField:)), for: UIControlEvents.editingChanged)
        tf2.addTarget(self, action: #selector(self.textFieldDidChange(textField:)), for: UIControlEvents.editingChanged)
        tf3.addTarget(self, action: #selector(self.textFieldDidChange(textField:)), for: UIControlEvents.editingChanged)
        tf4.addTarget(self, action: #selector(self.textFieldDidChange(textField:)), for: UIControlEvents.editingChanged)
    
    }
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(true)
        tf1.becomeFirstResponder()
    
    }
    func textFieldDidChange(textField: UITextField){
    
        let text = textField.text
    
        if text?.utf16.count==1{
            switch textField{
            case tf1:
                tf2.becomeFirstResponder()
            case tf2:
                tf3.becomeFirstResponder()
            case tf3:
                tf4.becomeFirstResponder()
            case tf4:
                tf4.resignFirstResponder()
            default:
                break
            }
        }else{
    
        }
    }
    
    0 讨论(0)
  • 2021-01-31 12:24

    I know this is a very old question, but here's my approach for allowing a single numeric value only across four UITextFields, and automatically 'tabbing' to the next one (pin1-pin4 each represents a PIN number digit lol, and are retained as properties):

    -(BOOL)textFieldShouldReturn:(UITextField*)textField;
    {
        if (textField == pin1)
        {
            [pin2 becomeFirstResponder];
        }
        else if (textField == pin2)
        {
            [pin3 becomeFirstResponder];
        }
        else if (textField == pin3)
        {
            [pin4 becomeFirstResponder];
        }
    
        return NO;
    }
    
    - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
    {
        // This allows numeric text only, but also backspace for deletes
        if (string.length > 0 && ![[NSScanner scannerWithString:string] scanInt:NULL])
            return NO;
    
        NSUInteger oldLength = [textField.text length];
        NSUInteger replacementLength = [string length];
        NSUInteger rangeLength = range.length;
    
        NSUInteger newLength = oldLength - rangeLength + replacementLength;
    
        // This 'tabs' to next field when entering digits
        if (newLength == 1) {
            if (textField == pin1)
            {
                [self performSelector:@selector(setNextResponder:) withObject:pin2 afterDelay:0.2];
            }
            else if (textField == pin2)
            {
                [self performSelector:@selector(setNextResponder:) withObject:pin3 afterDelay:0.2];
            }
            else if (textField == pin3)
            {
                [self performSelector:@selector(setNextResponder:) withObject:pin4 afterDelay:0.2];
            }
        }
        //this goes to previous field as you backspace through them, so you don't have to tap into them individually
        else if (oldLength > 0 && newLength == 0) {
            if (textField == pin4)
            {
                [self performSelector:@selector(setNextResponder:) withObject:pin3 afterDelay:0.1];
            }
            else if (textField == pin3)
            {
                [self performSelector:@selector(setNextResponder:) withObject:pin2 afterDelay:0.1];
            }
            else if (textField == pin2)
            {
                [self performSelector:@selector(setNextResponder:) withObject:pin1 afterDelay:0.1];
            }
        }
    
        return newLength <= 1;
    }
    
    - (void)setNextResponder:(UITextField *)nextResponder
    {
        [nextResponder becomeFirstResponder];
    }
    
    0 讨论(0)
  • 2021-01-31 12:28

    Swift 4.x

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

    @objc func textFieldDidChange(textField: UITextField)
    {
        let text = textField.text
        if text?.utf16.count == 1 {
            switch textField {
            case txtOtpNumber1:
                txtOtpNumber2.becomeFirstResponder()
            case txtOtpNumber2:
                txtOtpNumber3.becomeFirstResponder()
            case txtOtpNumber3:
                txtOtpNumber4.becomeFirstResponder()
            case txtOtpNumber4:
                txtOtpNumber4.resignFirstResponder()
            default:
                break
            }
        } else {
            switch textField {
            case txtOtpNumber4:
                txtOtpNumber3.becomeFirstResponder()
            case txtOtpNumber3:
                txtOtpNumber2.becomeFirstResponder()
            case txtOtpNumber2:
                txtOtpNumber1.becomeFirstResponder()
            case txtOtpNumber1:
                txtOtpNumber1.resignFirstResponder()
            default:
                break
            }
        }
    }
    

    PS. Chetan's answer updated for the current Swift.

    0 讨论(0)
提交回复
热议问题