How can I rewrite this in swift 4 or 5 - swift

I get an error saying 'characters' is unavailable: Please use String directly.
extension LoginViewController : UITextFieldDelegate {
func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
if let username = self.usernameField.text,
let password = self.passwordField.text {
if ((username.characters.count > 0) && //This is where I get the error
(password.characters.count > 0)) { //This is where I get the error
self.loginButton.isEnabled = true
}
}
return true
}
}
How can I fix this?

Simply remove .characters
extension LoginViewController : UITextFieldDelegate {
func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
if let username = self.usernameField.text,
let password = self.passwordField.text {
if username.count > 0 &&
password.count > 0 {
self.loginButton.isEnabled = true
}
}
return true
}
}

you don't need to state characters to count since it will already be counted when you type username.count or password.count
if ((username.count > 0) &&
(password.count > 0)) {

In Swift never check for empty string or empty collection type with .count > 0, there is the dedicated method isEmpty.
And in Swift parentheses around if expressions are not needed and the && operator can be replaced with a comma
if !self.usernameField.text!.isEmpty, !self.passwordField.text!.isEmpty {
self.loginButton.isEnabled = true
}
The text property of UITextField is never nil so force unwrapping is fine.

Related

Limitate number of string to type in TextField - Swift

I want to limitate my users to type just 3 words inside my TextField. I found some other topic that are talking about to limitate the char in input, but it's not what I want.
Does someone know if it's possible to count the space inside the TextField and do a kind of if statement if more than 3 spaces are typed you can't write anything else?
Ex: "Boys rescued in Thailand" should be blocked after "Boys rescued in"
Thanks in advance!
The quickest solution would be to hook into textField's delegate method shouldChangeCharactersIn
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if let text = textField.text, text.split(separator: " ").count < 3 {
return true
}
return (string == "" || string != " ")
}
I bealeave you could have custom validation function that would take the string from field then, split it to array. And check the length of array.
var fullName = "First Last"
var fullNameArr = split(fullName) {$0 == " "}
var firstName: String = fullNameArr[0]
var lastName: String? = fullNameArr.count > 1 ? fullNameArr[1] : nil
In TextField Delegate method
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
do some check like this
var wordCount = 0
if let text = textField.text {
wordCount = text.split(separator: " ").count
}
if wordCount < 3 {
return true
}
return false

Switching to next UITextField when character limit is reached

I have three text fields to enter in a phone number. I am trying to set the character limit for each textfield to three characters and once this is reached switch to a new textfield.
I saw online to use this code to limit the characters:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let currentCharacterCount = textField.text?.characters.count ?? 0
if (range.length + range.location > currentCharacterCount){
return false
}
let newLength = currentCharacterCount + string.characters.count - range.length
return newLength <= 25
}
and to use this to switch to typing on a new textfield:
.didbecomefirstresponder()
but I am not sure how to limit a text field to 3 characters and then switch to the next field.
This is my code, how I solve this:
The three textfields are:
#IBOutlet var txtField1: UITextField!
#IBOutlet var txtField2: UITextField!
#IBOutlet var txtField3: UITextField!
import UITextFieldDelegate in to your class and set the delegate to self for all the textfields.
And use this method to change focus.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let currentCharacterCount = ((textField.text?.characters.count)! + string.characters.count) - 1
switch (textField, currentCharacterCount) {
case (self.txtField1, 3):
self.txtField2.becomeFirstResponder()
case (self.txtField2, 7):
self.txtField3.becomeFirstResponder()
default:
break
}
return true
}
Here I set character count 3 for the first textfield, and 7 for second textfield.
You could use the delegate method for UITextField shouldChangeCharactersInRange. You'd have to do a little setup work though. Here's an example that creates 3 textFields, conforms to the UITextFieldDelegate, and does something similar to what you're describing.
class ViewController: UIViewController, UITextFieldDelegate {
var max = 3
var fields:[UITextField] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// This is just an example to layout 3 text fields.
for i in 0 ..< 3 {
let field = UITextField()
view.addSubview(field)
field.delegate = self
field.borderStyle = .roundedRect
field.font = UIFont(name: "AvenirNext-Regular", size: 15.0)
field.textColor = UIColor.black
field.frame = CGRect(x: view.bounds.width/2 - 100, y: 100 + (150*CGFloat(i)), width: 200, height: 50)
fields.append(field)
}
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// Make sure the field has non-nil text
if let text = textField.text {
// Check if the text length has reached max limit
if text.characters.count > (max-1) {
// Get index of field from fields array
if let index = fields.index(of: textField) {
// Make sure it's not the last field in the array else return false
if index < fields.count-1 {
// Make the next field in the array become first responder if it's text is non-nil and it's text's character count is less than desired max
if fields[index+1].text != nil && fields[index+1].text!.characters.count < (max-1) {
fields[index+1].becomeFirstResponder()
} else {
return false
}
} else {
return false
}
}
}
}
return true
}
}
I find Pierce's answers too complex and anas.p's answer doesn't work for me like it's supposed to. My Swift 5 solution:
override func viewDidLoad() {
textField1.addTarget(self, action: #selector(textFieldDidChange1), for:.editingChanged)
textField2.addTarget(self, action: #selector(textFieldDidChange2), for:.editingChanged)
}
#objc func textFieldDidChange1() {
// After 2 characters are typed, immediately jump to textField2
if (textField1.text?.count == 2) {
self.textField2.becomeFirstResponder()
}
}
#objc func textFieldDidChange2() {
if (textField2.text?.count == 2) {
self.textField3.becomeFirstResponder()
}
}

Prevent special characters in UITextField

How can I prevent the user from entering special characters in UITextField?
I solved the problem using this code:
let validString = NSCharacterSet(charactersInString: " !##$%^&*()_+{}[]|\"<>,.~`/:;?-=\\¥'£•¢")
// restrict special char in test field
if (textField == self.txt_firstName || textField == self.txt_lastName)
{
if let range = string.rangeOfCharacterFromSet(validString)
{
print(range)
return false
}
else
{
}
}
Swift 4.2
for emoji and special character
override func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField.isFirstResponder {
let validString = CharacterSet(charactersIn: " !##$%^&*()_+{}[]|\"<>,.~`/:;?-=\\¥'£•¢")
if (textField.textInputMode?.primaryLanguage == "emoji") || textField.textInputMode?.primaryLanguage == nil {
return false
}
if let range = string.rangeOfCharacter(from: validString)
{
print(range)
return false
}
}
return true
}
One more answer with default CharacterSet
Restrict all Special characters and also, this will support if any string you dont want to restrict.
extension String {
var containsValidCharacter: Bool {
guard self != "" else { return true }
let noNeedToRestrict = CharacterSet(charactersIn: " _") // NOT RESTRICT "Underscore and Space"
if noNeedToRestrict.containsUnicodeScalars(of: self.last!) {
return true
} else {
return CharacterSet.alphanumerics.containsUnicodeScalars(of: self.last!)
}
}
}
extension CharacterSet {
func containsUnicodeScalars(of character: Character) -> Bool {
return character.unicodeScalars.allSatisfy(contains(_:))
}
}
Usage:
extension ViewController: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
return string.containsValidCharacter
}
}
Here's an example of how you can allow users to only type alphanumeric characters using RxSwift.
Add the RxSwift pod
pod 'RxSwift', '~> 5'
Create a String extension to remove characters in a set
extension String {
func removeChars(in set: CharacterSet) -> String {
let filtered = self.unicodeScalars.filter { (scalarElement) -> Bool in
if (set.contains(scalarElement)) {
return false
}
return true
}
let trimmed = String(filtered.map({ (scalar) -> Character in
return Character(scalar)
}))
return trimmed
}
}
Use the RxSwift extension to remove invalid chars when the text changes
import UIKit
import RxSwift
class MyViewController: UIViewController {
private let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
setUpView()
}
private func setUpView() {
//Do view set up stuff here
//Use the extension here to listen to text changes and remove chars
myTextField.rx.text
.asDriver(onErrorJustReturn: "")
.drive(onNext: { [weak self] (text: String?) in
//Here you would change the character set to what you need
let newText = text?.removeChars(in: CharacterSet.alphanumerics.inverted)
self?.myTextField.text = newText
})
.disposed(by: disposeBag)
}
}
try with this
self.DataTblView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: false)

shouldChangeCharactersInRange multiple text fields gets frozen

Updated code bellow with solution.
This works with as many fields you want.
It also fixes the textfield frozen issue when the method returns false.
The line bellow is what guides the method to return true after returns false.
newString = currentString.stringByReplacingCharactersInRange(range, withString: string)
On view did load (this will add an identifier to each field so you can identify within the method what field is being used - it is an Int())
emailAddressField.tag = 1
userPasswordField.tag = 2
On delegate method
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
var maxLength = Int()
var newString = NSString()
var currentString = NSString()
println("MY TAG \(textField.tag)")
switch textField.tag {
case 1:
println("CASE 1 \(range)")
maxLength = 16
currentString = emailAddressField.text
newString = currentString.stringByReplacingCharactersInRange(range, withString: string)
case 2:
println("CASE 2 \(range)")
maxLength = 8
currentString = userPasswordField.text
newString = currentString.stringByReplacingCharactersInRange(range, withString: string)
default:
println("Didn't detect any field")
}
return newString.length <= maxLength
}
The issue is that the delegate method shouldChangeCharactersInRange is used for both text fields and in your implementation you're returning false as soon as one of the text fields reaches its limit which ultimately makes both text fields deny further input. To resolve this you need to check the method's textField parameter to identify which text field the method is called for.
One of the possible ways to do this is to set up tags on your two text fields, for instance in viewDidLoad,
override func viewDidLoad() {
...
emailAddressField.tag = EMAIL_ADDRESS_TEXTFIELD // e.g. 0
userPasswordField.tag = USER_PASSWORD_TEXTFIELD // e.g. 1
}
and then act upon the tag of the text field supplied to the delegate method
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let newString = textField.text!.stringByReplacingCharactersInRange(range, withString: string)
if textField.tag == EMAIL_ADDRESS_TEXTFIELD && count(newString) + 1 == 30 {
return false
}
if textField.tag == USER_PASSWORD_TEXTFIELD && count(newString) + 1 == 11 {
return false
}
return true
}

Digit input in a text field

Additional question:
Still need some help with my code. The textfield is 'measuredValue' and I plan to have 30 different texfields (measuredValue1...30). When I type '923' the text will be set to '9.23' right away. Then I want to add '4'... for '92.34' but that doesn't work. Thanks for helping out.
func textField(textField: UITextField,
shouldChangeCharactersInRange range: NSRange,
replacementString string: String)
-> Bool {
if count(string) == 0 { return true }
var measuredValue = (textField.text as NSString).stringByReplacingCharactersInRange(range, withString: string)
switch textField {
case digitsOnlyTextField:
if count(measuredValue) == 3 || count(measuredValue) == 4 {
let stringNumeric = Double(measuredValue.toInt()!) / 100
measuredValue = String(format:"%.2f", stringNumeric)
digitsOnlyTextField.text = measuredValue
}
return measuredValue.containsOnlyCharactersIn("0123456789") && count(measuredValue) <= 4
default:
return true
}
}
Original question:
I would like to validate my text fields to get the right input for my app. Input needs to be formatted like '9.90' or '15.34'. So always 3 or 4 digits, and always 2 decimals.
I would like to use 'numberpad keyboard' (just 0...9, no point) and add the decimal point after the user exits the field. So the user input is 990 or 1534, and then in the text field it will become 9.90 or 15.34 automatically.
I tried searching for examples first, but didn't find what I was looking for.
Any help appreciated.
Jan
You have to implement the UITextField delegate method
func textFieldDidEndEditing(textField: UITextField) {
//Logic goes here....
var textString = textField.text as! String
let textLength = countElements(textString)
if textLength >= 3 && textLength <= 4 {
let stringNumeric = Double(textString.toInt()!) / 100
let texts = String(format:"%.2f", stringNumeric)
textField.text = texts
}
}
Your class should confirm to the protocol UITextFieldDelegate
this is the 'final' code I used (but I'm open for improvements!). A few remarks:
when a user clicks the background (and leaves the textfield) the
decimal point gets set.
when a user copies & pastes text from another app, this code handles that.
when a user goes back in the textfield for editing, the decimal point gets set again.
Deleting a value (to an empty textfield) is handled correctly.
So I'm pleased. Thanks for the help.
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var numberField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
numberField.delegate = self
numberField.keyboardType = UIKeyboardType.NumberPad
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// Tap background to add decimal point and defocus keyboard
#IBAction func userTappedBackground(sender: AnyObject) {
for view in self.view.subviews as! [UIView] {
if let textField = view as? UITextField {
if count(numberField.text) > 0 {
var numberString = numberField.text
numberString = numberString.stringByReplacingOccurrencesOfString(".", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
var numberFromString = Double(numberString.toInt()!) / 100
numberField.text = String(format:"%.2f", numberFromString)
}
textField.resignFirstResponder()
}
}
}
func textField(textField: UITextField,
shouldChangeCharactersInRange range: NSRange,
replacementString string: String)
-> Bool {
var result = true
var prospectiveText = (textField.text as NSString).stringByReplacingCharactersInRange(range, withString: string)
prospectiveText = prospectiveText.stringByReplacingOccurrencesOfString(".", withString: "", options: NSStringCompareOptions.LiteralSearch, range: nil)
if textField == numberField {
if count(string)>0 {
let disallowedCharacterSet = NSCharacterSet(charactersInString: "0123456789").invertedSet
let replacementStringIsLegal = string.rangeOfCharacterFromSet(disallowedCharacterSet) == nil
let resultingStringLengthIsLegal = count(prospectiveText) <= 4
let scanner = NSScanner(string: prospectiveText)
let resultingTextIsNumeric = scanner.scanDecimal(nil) && scanner.atEnd
result = replacementStringIsLegal && resultingStringLengthIsLegal && resultingTextIsNumeric
}
}
return result
}