I'm developing an app for Ipad. I'm designing a forgot password screen to allow user to enter password to UITextField. By design, the password only allow numeric input. I can set UITextFiled keyboardtype to be phonepad in Iphone but the option seem not working for Ipad (Ipad always show the full keyboard layout). How can we achieve the keyboard for Ipad app that only have number?
Do I have to design my own keyboard layout?
Any help is much appreciate. Thanks!
The keyboard type does not dictate what sort of input the textfield accepts, even if you use a custom keyboard that only displays numbers, the user can always paste something or use an external hardware keyboard.To do that, you need to observe the input, for example, by becoming the UITextFieldDelegate and then:
Example in swift:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool{
// non decimal digit character set, better save this as a property instead of creating it for each keyboard stroke
let non_digits = NSCharacterSet.decimalDigits.inverted
// Find location for non digits
let range = string.rangeOfCharacter(from: non_digits)
if range == nil { // no non digits found, allow change
return true
}
return false // range was valid, meaning non digits were found
}
This will prevent any non digit character from being added to the textfield.
There is not a built in number-only (phone/pin) keyboard on iPad
You need to implement your own keyboard if you want this on iPad.
There are many examples out there:
https://github.com/azu/NumericKeypad
https://github.com/lnafziger/Numberpad
https://github.com/benzado/HSNumericField
Yes I was facing the same for iPad hence I used this:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// to avoid any other characters except digits
return string.rangeOfCharacter(from: CharacterSet(charactersIn:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!##$%^&*()-=_+`~[{]}|\\: ;\"/?>.<,'")) == nil
}
Related
I want to disable manual text entering from one-time-code textField while the user only can tap SMS OTP Code from Keyboard Quicktype Bar.
Another question i got from seeing whatsapp is that their input shows the Keyboard Quicktype bar automatically while mine is not unless i call becomeFirstResponder
How can i achieve this?
Thanks.
You can try this, maybe it works for your use case
Remove textField.isEnabled = false if you added it before
Add textField.delegate = self so we can manage what happens when user adds input
Add textField.becomeFirstResponder() to make the keyboard appear
Then implement this UITextFieldDelegate callback
extension YourViewOrViewController: UITextFieldDelegate {
func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
// Only allow multiple characters to be set like the OTP
// Or define your own logic when you want text to be
// accepted into the text field
return string.count != 1
}
}
Check if this gives you the desired result
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.
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
}
}
}
I have created a new project that only has one text field and I set the capitalization to all characters. I tried this from both interface builder and code:
[self.textField setAutocapitalizationType:UITextAutocapitalizationTypeAllCharacters];
No matter what I try, this is the result:
I am aware that the keyboard Auto-Capitalization settings can be changed from Settings - General - Keyboard - Auto-Capitalization, but I assume there would be no purpose in having the AutocapitalizationType property on a text field if it is overwritten by the iOS anyway.
It is also not working for UITextAutocapitalizationTypeWords.
This is happening on iOS 10.0.2 on an iPhone 6S (other answers say that it happens in simulator, but this is not the case).
Any idea what is the issue?
I am aware that the keyboard Auto-Capitalization settings can be
changed from Settings - General - Keyboard - Auto-Capitalization, but
I assume there would be no purpose in having the
AutocapitalizationType property on a text field if it is overwritten
by the iOS anyway.
You're right, the this switch in settings should be on. But it does not override the value of textfields in an app. If this toggle is on, all textfields which want to capitilize user's input will be allowed to do so and textfields with UITextAutocapitalizationTypeNone value won't capitalize anything.
One way I enforced an all-caps presentation in textField regardless of the autocapitalization directive, was to insert this little twist in the UITextFieldDelegate func:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let str = string.capitalized
let result = (textField.text as NSString?)?.replacingCharacters(in: range, with: str) ?? str
textField.text = result
return false
}
This will capitalize inserted characters too, as expected.
Using Storyboard, I setup a UITextField and two static UILabels. I setup the constraints so the static labels spread out as user types in the TextField.
[Label1] [TextField] [Label2]
[Label1] [TextFieldIsGettingFilled] [Label2]
Everything is fine until now. The TextField is getting wider as user types in. However, if the user uses backspace (deletes character), TextField doesn't get narrower. So it becomes like:
[Label1] [TextField ] [Label2]
What is the way of detecting backspace and narrow TextFields' width accordingly while user is still typing?
EDIT
I made an example and this works (increases and decreases according to text):
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
textField.invalidateIntrinsicContentSize()
return true
}
Source: Resize a UITextField while typing (by using Autolayout)