Swift - 如何在每个单独的 SMS Otp UITextfield 中放置多个字符
Swift -How to put multiple characters in each individual SMS Otp UITextfield
我正在尝试以这种格式输入用户阿联酋 ID
为此,我尝试并遵循
如果我有一个字符,它工作正常,但如果你检查图像,我希望第一个文本字段显示 3 个字符,第二个显示 4 个,第三个显示 7 个,第四个文本字段显示 1 个字符。而且,如果我尝试从文本字段中删除字符,它不应该全部删除,而只是一个一个地删除,然后在文本字段中出现注释后移动到另一个。
谁能帮我解决这个问题。提前致谢
protocol EmiratesIdTextFieldDelegate: class {
func textFieldDidDelete()
}
import UIKit
class EmiratesIdTextField: UITextField {
weak var emiratesIdTextFieldDelegate: EmiratesIdTextFieldDelegate? // make sure to declare this as weak to prevent a memory leak/retain cycle
override func deleteBackward() {
super.deleteBackward()
emiratesIdTextFieldDelegate?.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
}
}
class PinViewController: UIViewController,EmiratesIdTextFieldDelegate,UITextFieldDelegate {
@IBOutlet weak var textField1: EmiratesIdTextField!
@IBOutlet weak var textField2: EmiratesIdTextField!
@IBOutlet weak var textField3: EmiratesIdTextField!
@IBOutlet weak var textField4: EmiratesIdTextField!
var activeTextField = UITextField()
override func viewDidLoad() {
super.viewDidLoad()
textField1.delegate = self
textField2.delegate = self
textField3.delegate = self
textField4.delegate = self
textField1.emiratesIdTextFieldDelegate = self
textField2.emiratesIdTextFieldDelegate = self
textField3.emiratesIdTextFieldDelegate = self
textField4.emiratesIdTextFieldDelegate = self
// configureAnchors()
textField1.becomeFirstResponder()
}
func textFieldDidBeginEditing(_ textField: UITextField) {
activeTextField = textField
}
func textFieldDidDelete() {
if activeTextField == textField1 {
print("backButton was pressed in otpTextField1")
// do nothing
}
if activeTextField == textField2 {
print("backButton was pressed in otpTextField2")
textField2.isEnabled = false
textField1.isEnabled = true
textField1.becomeFirstResponder()
textField1.text = ""
}
if activeTextField == textField3 {
print("backButton was pressed in otpTextField3")
textField3.isEnabled = false
textField2.isEnabled = true
textField2.becomeFirstResponder()
textField2.text = ""
}
if activeTextField == textField4 {
print("backButton was pressed in otpTextField4")
textField4.isEnabled = false
textField3.isEnabled = true
textField3.becomeFirstResponder()
textField3.text = ""
}
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let text = textField.text
if let text = 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 == textField1 {
textField1.isEnabled = false
textField2.isEnabled = true
textField2.becomeFirstResponder()
}
if textField == textField2 {
textField2.isEnabled = false
textField3.isEnabled = true
textField3.becomeFirstResponder()
}
if textField == textField3 {
textField3.isEnabled = false
textField4.isEnabled = true
textField4.becomeFirstResponder()
}
if textField == textField4 {
// do nothing
}
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 == textField2 {
textField2.isEnabled = false
textField2.isEnabled = true
textField1.becomeFirstResponder()
textField1.text = ""
}
if textField == textField3 {
textField3.isEnabled = false
textField2.isEnabled = true
textField2.becomeFirstResponder()
textField2.text = ""
}
if textField == textField4 {
textField4.isEnabled = false
textField3.isEnabled = true
textField3.becomeFirstResponder()
textField3.text = ""
}
if textField == textField1 {
// 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 == textField1 {
textField1.isEnabled = false
textField2.isEnabled = true
textField2.becomeFirstResponder()
}
if textField == textField2 {
textField2.isEnabled = false
textField3.isEnabled = true
textField3.becomeFirstResponder()
}
if textField == textField3 {
textField3.isEnabled = false
textField4.isEnabled = true
textField4.becomeFirstResponder()
}
if textField == textField4 {
// do nothing
}
textField.text = string
return false
}
}
return true
}
}
这类似于。您可以将其复制并粘贴到一个文件中,然后 运行 看看它是如何工作的。
这个是不同的,因为 OP 想知道阿联酋航空如何在每个 textField 中使用多个数字来使用他们的 textFields。我不知道他们的工作原理,但这就是 UberEats 的短信文本字段的工作方式,所以我将它们组合在一起,允许每个文本字段中有多个数字。您不能随便按一个 textField 并 select 它。使用它你只能向前和向后移动。 ux 是主观的,但如果优步使用它,ux 必须是有效的。我说它很相似,因为它们也有一个灰色框覆盖 textField,所以我不确定它背后发生了什么。这是我能得到的最接近的。
首先,您必须使用 subclass UITextField using this answer 来检测何时按下退格键。当按下后退按钮时,第 11 步中的每个 textField 都有逻辑,确定每个 textField 中应该包含什么文本。
其次,一旦字符位于文本字段 using this answer 内,您将不得不阻止用户 select 光标的左侧。您从第一步覆盖了同一个子类中的方法。
第三,您需要检测哪个文本字段当前处于活动状态
第四,向每个文本字段添加一个addTarget 方法,以在用户键入时进行监视。我将它添加到每个 textField,您将在 viewDidLoad 底部的第 8 步中看到它们。关注 以了解其运作方式。
我正在以编程方式执行所有操作,因此您可以将整个代码复制并粘贴到一个项目中,然后 运行 它
首先创建 UITextField 的子类并将其命名为 MyTextField(位于文件顶部)。
其次在 class 中使用 OTP 文本字段,将 class 设置为使用 UITextFieldDelegate 和 MyTextFieldDelegate,然后创建一个 class 属性 并将其命名为 activeTextField .当 textFieldDidBeginEditing 中的任何一个 textField 变为活动时,您将 activeTextField 设置为那个。在 viewDidLoad 中,将所有文本字段设置为使用两个委托。 addTargets 方法也在那里。您可以将它们放在每个 textField closure
中并伴随委托方法以获得更清晰的代码。如果您这样做,请将每个文本字段更改为以 lazy var
开头,而不是 let
.
确保第一个 otpTextField 已启用,第二个、第三个和第四个 otpTextField 最初都是禁用的
一切都在编号为 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
}
}
我正在尝试以这种格式输入用户阿联酋 ID
为此,我尝试并遵循
如果我有一个字符,它工作正常,但如果你检查图像,我希望第一个文本字段显示 3 个字符,第二个显示 4 个,第三个显示 7 个,第四个文本字段显示 1 个字符。而且,如果我尝试从文本字段中删除字符,它不应该全部删除,而只是一个一个地删除,然后在文本字段中出现注释后移动到另一个。
谁能帮我解决这个问题。提前致谢
protocol EmiratesIdTextFieldDelegate: class {
func textFieldDidDelete()
}
import UIKit
class EmiratesIdTextField: UITextField {
weak var emiratesIdTextFieldDelegate: EmiratesIdTextFieldDelegate? // make sure to declare this as weak to prevent a memory leak/retain cycle
override func deleteBackward() {
super.deleteBackward()
emiratesIdTextFieldDelegate?.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
}
}
class PinViewController: UIViewController,EmiratesIdTextFieldDelegate,UITextFieldDelegate {
@IBOutlet weak var textField1: EmiratesIdTextField!
@IBOutlet weak var textField2: EmiratesIdTextField!
@IBOutlet weak var textField3: EmiratesIdTextField!
@IBOutlet weak var textField4: EmiratesIdTextField!
var activeTextField = UITextField()
override func viewDidLoad() {
super.viewDidLoad()
textField1.delegate = self
textField2.delegate = self
textField3.delegate = self
textField4.delegate = self
textField1.emiratesIdTextFieldDelegate = self
textField2.emiratesIdTextFieldDelegate = self
textField3.emiratesIdTextFieldDelegate = self
textField4.emiratesIdTextFieldDelegate = self
// configureAnchors()
textField1.becomeFirstResponder()
}
func textFieldDidBeginEditing(_ textField: UITextField) {
activeTextField = textField
}
func textFieldDidDelete() {
if activeTextField == textField1 {
print("backButton was pressed in otpTextField1")
// do nothing
}
if activeTextField == textField2 {
print("backButton was pressed in otpTextField2")
textField2.isEnabled = false
textField1.isEnabled = true
textField1.becomeFirstResponder()
textField1.text = ""
}
if activeTextField == textField3 {
print("backButton was pressed in otpTextField3")
textField3.isEnabled = false
textField2.isEnabled = true
textField2.becomeFirstResponder()
textField2.text = ""
}
if activeTextField == textField4 {
print("backButton was pressed in otpTextField4")
textField4.isEnabled = false
textField3.isEnabled = true
textField3.becomeFirstResponder()
textField3.text = ""
}
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let text = textField.text
if let text = 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 == textField1 {
textField1.isEnabled = false
textField2.isEnabled = true
textField2.becomeFirstResponder()
}
if textField == textField2 {
textField2.isEnabled = false
textField3.isEnabled = true
textField3.becomeFirstResponder()
}
if textField == textField3 {
textField3.isEnabled = false
textField4.isEnabled = true
textField4.becomeFirstResponder()
}
if textField == textField4 {
// do nothing
}
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 == textField2 {
textField2.isEnabled = false
textField2.isEnabled = true
textField1.becomeFirstResponder()
textField1.text = ""
}
if textField == textField3 {
textField3.isEnabled = false
textField2.isEnabled = true
textField2.becomeFirstResponder()
textField2.text = ""
}
if textField == textField4 {
textField4.isEnabled = false
textField3.isEnabled = true
textField3.becomeFirstResponder()
textField3.text = ""
}
if textField == textField1 {
// 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 == textField1 {
textField1.isEnabled = false
textField2.isEnabled = true
textField2.becomeFirstResponder()
}
if textField == textField2 {
textField2.isEnabled = false
textField3.isEnabled = true
textField3.becomeFirstResponder()
}
if textField == textField3 {
textField3.isEnabled = false
textField4.isEnabled = true
textField4.becomeFirstResponder()
}
if textField == textField4 {
// do nothing
}
textField.text = string
return false
}
}
return true
}
}
这类似于
这个是不同的,因为 OP 想知道阿联酋航空如何在每个 textField 中使用多个数字来使用他们的 textFields。我不知道他们的工作原理,但这就是 UberEats 的短信文本字段的工作方式,所以我将它们组合在一起,允许每个文本字段中有多个数字。您不能随便按一个 textField 并 select 它。使用它你只能向前和向后移动。 ux 是主观的,但如果优步使用它,ux 必须是有效的。我说它很相似,因为它们也有一个灰色框覆盖 textField,所以我不确定它背后发生了什么。这是我能得到的最接近的。
首先,您必须使用 subclass UITextField using this answer 来检测何时按下退格键。当按下后退按钮时,第 11 步中的每个 textField 都有逻辑,确定每个 textField 中应该包含什么文本。
其次,一旦字符位于文本字段 using this answer 内,您将不得不阻止用户 select 光标的左侧。您从第一步覆盖了同一个子类中的方法。
第三,您需要检测哪个文本字段当前处于活动状态
第四,向每个文本字段添加一个addTarget 方法,以在用户键入时进行监视。我将它添加到每个 textField,您将在 viewDidLoad 底部的第 8 步中看到它们。关注
我正在以编程方式执行所有操作,因此您可以将整个代码复制并粘贴到一个项目中,然后 运行 它
首先创建 UITextField 的子类并将其命名为 MyTextField(位于文件顶部)。
其次在 class 中使用 OTP 文本字段,将 class 设置为使用 UITextFieldDelegate 和 MyTextFieldDelegate,然后创建一个 class 属性 并将其命名为 activeTextField .当 textFieldDidBeginEditing 中的任何一个 textField 变为活动时,您将 activeTextField 设置为那个。在 viewDidLoad 中,将所有文本字段设置为使用两个委托。 addTargets 方法也在那里。您可以将它们放在每个 textField closure
中并伴随委托方法以获得更清晰的代码。如果您这样做,请将每个文本字段更改为以 lazy var
开头,而不是 let
.
确保第一个 otpTextField 已启用,第二个、第三个和第四个 otpTextField 最初都是禁用的
一切都在编号为 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
}
}