我正在尝试以这种格式输入用户酋长国ID
为此,我尝试并遵循
https://stackoverflow.com/a/54601324/428240
它的工作正常,如果我有一个字符,但如果你检查图像,我……
这类似于 我的另一个答案 。您可以将其复制并粘贴到文件中并运行它以查看其工作原理。
这个是不同的,因为OP想知道阿联酋航空如何在每个textField中使用多个数字来使用textFields。我不知道它们是如何工作的,但这就是UberEats如何将它们的短信文本字段工作,所以我将它们组合在一起,允许每个textField中有多个数字。您不能随意按下textField并选择它。使用此功能,您只能向前和向后移动。 ux是主观的,但如果Uber使用它,则ux必须是有效的。我说它很相似,因为它们还有一个覆盖textField的灰色框,所以我不确定它背后会发生什么。这是我能得到的最接近的。
首先,您必须继承UITextField的子类 用这个答案 检测何时按下退格按钮。当按下后退按钮时,在步骤11中存在与每个textField相关的逻辑,该逻辑确定每个textField内应该是什么文本。
其次,一旦char在textField中,你必须阻止用户选择光标的左侧 用这个答案 。您可以在第一步中覆盖同一子类中的方法。
第三,您需要检测哪个textField当前处于活动状态 用这个答案
第四,为每个textField添加一个addTarget方法,以便在用户输入时进行监视。我将它添加到每个textField中,您将在viewDidLoad底部的步骤8中看到它们。跟随 这个答案 看看它是如何工作的。
我正在以编程方式执行所有操作,因此您可以将整个代码复制并粘贴到项目中并运行它
首先创建一个UITextField的子类,并将其命名为MyTextField(它位于文件的顶部)。
在具有OTP文本字段的类中,将类设置为使用UITextFieldDelegate和MyTextFieldDelegate,然后创建一个类属性并将其命名为activeTextField。当textFieldDidBeginEditing中的textField变为活动时,您将activeTextField设置为该值。在viewDidLoad中,将所有textField设置为使用两个委托。 addTargets方法也在那里。你可以把它们放在每个附带的代理方法中 textField closure 更清洁的代码。如果这样做,请更改每个textField lazy var 代替 let 。
textField closure
lazy var
let
确保第一个otpTextField已启用,第二个,第三个和第四个otpTextFields最初是DIASABLED
所有内容都在编号为1-12的代码行上方的注释中进行了解释
import UIKit 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 } } // 2. set the class to use 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: 18) 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: 18) 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: 18) 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: 18) 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. add this method to each textField's target action so it can be monitored while the user is typing otpTextField1.addTarget(self, action: #selector(monitorTextFieldWhileTyping(_ :)), for: .editingChanged) otpTextField2.addTarget(self, action: #selector(monitorTextFieldWhileTyping(_ :)), for: .editingChanged) otpTextField3.addTarget(self, action: #selector(monitorTextFieldWhileTyping(_ :)), for: .editingChanged) otpTextField4.addTarget(self, action: #selector(monitorTextFieldWhileTyping(_ :)), for: .editingChanged) } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() addBottomLayerTo(textField: otpTextField1) addBottomLayerTo(textField: otpTextField2) addBottomLayerTo(textField: otpTextField3) addBottomLayerTo(textField: otpTextField4) } // 9. when a textField is active set the activeTextField property to that textField func textFieldDidBeginEditing(_ textField: UITextField) { activeTextField = textField } // 10. 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() } if activeTextField == otpTextField3 { print("backButton was pressed in otpTextField3") otpTextField3.isEnabled = false otpTextField2.isEnabled = true otpTextField2.becomeFirstResponder() } if activeTextField == otpTextField4 { print("backButton was pressed in otpTextField4") otpTextField4.isEnabled = false otpTextField3.isEnabled = true otpTextField3.becomeFirstResponder() } } // 11. as the user types it will check which textField you are in and then once the text.count is what you want for that textField it will jump to the next textField @objc func monitorTextFieldWhileTyping(_ textField: UITextField) { if let text = textField.text { if text.count >= 1 { // otpTextField1 if textField == otpTextField1 { if let textInOtpTextField1 = otpTextField1.text { if textInOtpTextField1.count == 3 { otpTextField1.isEnabled = false otpTextField2.isEnabled = true otpTextField2.becomeFirstResponder() } // when the user presses the back button in textInOtpTextField2, now that they're back in textInOtpTextField1 if the conditional statement below is met this will execute if textInOtpTextField1.count > 3 { let firstThreeCharsInTextField1 = textInOtpTextField1.prefix(3) let convertFirstThreeToString = String(firstThreeCharsInTextField1) DispatchQueue.main.async { [weak self] in self?.otpTextField1.text = convertFirstThreeToString } otpTextField1.isEnabled = false otpTextField2.isEnabled = true otpTextField2.becomeFirstResponder() let convertLastCharToString = String(textInOtpTextField1.last!) otpTextField2.text = convertLastCharToString } } } // otpTextField2 if textField == otpTextField2 { if let textInOtpTextField2 = otpTextField2.text { if textInOtpTextField2.count == 4 { otpTextField2.isEnabled = false otpTextField3.isEnabled = true otpTextField3.becomeFirstResponder() } // when the user presses the back button in textInOtpTextField3, now that they're back in textInOtpTextField2 if the conditional statement below is met this will execute if textInOtpTextField2.count > 4 { let firstFourCharsInTextField2 = textInOtpTextField2.prefix(4) let convertFirstFourToString = String(firstFourCharsInTextField2) DispatchQueue.main.async { [weak self] in self?.otpTextField2.text = convertFirstFourToString } otpTextField2.isEnabled = false otpTextField3.isEnabled = true otpTextField3.becomeFirstResponder() let convertLastCharToString = String(textInOtpTextField2.last!) otpTextField3.text = convertLastCharToString } } } // otpTextField3 if textField == otpTextField3 { if let textInOtpTextField3 = otpTextField3.text { if textInOtpTextField3.count == 7 { otpTextField3.isEnabled = false otpTextField4.isEnabled = true otpTextField4.becomeFirstResponder() } // when the user presses the back button in textInOtpTextField4, now that they're back in textInOtpTextField4 if the conditional statement below is met this will execute if textInOtpTextField3.count > 7 { let firstSevenCharsInTextField3 = textInOtpTextField3.prefix(7) let convertFirstSevenToString = String(firstSevenCharsInTextField3) DispatchQueue.main.async { [weak self] in self?.otpTextField3.text = convertFirstSevenToString } otpTextField3.isEnabled = false otpTextField4.isEnabled = true otpTextField4.becomeFirstResponder() let convertLastCharToString = String(textInOtpTextField3.last!) otpTextField4.text = convertLastCharToString } } } if textField == otpTextField4 { // do nothing } textField.text = text } } } // 12. Use this delegate method to limit the text in otpTextField4 so that it can only take 1 character func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { if textField == otpTextField4 { textField.text = string return false } return true } } extension ViewController { // 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 } }