How to move cursor from one text field to another automatically in swift ios programmatically?

后端 未结 13 2112
抹茶落季
抹茶落季 2020-12-05 08:11
    func textFieldDidBeginEditing(textField: UITextField) {
    scrlView.setContentOffset(CGPointMake(0, textField.frame.origin.y-70), animated: true)


    if(textF         


        
相关标签:
13条回答
  • 2020-12-05 09:09

    This is similar to how UberEats has their otp fields. You can just copy and paste this into a file and run it to see how it works. But don't forget to add the MyTextField class or it won't work.

    If you want it to automatically to move to the next textfield after a number is entered and still be able to move backwards if the back button is pressed WHILE the textField is empty this will help you.

    Like the very first thing I said, this is similar to how UberEats has their sms textFields working. You can't just randomly press a textField and select it. Using this you can only move forward and backwards. The ux is subjective but if Uber uses it the ux must be valid. I say it's similar because they also have a gray box covering the textField so I'm not sure what's going on behind it. This was the closest I could get.

    First your going to have to subclass UITextField using this answer to detect when the backspace button is pressed. When the back button is pressed your going to erase everything inside that field AND the previous field then jump to the previous field.

    Second your going to have to prevent the user from being able to select the left side of the cursor once a char is inside the textField using this answer. You override the method in the same subClass from the first step.

    Third you need to detect which textField is currently active using this answer

    Fourth your going to have to run some checks inside func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool using this YouTube tutorial. I added some things to his work.

    I'm doing everything programmatically so you can copy and paste the entire code into a project and run it

    First create a subClass of UITextField and name it MyTextField:

    protocol MyTextFieldDelegate: class {
        func textFieldDidDelete()
    }
    
    // 1. subclass UITextField and create protocol for it to know when the backButton is pressed
    class MyTextField: UITextField {
    
        weak var myDelegate: MyTextFieldDelegate? // make sure to declare this as weak to prevent a memory leak/retain cycle
    
        override func deleteBackward() {
            super.deleteBackward()
            myDelegate?.textFieldDidDelete()
        }
    
        // when a char is inside the textField this keeps the cursor to the right of it. If the user can get on the left side of the char and press the backspace the current char won't get deleted
        override func closestPosition(to point: CGPoint) -> UITextPosition? {
            let beginning = self.beginningOfDocument
            let end = self.position(from: beginning, offset: self.text?.count ?? 0)
            return end
        }
    }
    

    Second inside the class with the OTP textfields, set the class to use the UITextFieldDelegate and the MyTextFieldDelegate, then create a class property and name it activeTextField. When whichever textField becomes active inside textFieldDidBeginEditing you set the activeTextField to that. In viewDidLoad set all the textFields to use both delegates.

    Make sure the First otpTextField is ENABLED and the second, third, and fourth otpTextFields are ALL initially DIASABLED

    import UIKit
    
    // 2. set the class to BOTH Delegates
    class ViewController: UIViewController, UITextFieldDelegate, MyTextFieldDelegate {
    
        let staticLabel: UILabel = {
            let label = UILabel()
            label.translatesAutoresizingMaskIntoConstraints = false
            label.font = UIFont.systemFont(ofSize: 17)
            label.text = "Enter the SMS code sent to your phone"
            return label
        }()
    
        // 3. make each textField of type MYTextField
        let otpTextField1: MyTextField = {
            let textField = MyTextField()
            textField.translatesAutoresizingMaskIntoConstraints = false
            textField.font = UIFont.systemFont(ofSize: 25)
            textField.autocorrectionType = .no
            textField.keyboardType = .numberPad
            textField.textAlignment = .center
            // **important this is initially ENABLED
            return textField
        }()
    
        let otpTextField2: MyTextField = {
            let textField = MyTextField()
            textField.translatesAutoresizingMaskIntoConstraints = false
            textField.font = UIFont.systemFont(ofSize: 25)
            textField.autocorrectionType = .no
            textField.keyboardType = .numberPad
            textField.textAlignment = .center
            textField.isEnabled = false // **important this is initially DISABLED
            return textField
        }()
    
        let otpTextField3: MyTextField = {
            let textField = MyTextField()
            textField.translatesAutoresizingMaskIntoConstraints = false
            textField.font = UIFont.systemFont(ofSize: 25)
            textField.autocorrectionType = .no
            textField.keyboardType = .numberPad
            textField.textAlignment = .center
            textField.isEnabled = false // **important this is initially DISABLED
            return textField
        }()
    
        let otpTextField4: MyTextField = {
            let textField = MyTextField()
            textField.translatesAutoresizingMaskIntoConstraints = false
            textField.font = UIFont.systemFont(ofSize: 25)
            textField.autocorrectionType = .no
            textField.keyboardType = .numberPad
            textField.textAlignment = .center
            textField.isEnabled = false // **important this is initially DISABLED
            return textField
        }()
    
        // 4. create this property to know which textField is active. Set it in step 8 and use it in step 9
        var activeTextField = UITextField()
    
        override func viewDidLoad() {
            super.viewDidLoad()
            view.backgroundColor = .white
    
            // 5. set the regular UItextField delegate to each textField
            otpTextField1.delegate = self
            otpTextField2.delegate = self
            otpTextField3.delegate = self
            otpTextField4.delegate = self
    
            // 6. set the subClassed textField delegate to each textField
            otpTextField1.myDelegate = self
            otpTextField2.myDelegate = self
            otpTextField3.myDelegate = self
            otpTextField4.myDelegate = self
    
            configureAnchors()
    
            // 7. once the screen appears show the keyboard 
            otpTextField1.becomeFirstResponder()
        }
    
    // 8. when a textField is active set the activeTextField property to that textField
    func textFieldDidBeginEditing(_ textField: UITextField) {
    
        activeTextField = textField
    }
    
    // 9. when the backButton is pressed, the MyTextField delegate will get called. The activeTextField will let you know which textField the backButton was pressed in. Depending on the textField certain textFields will become enabled and disabled.
    func textFieldDidDelete() {
    
        if activeTextField == otpTextField1 {
            print("backButton was pressed in otpTextField1")
            // do nothing
        }
    
        if activeTextField == otpTextField2 {
            print("backButton was pressed in otpTextField2")
            otpTextField2.isEnabled = false
            otpTextField1.isEnabled = true
            otpTextField1.becomeFirstResponder()
            otpTextField1.text = ""
        }
    
        if activeTextField == otpTextField3 {
            print("backButton was pressed in otpTextField3")
            otpTextField3.isEnabled = false
            otpTextField2.isEnabled = true
            otpTextField2.becomeFirstResponder()
            otpTextField2.text = ""
        }
    
        if activeTextField == otpTextField4 {
            print("backButton was pressed in otpTextField4")
            otpTextField4.isEnabled = false
            otpTextField3.isEnabled = true
            otpTextField3.becomeFirstResponder()
            otpTextField3.text = ""
        }
    }
    
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    
        if let text = textField.text {
    
            // 10. when the user enters something in the first textField it will automatically adjust to the next textField and in the process do some disabling and enabling. This will proceed until the last textField
            if (text.count < 1) && (string.count > 0) {
    
                if textField == otpTextField1 {
                    otpTextField1.isEnabled = false
                    otpTextField2.isEnabled = true
                    otpTextField2.becomeFirstResponder()
                }
    
                if textField == otpTextField2 {
                    otpTextField2.isEnabled = false
                    otpTextField3.isEnabled = true
                    otpTextField3.becomeFirstResponder()
                }
    
                if textField == otpTextField3 {
                    otpTextField3.isEnabled = false
                    otpTextField4.isEnabled = true
                    otpTextField4.becomeFirstResponder()
                }
    
                if textField == otpTextField4 {
                    // do nothing or better yet do something now that you have all four digits for the sms code. Once the user lands on this textField then the sms code is complete
                }
    
                textField.text = string
                return false
    
            } // 11. if the user gets to the last textField and presses the back button everything above will get reversed
            else if (text.count >= 1) && (string.count == 0) {
    
                if textField == otpTextField2 {
                    otpTextField2.isEnabled = false
                    otpTextField1.isEnabled = true
                    otpTextField1.becomeFirstResponder()
                    otpTextField1.text = ""
                }
    
                if textField == otpTextField3 {
                    otpTextField3.isEnabled = false
                    otpTextField2.isEnabled = true
                    otpTextField2.becomeFirstResponder()
                    otpTextField2.text = ""
                }
    
                if textField == otpTextField4 {
                    otpTextField4.isEnabled = false
                    otpTextField3.isEnabled = true
                    otpTextField3.becomeFirstResponder()
                    otpTextField3.text = ""
                }
    
                if textField == otpTextField1 {
                    // do nothing
                }
    
                textField.text = ""
                return false
    
            } // 12. after pressing the backButton and moving forward again you will have to do what's in step 10 all over again
            else if text.count >= 1 {
    
                if textField == otpTextField1 {
                    otpTextField1.isEnabled = false
                    otpTextField2.isEnabled = true
                    otpTextField2.becomeFirstResponder()
                }
    
                if textField == otpTextField2 {
                    otpTextField2.isEnabled = false
                    otpTextField3.isEnabled = true
                    otpTextField3.becomeFirstResponder()
                }
    
                if textField == otpTextField3 {
                    otpTextField3.isEnabled = false
                    otpTextField4.isEnabled = true
                    otpTextField4.becomeFirstResponder()
                }
    
                if textField == otpTextField4 {
                    // do nothing or better yet do something now that you have all four digits for the sms code. Once the user lands on this textField then the sms code is complete
                }
    
                textField.text = string
                return false
            }
        }
        return true
    }
    
    //**Optional** For a quick setup use this below. Here is how to add a gray line to the textFields and here are the anchors:
        // if your app supports portrait and horizontal your going to have to make some adjustments to this every time the phone rotates
        override func viewDidLayoutSubviews() {
            super.viewDidLayoutSubviews()
    
            addBottomLayerTo(textField: otpTextField1)
            addBottomLayerTo(textField: otpTextField2)
            addBottomLayerTo(textField: otpTextField3)
            addBottomLayerTo(textField: otpTextField4)
        }
    
        // this adds a lightGray line at the bottom of the textField
        func addBottomLayerTo(textField: UITextField) {
            let layer = CALayer()
            layer.backgroundColor = UIColor.lightGray.cgColor
            layer.frame = CGRect(x: 0, y: textField.frame.height - 2, width: textField.frame.width, height: 2)
            textField.layer.addSublayer(layer)
        }
    
        func configureAnchors() {
    
            view.addSubview(staticLabel)
    
            view.addSubview(otpTextField1)
            view.addSubview(otpTextField2)
            view.addSubview(otpTextField3)
            view.addSubview(otpTextField4)
    
            let width = view.frame.width / 5
    
            staticLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 15).isActive = true
            staticLabel.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10).isActive = true
            staticLabel.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10).isActive = true
    
            // textField 1
            otpTextField1.topAnchor.constraint(equalTo: staticLabel.bottomAnchor, constant: 10).isActive = true
            otpTextField1.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10).isActive = true
            otpTextField1.widthAnchor.constraint(equalToConstant: width).isActive = true
            otpTextField1.heightAnchor.constraint(equalToConstant: width).isActive = true
    
            // textField 2
            otpTextField2.topAnchor.constraint(equalTo: staticLabel.bottomAnchor, constant: 10).isActive = true
            otpTextField2.leadingAnchor.constraint(equalTo: otpTextField1.trailingAnchor, constant: 10).isActive = true
            otpTextField2.widthAnchor.constraint(equalTo: otpTextField1.widthAnchor).isActive = true
            otpTextField2.heightAnchor.constraint(equalToConstant: width).isActive = true
    
            // textField 3
            otpTextField3.topAnchor.constraint(equalTo: staticLabel.bottomAnchor, constant: 10).isActive = true
            otpTextField3.leadingAnchor.constraint(equalTo: otpTextField2.trailingAnchor, constant: 10).isActive = true
            otpTextField3.widthAnchor.constraint(equalTo: otpTextField1.widthAnchor).isActive = true
            otpTextField3.heightAnchor.constraint(equalToConstant: width).isActive = true
    
            // textField 4
            otpTextField4.topAnchor.constraint(equalTo: staticLabel.bottomAnchor, constant: 10).isActive = true
            otpTextField4.leadingAnchor.constraint(equalTo: otpTextField3.trailingAnchor, constant: 10).isActive = true
            otpTextField4.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10).isActive = true
            otpTextField4.widthAnchor.constraint(equalTo: otpTextField1.widthAnchor).isActive = true
            otpTextField4.heightAnchor.constraint(equalToConstant: width).isActive = true
         }
    }
    

    This is separate from the answer above but if you need to add multiple characters to each otpTextField then follow this answer.

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