Swift - 如何在每个单独的 SMS Otp UITextfield 中放置多个字符 [英] Swift -How to put multiple characters in each individual SMS Otp UITextfield
问题描述
我正在尝试以这种格式输入用户酋长国 ID
为此,我尝试遵循
I'm trying to input user emirates id in this format
for this i try and follow
https://stackoverflow.com/a/54601324/428240
its working fine if i have one character but if you check the image, i want first textfield to show 3 characters, second to show 4 , third to show 7 and fourth textfield to show 1 character. and also if i try to remove character from textfield it should not remove all instead only remove one by one and then move to other once there is noting in textfield.
can any one help me with this. thanks in advance
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
}
}
This is similar to my other answer. You can just copy and paste this into a file and run it to see how it works.
This one is different though because the OP wanted to know how Emirates uses their textFields using multiple digits in each textField. I don't know how their's works but this is how UberEats has their sms textFields working so I combined them both allowing multiple digits in each textField. 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 there is logic pertaining to each textField inside step 11 that determines what text should be inside each textField.
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 add an addTarget method to each textField to monitor as the user types. I added it to each textField and you will see them in step 8 at the bottom of viewDidLoad. Follow this answer to see how it works.
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 (it's at the top of the file).
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. The addTargets methods are in there also. You can just put them and there accompany delegates method in each textField closure
for cleaner code. If you do that change each textField to begin with lazy var
instead of let
.
Make sure the First otpTextField is ENABLED and the second, third, and fourth otpTextFields are ALL initially DIASABLED
Everything is explained in the comments above the lines of code numbered from 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
}
}
这篇关于Swift - 如何在每个单独的 SMS Otp UITextfield 中放置多个字符的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!