why is shouldChangeCharactersInRange delegate method never called? - ios

I have followed this tutorial on how to restrict textField to a certain length and character set.
Here is my code, and yet the delegate isn't called:
VC class: this is called (verified while debugging)
var textManager = TextManager()
#IBOutlet weak var enterName_text: UITextField!
func onVideDidLoad() {
...
enterName_text.placeholder = StringConstans.name
enterName_text.delegate = textManager
}
TextManager class:
import Foundation
public class TextManager: NSObject, UITextFieldDelegate {
public func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if string.characters.count == 0 {
return true
}
let currentText = textField.text ?? ""
let prospectiveText = (currentText as NSString).stringByReplacingCharactersInRange(range, withString: string)
return prospectiveText.containsOnlyCharactersIn("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") &&
prospectiveText.characters.count <= 6
}
}

The code below works. How does yours differ?
class ViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
let textManager = TextManager()
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = textManager
}
}
public class TextManager: NSObject, UITextFieldDelegate {
public func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
print("should")
if string.characters.count == 0 {
return true
}
let currentText = textField.text ?? ""
let prospectiveText = (currentText as NSString).stringByReplacingCharactersInRange(range, withString: string)
return prospectiveText.containsOnlyCharactersIn("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") &&
prospectiveText.characters.count <= 6
}
}
extension String {
func containsOnlyCharactersIn(matchCharacters: String) -> Bool {
let disallowedCharacterSet = NSCharacterSet(charactersInString: matchCharacters).invertedSet
return self.rangeOfCharacterFromSet(disallowedCharacterSet) == nil
}
}

Related

Filter not working for searchbar UITextField in tableView Swift

I have implemented searchbar(UITextField) for tableView and defined same array of SectionList but exactly no idea filtering is not working. when I try to print IndexData it has data but tableFilterData is empty.
This is the main line which shows empty.
tableFilterdata = IndexData.filter({$0.names.contains(searchText) })
var IndexData = [SectionList]()`
var tableFilterdata = [SectionList]()`
var isSearch : Bool! = false`
struct SectionList {
let letter : String
let names : [String]
}
Main Functions
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
searchTxt.resignFirstResponder()
return true
}
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool{
let searchText = searchTxt.text! + string
print(IndexData)
tableFilterdata = IndexData.filter({$0.names.contains(searchText) })
print(tableFilterdata)
if(tableFilterdata.count == 0){
isSearch = false
}else{
isSearch = true
}
tableView.reloadData()
return true
}
Replace,
let searchText = searchTxt.text! + string
print(IndexData)
tableFilterdata = IndexData.filter({$0.names.contains(searchText.lowercased()) })
with
if string.isEmpty {
searchText = String(searchText.dropLast())
}
else {
searchText = textField.text!+string
}
print(IndexData)
self.IndexData.removeAll()
self.tableFilterdata.removeAll()
if searchText.count == 0 {
self.tableFilterdata = self.IndexData
} else {
tableFilterdata = IndexData.filter{ ($0.names.contains(searchText)) }
}

How to restrict textfield to accept only decimal values in swift

I want to accept only decimal values in my textfield.
The following code allows me to enter only numbers and '.' but we can enter more than one '.'
How can i restrict it to just one '.' or how can I restrict the textfield to accept only decimal values in swift 3.1
let aSet = NSCharacterSet(charactersIn:"0123456789.").inverted
let compSepByCharInSet = r_Qty_txt.text?.components(separatedBy: aSet)
let numberFiltered = compSepByCharInSet?.joined(separator: "")
My target device is iPad.
listViewCell.swift Code
import UIKit
class listViewCell: UITableViewCell, UITextFieldDelegate {
var delegate: CellInfoDelegate?
#IBOutlet weak var desc_lbl: UILabel!
#IBOutlet weak var openQty_lbl: UILabel!
#IBOutlet weak var r_Qty_txt: UITextField!
#IBOutlet weak var itemId_lbl: UILabel!
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let newString: String = (textField.text! as NSString).replacingCharacters(in: range, with: string)
let expression: String = "^[0-9]*((\\.|,)[0-9]{0,2})?$"
//var error: Error? = nil
let regex = try? NSRegularExpression(pattern: expression, options: .caseInsensitive)
let numberOfMatches: Int = (regex?.numberOfMatches(in: newString, options: [], range: NSRange(location: 0, length: (newString.characters.count ))))!
return numberOfMatches != 0
}
public func configure(textVal: String?, placeholder: String){
r_Qty_txt.text = textVal
r_Qty_txt.placeholder = placeholder
r_Qty_txt.accessibilityValue = textVal
r_Qty_txt.accessibilityLabel = placeholder
}
#IBAction func QtyEntered(_ sender: UITextField) {
print("Value Added \(String(describing: r_Qty_txt.text)) and \(String(describing: openQty_lbl.text))")
if (r_Qty_txt.text!.isEmpty) {
}else if(Int(r_Qty_txt.text!)! > Int(openQty_lbl.text!)!){
print("Not Allowed")
r_Qty_txt.text = nil
}
}
override func awakeFromNib() {
r_Qty_txt.delegate = self
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
If you want to allow just decimal number with your textField you can simply make it like this way no need to compare anything else.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField.text != "" || string != "" {
let res = (textField.text ?? "") + string
return Double(res) != nil
}
return true
}
select keyboard type just like this from atribute inspector for only numbers.
and use delegate for only one decimal points(.)
func textField(textField: UITextField,shouldChangeCharactersInRange range: NSRange,replacementString string: String) -> Bool
{
let countdots = textField.text.componentsSeparatedByString(".").count - 1
if countdots > 0 && string == "."
{
return false
}
return true
}

Swift: How to use two func textfield at same time

I'm using Xcode 8 and Swift 3.
I have a project with 3 textfields, 1 button to clear and label to display result.
Inside my class ViewController I have:
class ViewController: UIViewController,UITextFieldDelegate {
#IBOutlet weak var input1: UITextField!
#IBOutlet weak var input2: UITextField!
#IBOutlet weak var input3: UITextField!
#IBOutlet weak var lblResult: UILabel!
#IBOutlet weak var clearButton: UIButton!
I want to limit my textfields inputs to max 3 digits but also to a value of 360. I manage to get code for both things and they work if used only one at a time but because they both start with func textfield I can't make them both work together. Do I have to do it in different class?
I know this is a basic question but its part of the learning process.
These are the two codes I want to combine:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool
{
let maxLength = 3
let currentString: NSString = textField.text! as NSString
let newString: NSString = currentString.replacingCharacters(in: range, with: string) as NSString
return newString.length <= maxLength
}
and:
func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool
{
var startString = ""
if (textField.text != nil)
{
startString += textField.text!
}
startString += string
let limitNumber = Int(startString)
if limitNumber! > 360
{
return false
}
else
{
return true;
}
}
They are both inside the class ViewController.
Thanks for the help!
If I am not mistaken, here is all you need:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
let maxLength = 3
let limitValue = 360
let text = textField.text!
let currentString: NSString = text as NSString
let newString: NSString = currentString.replacingCharacters(in: range, with: string) as NSString
   var startString = ""
if !text.isEmpty {
startString += text
}
startString += string
let limitNumber = Int(startString)!
return limitNumber < limitValue && newString.length <= maxLength
}
Update:
Auto focus on a next texfield.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
let maxLength = 3
let limitValue = 360
let text = textField.text!
let currentString: NSString = text as NSString
let newString: NSString = currentString.replacingCharacters(in: range, with: string) as NSString
   var startString = ""
if !text.isEmpty {
startString += text
}
startString += string
let limitNumber = Int(startString)!
let newLength: Int = newString.length
if textField == input1 {
if newLength == maxLength {
input2.becomeFirstResponder()
}
}
if textField == input2 {
if newLength == maxLength {
input3.becomeFirstResponder()
}
}
if textField == input3 {
if newLength == maxLength {
self.view.endEditing(true)
}
}
return limitNumber < limitValue && newLength <= maxLength
}
You need to add an if statement in func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool checking for the current textFieldlike this:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textFiled == input1 {
// do logic for input1
} else if textFiled == input2 {
// do logic for input2
}
}
Swift switch statement will do it.
func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool
{
switch textField {
case input1:
// ...
case input2:
// ...
case input3:
// ...
default:
break
}
}
People didn't understood the question. Actually, all you need is merging your statement as mentioned by javimuu.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool
{
let maxLength = 3
let currentString: NSString = textField.text! as NSString
let newString: NSString = currentString.replacingCharacters(in: range, with: string) as NSString
var startString = ""
if (textField.text != nil)
{
startString += textField.text!
}
startString += string
let limitNumber = Int(startString)
return newString.length <= maxLength && limitNumber! <= 360
}

Swift. Format input in textField

I created textField and want to format text in it like 43/35. Number / number - for credit card month and year.
Can I use number fomatter for it or how can I do it more easily?
The issue here that I need to replace 3rd character if I add new character and remove it if I remove 2nd one.
I do not want use any 3rd party library, I need native implementation
This is my current solution. Basically you need to:
1) Implement the delegate of your textfield somewhere (in my code below I implemented on the ViewController)
2) Implement textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool.
To apply the mask, I created some extensions for String and Characters, as you can see at the end of the following code:
class ViewController: UIViewController {
#IBOutlet weak var textfield: UITextField!
let mask = "##/##"
override func viewDidLoad() {
super.viewDidLoad()
textfield.delegate = self
}
}
extension ViewController: UITextFieldDelegate {
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
guard let normalText = textField.text else { return false }
let beginning = textField.beginningOfDocument
// save cursor location
let cursorLocation = textField.positionFromPosition(beginning, offset: range.location + string.characters.count)
let newString = (normalText as NSString).stringByReplacingCharactersInRange(range, withString: string)
let newStringClean = newString.stringWithOnlyNumbers().withMask(mask)
guard newString != newStringClean else { return true }
textField.text = newStringClean
guard string != "" else { return false }
// fix cursor location after changing textfield.text
if let cL = cursorLocation {
let textRange = textField.textRangeFromPosition(cL, toPosition: cL)
textField.selectedTextRange = textRange
}
return false
}
}
extension String {
func stringWithOnlyNumbers() -> String {
return self.characters.reduce("") { (acc, c) -> String in
guard c.isDigit() else { return acc }
return "\(acc)\(c)"
}
}
func withMask(mask: String) -> String {
var resultString = String()
let chars = self.characters
let maskChars = mask.characters
var stringIndex = chars.startIndex
var maskIndex = mask.startIndex
while stringIndex < chars.endIndex && maskIndex < maskChars.endIndex {
if (maskChars[maskIndex] == "#") {
resultString.append(chars[stringIndex])
stringIndex = stringIndex.successor()
} else {
resultString.append(maskChars[maskIndex])
}
maskIndex = maskIndex.successor()
}
return resultString
}
}
extension Character {
func isDigit() -> Bool {
let s = String(self).unicodeScalars
let uni = s[s.startIndex]
let digits = NSCharacterSet.decimalDigitCharacterSet()
let isADigit = digits.longCharacterIsMember(uni.value)
return isADigit
}
}
Swift 4:
extension CodeEnterViewController: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let normalText = textField.text else { return false }
let beginning = textField.beginningOfDocument
// save cursor location
let cursorLocation = textField.position(from: beginning, offset: range.location + string.count)
let newString = (normalText as NSString).replacingCharacters(in: range, with: string)
let newStringClean = newString.stringWithOnlyNumbers().withMask(mask: mask)
guard newString != newStringClean else { return true }
textField.text = newStringClean
guard string != "" else { return false }
// fix cursor location after changing textfield.text
if let cL = cursorLocation {
let textRange = textField.textRange(from: cL, to: cL)
textField.selectedTextRange = textRange
}
return false
}
}
String
extension String {
func stringWithOnlyNumbers() -> String {
return self.reduce("") { (acc, c) -> String in
guard c.isDigit() else { return acc }
return "\(acc)\(c)"
}
}
func withMask(mask: String) -> String {
var resultString = String()
let chars = self
let maskChars = mask
var stringIndex = chars.startIndex
var maskIndex = mask.startIndex
while stringIndex < chars.endIndex && maskIndex < maskChars.endIndex {
if (maskChars[maskIndex] == "#") {
resultString.append(chars[stringIndex])
stringIndex = chars.index(after: stringIndex)
} else {
resultString.append(maskChars[maskIndex])
}
maskIndex = chars.index(after: maskIndex)
}
return resultString
}
}
Character
extension Character {
func isDigit() -> Bool {
let s = String(self).unicodeScalars
let uni = s[s.startIndex]
let digits = NSCharacterSet.decimalDigits
let isADigit = digits.hasMember(inPlane: UInt8(uni.value))
return isADigit
} }

How do I disable the "Space" Key on the keyboard in Swift?

I'm creating a login system and I don't want spaces to be allowed in the username field. Instead of check and validating the field, I want to prevent spaces from being added.
So, every time the user presses the spacebar, nothing should happen. How can I do that?
I've seen Instagram do this.
This is my code so far:
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var signUp: UIBarButtonItem!
#IBOutlet weak var navBar: UINavigationBar!
#IBOutlet weak var UsernameText: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.clipsToBounds = true
UsernameText.attributedPlaceholder = NSAttributedString(string:"TypeYourUsername",
attributes:[NSForegroundColorAttributeName: UIColor.whiteColor()])
func textField(UsernameText: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if (string == " ") {
return false
}
return true
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(animated: Bool) {
UsernameText.becomeFirstResponder()
}
override func prefersStatusBarHidden() -> Bool {
return true
}
}
Yes you can do it. Override UITextField delegate method shouldChangeCharactersInRange. And in that method check for space character. If found than return false.
Note: Don't forget to set delegate for textfield.
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if (string == " ") {
return false
}
return true
}
Edit: More Code
class ViewController : UIViewController, UITextFieldDelegate {
#IBOutlet weak var textField : UITextField!
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if (string == " ") {
return false
}
return true
}
}
Here textField object is an IBOutlet. It means that text field control is in storyboard and connected with this variable.
Swift 4.1/4.2/5.0/5.1/5.2
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if string == " " {
return false
}
return true
}
In Swift 2.0
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let whitespaceSet = NSCharacterSet.whitespaceCharacterSet()
let range = string.rangeOfCharacterFromSet(whitespaceSet)
if let _ = range {
return false
}
else {
return true
}
}
In Swift 2.1
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let whitespaceSet = NSCharacterSet.whitespaceCharacterSet()
if let _ = string.rangeOfCharacterFromSet(whitespaceSet) {
return false
}
return true
}
Swift 3
class SignUpViewController: UIViewController, UITextFieldDelegate {
lazy var emailTextField: UITextField = {
let tf = UITextField()
tf.translatesAutoresizingMaskIntoConstraints = false
tf.placeholder = "Email address"
tf.delegate = self
return tf
}()
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let whitespaceSet = NSCharacterSet.whitespaces
if let _ = string.rangeOfCharacter(from: whitespaceSet) {
return false
}
else {
return true
}
}
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let whitespaceSet = CharacterSet.whitespaces
if let _ = string.rangeOfCharacter(from: whitespaceSet) {
return false
} else {
return true
}
}
Swift 5
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
return string != " "
}
This is the most easy to understand syntax for me.
Swift 4.1
Just simple with Single line of code you can stop whitespace
For All Textfield
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
textField.text = textField.text?.replacingOccurrences(of: " ", with: "")
return true
}
For Single Textfield
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if textField == txtid
{
textField.text = textField.text?.replacingOccurrences(of: " ", with: "")
}
return true
}
Here you can disable WhiteSpaces until Your (First character )text enter. (some improvement )
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let whitespaceSet = NSCharacterSet.whitespaces
let range = string.rangeOfCharacter(from: whitespaceSet)
if let _ = range {
let text = textField.text ?? ""
if text.isBlankByTrimming || text.isEmpty {
return false
}else {
return true
}
}
else {
return true
}
}
Here u can use this Extension to trim white spaces .(even when u are checking your validations )
using above textfield Delegate you can disable whitespaces but if have string like this
Example : "MyName Surname "
so can trim end spaces also before saving this data.
extension String {
var isBlankByTrimming: Bool {
let trimmed = self.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
return trimmed.isEmpty
}
func trimWhitespacesAndNewlines() -> String{
return self.trimmingCharacters(in:
CharacterSet.whitespacesAndNewlines)
}
}
for use ::
let yourString = "MyName Surname "
let finalString = yourString.trimWhitespacesAndNewlines()
You can check if the User tapped on space key and neglect it with the following logic. But I think you cannot disable the button itself.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
string != " "
}

Resources