Swift3: best way to validate the text entered by the user in a UITextField - ios

Evening, in my app I have several UITextfield. Each one has to confirm to different limitations.
For example, I have a date Field, zipCode Field, SSN Field etc.
From the Apple Documentation I found:
Assign a delegate object to handle important tasks, such as:
Determining whether the user should be allowed to edit the text field’s contents.
Validating the text entered by the user.
Responding to taps in the keyboard’s return button.
Forwarding the user-entered text to other parts of your app.
Store a reference to the text field in one of your controller objects.
So I'm pretty sure I have to use delegates and func textFieldDidEndEditing(_:).
The only way that came to my mind is to use a switch statement inside the func textFieldDidEndEditing(_:) to confirm the delegate to the difference limitation.
Is there a better, safer and faster pattern to face this problem?

You can set unique tag to your every text field and can compare in textFieldDidEndEditing or you can take IBOutlet of every textField and can compare it in textFieldDidEndEditing like,
func textFieldDidEndEditing(textField: UITextField) {
// By tag
if textField.tag == 100 {
}
// OR
//by outlet
if textField == self.myTextField {
}
}

You are right, you will have to check the textfield, either you can check tags assigned for different text fields using switch statement like you said,
or you can compare textfields itself,
if textfield1,textfield2 are outlets to two text fields, you can compare as following,
func textFieldDidEndEditing(textField: UITextField)
{
if textField == textfield1
{
}
else if textField == textfield2
{
}
}

you can create enum for validation
enum Type {
case zipcode
case number
}
then you can create a method for validation like this :
func isValidate(text: String, type: Type) -> Bool {
switch type {
case .zipcode:
...
}
}
this method can be in Util class. this is best practice. because your logic is encapsulate from out .

If you need more control over the text which is committed to the text field or if you want to provide feedback while the text field is being edited, you should implement a different delegate instead:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// Build and check the new text value. Accept or reject the change.
}
In the delegate, you can build the new text value. Validate it, apply constraints, decide on which feedback the user should receive ("Your password must be at least eight characters long", "This is not a valid IBAN"), accept or reject the change (i.e. return false).
Keep in mind, that the delegate is not called if you assign the text property manually. Moreover, this delegate is called when text is pasted into or deleted from the text field, which can make matters much more complicated depending on what you do.

Related

iOS: when user select QuickType keyboard handle selected UITextField

In my project when user select specific UITextField (that UITextField supposed to get user telephone number), the QuickType Keyboard show user telephone number. I want when user select his/her telephone number I can change that (remove "+" in telephone number) and show the result in that UITextField. how can I do that?
UPDATE:
I tried shouldChangeCharactersIn (UITextFieldDelegate function) to handle that, but replacementString return space (" ") and if I just return true (doing nothing inside that function) to that nothing will show inside UITextField.
You were on the right track with shouldChangeCharactersIn, but in case with QuickType keyboard it gets called two times instead of one.
First call is made to clean the current string, even when it's empty: range is (0, 0) in this case. Not much sence, but if you type something, select whole text, and paste phone number from quick help, this first change will have range of the full string to clear it.
And to modify input string, you need to update text field text and return false, because you don't need system to update text anymore.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if string.starts(with: "+") {
let modifierString = String(string.dropFirst())
.trimmingCharacters(in: .whitespacesAndNewlines)
textField.text = ((textField.text ?? "") as NSString)
.replacingCharacters(in: range, with: modifierString)
return false
} else {
return true
}
}
Note that it'll also ignore press of "+" made by user, if you don't want that you probably need to add more logic to that if, like
if string.count > 1 && string.starts(with: "+") {
This will allow user to press "+" but remove it from any pasted content.
If your objective is to remove any extra characters and keep only numbers, you could in the first place restrict the user to inputting only numbers by selecting the "Number Pad" keyboard type in the attributes inspector of the text field in the storyboard.
Or if you'd like to do it with code:
textField.keyboardType = .numberPad
Sorry if this is not what you are looking for, I just thought maybe you overcomplicated the problem and resorted to filtering the text to numbers, when there is an Apple-provided type of keyboard exactly for collecting phone numbers that you could use instead.

iOS autofill assumes the field before password is the username

The autofill works incredibly well, but I have one scenario when it isn't optimal and I am not aware of a work around a perhaps a proper way of doing it.
I have a screen with 2 UITextFields, first one is Amount, a monetary value to be transferred to another person. The second field is the password, the user will need to re-enter his password before the transaction can be completed.
The first field (Amount) has content type set as .unspecified and the second field (password) has it set to .password
When the user taps the second field Autofill beautifully suggest the the password to be used, but once the correct password is tapped, iOS automatically assumes the first field (amount) is the username and fills it with the username associated with the selected account, overwriting the amount the user had previously entered.
Is there a way to force password only autofill?
Today i face the same issue, with some different way. My situation is taking input of mobile number and password entry into the login page. Native app only support for mobile number while the web app support for email only. So while iOS autofill is in action, it fill the mobile number field with the email field, which is not acceptable.
After playing sometime with the autofill, i have found the life-cycle of the UITextField delegate is somewhat different in case of autofill.
When a autofill is tapped which is provided in the top of the keyboard, the UITextFieldDelegate start working from the beginning. Although the keyboard is open the delegate method started from the current with call order as follows
textField(_:shouldChangeCharactersIn:replacementString:)
textFieldShouldEndEditing(_:)
This delegate calls without the keyboard dismissing and re-appear again. This is unusual. Returning false in the textField(_:shouldChangeCharactersIn:replacementString:) has no effect in this case.
So theoretically i have the chance to edit the mobile field in the textFieldShouldEndEditing. To do that i keep track of the text which was present before the autofill begin. so took two variable as follows
var previousText: String?
var nextText: String?
Whenever a UITextField begin editing, i save it in previousText as following
public func textFieldDidBeginEditing(_ textField: UITextField) {
previousText = textField.text
}
then i track the changes inside the textField(_:shouldChangeCharactersIn:replacementString:) as following
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
nextText = self.getCompleteString(original: textField.text, replacingRange: range, withString: string)
// YOU MAY RETURN `true` OR `false` BASED ON KEYBOARD TYPING, BUT RETURNING `false` IN CASE OF AUTOFILL HAS NO EFFECT. SO I ASSUME, YOU RETURN `true` ALWAYS.
return true;
}
func getCompleteString(original: String?, replacingRange: NSRange, withString: String) -> String? {
guard var originalText = original else {
return nil
}
guard let range = Range<String.Index>.init(replacingRange, in: originalText) else {
return nil
}
originalText.replaceSubrange(range, with: withString)
return originalText
}
Now the most interesting part, detecting custom requirement (in my case detecting a possible valid mobile number, where your case was detecting a valid amount)
func textFieldDidEndEditing(_ textField: UITextField) {
if textField == MY_MOBILE_TEXT_FIELD {
if nextText.IS_POSSIBLE_VALID_MOBILE_NUMBER() { // my function to detect the possible valid mobile number
textField.text = nextText
} else {
textField.text = previousText
}
}
}
It worked for me, hope it works for you too.
Autofill disregards what textfield delegate methods return
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
So can try another hack which worked for me,
You can set textContentType of amount text field to password type and use a toolbar on the keyboard to side autofill suggestions(Since you would be using num pad for entering digits in the amount field so you might already be using a custom toolbar).
amount_text_field.textContentType = .password
This way, iOS will assume it to be a password textfield and would not fill usernames or passwords which were applied from other textfields. And using toolbar should help you hide autofill suggestions on this textfield.

Delegates VS IBAction for handling events iOS

I am new to swift and new to iOS development. I am currently struggling to understand why delegates are used to handle events that happen in the UI.
So far, in the tutorial I am working through, I have only ever done things like the following code segment to handle UI events:
#IBAction func fahrenheitFieldEditingChanged(_ textField: UITextField) {
if let text = textField.text, let value = Double(text) {
fahrenheitValue = Measurement(value: value, unit: .fahrenheit)
} else {
fahrenheitValue = nil
}
}
But now the tutorial I am working through is having me use a delegate to to handle another UI event from the same text field. Why is it done this way? What is the point of using delegates rather than just write actions to handle the events?
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let existingTextHasDecimalSeparator = textField.text?.range(of: ".")
let replacementTextHasDecimalSeparator = string.range(of: ".")
if existingTextHasDecimalSeparator != nil, replacementTextHasDecimalSeparator != nil {
return false
} else {
return true
}
}
Take a look at UITextViewDelegate. It'll become easier to understand.
Responding to Editing Notifications
func textViewShouldBeginEditing(UITextView)
Asks the delegate if editing should begin in the specified text view.
func textViewDidBeginEditing(UITextView)
Tells the delegate that editing of the specified text view has begun.
func textViewShouldEndEditing(UITextView)
Asks the delegate if editing should stop in the specified text view.
func textViewDidEndEditing(UITextView)
Tells the delegate that editing of the specified text view has ended.
Responding to Text Changes
func textView(UITextView, shouldChangeTextIn: NSRange, replacementText: String)
Asks the delegate whether the specified text should be replaced in the text view.
func textViewDidChange(UITextView)
Tells the delegate that the text or attributes in the specified text view were changed by the user.
There is more, but I won't copy/paste anymore you can see yourself. What you see here are things that you can tune in if you want.
If you want you can tune in and get the callback of when the user starts typing. Or you can tune in and get the callback of when the user ended his editing.
How can you find out when the user stoped or started using IBAction?!
Once* you set yourself as a delegate (textViewInstance.delegate = self, you can then choose to get any of these callbacks.
*To be 100% accurate, you need to do that, but also adopt the UITextViewDelegate protocol and then conform to it using the mentioned delegate callbacks
Delegate is a design pattern that allows you to easily customize the behavior of several objects in one central object.
Actions are merely user interactions.
It's not possible to replace delegate callbacks with actions. For instance, if you have a WebView which fails to load, then how does it inform your object using an action?
In your example, an action cannot return properties like shouldChangeCharactersIn and replacementString. You need a delegate for that.
Read more...
Delegate pattern (Apple docs)

How to detect when user used Password AutoFill on a UITextField

I've implemented all the app and server changes necessary to support Password Autofill on iOS 11, and it works well. I'd like it to work a little better.
My username and password fields are UITextFields. I would like to identify when a user has "autofilled" one of the two UITextFields, so I can progress to the next step. Currently the user autofills an item, then needs to press the "Next" button on the on-screen keyboard in order to advance. I'd like to trigger this on behalf of the user.
The WWDC2017 Password Autofill session says to use UITextFieldTextDidChange. This works, but of course this is also triggered when a user is manually typing in those fields.
My thought has been to compare the prior version of the text with the new version of the text, and assume that if the length has increased from zero to greater than some minimal length (2 or more), the user used autofill. That should work most of the time, but has a risk of a false trigger (fast typing on slow device perhaps). So to me, this may be a risky assumption.
I'm curious is anyone has found a more surefire way to determine if Password Autofill has been used on a UITextField, or just thinks my worry about a false trigger is unfounded.
Not sure if the previous answer stopped working at some point, but I can't get it to work—I only get a single didBeginEditing call when AutoFill is used.
However, I did find a way to detect AutoFill. And keep in mind that it is possible for AutoFill to be used after some characters have already been entered, for example if the user has already typed some numbers in the phone number, then they AutoFill the full number.
For Swift 4/5, add the following to the delegate of the UITextField:
private var fieldPossibleAutofillReplacementAt: Date?
private var fieldPossibleAutofillReplacementRange: NSRange?
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// To detect AutoFill, look for two quick replacements. The first replaces a range with a single space
// (or blank string starting with iOS 13.4).
// The next replaces the same range with the autofilled content.
if string == " " || string == "" {
self.fieldPossibleAutofillReplacementRange = range
self.fieldPossibleAutofillReplacementAt = Date()
} else {
if fieldPossibleAutofillReplacementRange == range, let replacedAt = self.fieldPossibleAutofillReplacementAt, Date().timeIntervalSince(replacedAt) < 0.1 {
DispatchQueue.main.async {
// Whatever you use to move forward.
self.moveForward()
}
}
self.fieldPossibleAutofillReplacementRange = nil
self.fieldPossibleAutofillReplacementAt = nil
}
return true
}
Found a solution.
When the password manager is used to autofill username + password, it will trigger didBeginEditing twice, faster than a human ever could.
So, I calculate the time between the events. If the time is extremely fast, then I assume that autofill (e.g. FaceID or TouchID) was used to enter credentials and auto-trigger whatever UI is next -- in my case, the User tapping "Sign-in".
Obviously, you have to set up the correct delegation of the UITextFields you want to monitor, but once you do that:
var biometricAutofillTime: Date!
func textFieldDidBeginEditing(_ textField: UITextField) {
if biometricAutofillTime != nil {
if Date().timeIntervalSince(biometricAutofillTime) < 0.1 {
// NOTE: Need to hesitate for a very short amount of time,
// because, otherwise, the second UITextField (password)
// won't yet be populated
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { self.didTapSignin() }
}
biometricAutofillTime = nil
}
biometricAutofillTime = Date()
}
This detects when user has autofilled via passwords. It may also trigger when user pastes text from their clipboard. (If textfield is empty)
You can probably handle the logic to remove user pasted cases with this link..
how to know when text is pasted into UITextView
private var didAutofillTextfield: Bool = false {
didSet {
if didAutofillTextfield {
// Fire analytics for user autofilling
}
}
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// If the range is {0,0} and the string count > 1, then user copy paste text or used password autofill.
didAutofillTextfield = range == NSRange(location: 0, length: 0) && string.count > 1
return true
}
I used this delegate method:
func textFieldDidChangeSelection(_ textField: UITextField) {
// call when user select something
}
from documentation:
Method
textFieldDidChangeSelection(_:)
Tells the delegate when the text selection changes in the specified text field.te
I don't think there is a better solution.
One thing I noticed is that autofill is only enabled when the text field is empty.
So if the text field went from empty to a length greater than the minimum password/username, then it is most likely autofill/paste.
I am using shouldChangeCharactersIn to detect the change in the UITextField. I'm not for sure if there is a case where text from the keyboard could be batched together before the delegate method is called.
I'd like to trigger this on behalf of the user.
If this is your primary goal, I'm doing a little different approach here.
Upon showing the login form, I first check iCloud Keychain with SecRequestSharedWebCredential. If the closure returns a credentials, which means user's intent is to login with it, then I automatically login for him/her. Otherwise, make the login text filed becomeFirstResponder().
This approach does not support third-party password manager, but most people use iCloud Keychain I believe.
Swift 5:
The following delegate method gets triggered every time that the user types something in the textfield. That string count is usually one, so any number great than that is either autofill or the user pasting some text into the field. I am only using this delegate on this password field.
extension LoginView: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if string.count > 1 {
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) { [weak self] in
self?.endEditing(true)
}
}
return true
}
}
I found another simple way.. Hope it will help who is looking for it.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// Usually string is coming as single character when user hit the keyboard ..
// but at AutoFill case it will come as whole string which recieved ..
// at this moment you can replace what you currently have by what you have recieved.
// In below case I'm expecting to recieve 4 digits as OTP code .. you can change it by what you are expecting to recieve.
if string.count == 4 {
doWhatEverYouByAutoFillString(text: string)
return true
}
}
}

Populating information in swift 3 for iPad

Let's say I have a textfield where I tell the user to put in their name. The 'name' then gets populated into all the other appropriate fields. For example, say the 'other field' is where the person has to sign a document. Therefore, underneath that 'line' where the person would have to sign the document, the persons name will then be populated there.
How would I go about to fill out information in one part and it would then be populated in all the appropriate fields?
You could implement UITextFieldDelegate and your delegate will be called when the user enter something in your text field.
First set the delegate to self
txtField.delegate = self
Use this if you want to change the other fields when the user is done editiing your master field
func textFieldDidEndEditing(_ textField: UITextField, reason: UITextFieldDidEndEditingReason) {
// change other fields here
otherField.text = textField.text
}

Resources