I was trying to filter my array of objects by one of its properties. tried lot of solutions but it was throwing error after I start typing
//My model class
class Book{
var bookId:Int?
var bookName:String?
//omitted init function
}
// viewController
//this is my textfield delegate method
let myArray:[Book] = [Book(bookId:23,bookName:"book1"),Book(bookId:53,bookName:"book2"),Book(bookId:43,bookName:"book3"),]
func textField(_ textField: UITextField, shouldChangeCharactersIn
range: NSRange, replacementString string: String) -> Bool
{
lastCharactar = string
textFieldText = myTextField.text! + lastCharactar
let predicate = NSPredicate(format: "ANY SELF.bookName BEGINSWITH %#", textFieldText)
let arr = ( myArray as NSArray).filtered(using: predicate)
return true
}
I am getting the following error
"this class is not key value coding-compliant for the key bookName."
Swift Array doesn't need predicate to filter its content. Swift array has filter method to filter array. Eg:
struct Book{
var bookId:Int?
var bookName:String?
}
let myArray:[Book] = [Book(bookId:23,bookName:"book1"),Book(bookId:53,bookName:"book2"),Book(bookId:43,bookName:"book3"),]
func textField(_ textField: UITextField, shouldChangeCharactersIn
range: NSRange, replacementString string: String) -> Bool
{
lastCharactar = string
textFieldText = myTextField.text! + lastCharactar
let arr = myArray.filter { (book) -> Bool in
if let name = book.bookName, name.hasPrefix(textFieldText) {
return true
}
return false
}
return true
}
Note: A struct is a value type whose value is copied when it is assigned to a variable or constant, or when it is passed to a function whereas class is reference type whose values will not get copied by default.
The bridge cast to NSArray to apply the predicate requires key-value compliance of the properties by adding the #objc attribute and the class must be even a subclass of NSObject.
But this is Swift, there's no need to use NSPredicate and NSArray. This native syntax does the same
let arr = myArray.filter{ $0.bookName.range(of: textFieldText, options: .anchored) != nil }
Side note:
Apparently all books have name and id so declare the object as struct with non-optional constant members and remove the redundant naming. The initializer is for free.
struct Book {
let id : Int
let name : String
}
var myArray = [Book(bookName:"book1", bookId:23),Book(bookName:"book2", bookId:53),Book(bookName:"book3", bookId:43)]
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
var lastCharactar = string
var textFieldText = myTextField.text! + lastCharactar
let arr = myArray.filter{ $0.bookName!.range(of: textFieldText, options: .caseInsensitive) != nil }
print(arr)
return true
}
struct Book {
var bookName : String?
var bookId : Int?
}
Related
This question already has answers here:
Swift startsWith method?
(7 answers)
Closed 2 years ago.
My data array is ["Apple", "Pear", "Banana", "Orange"].
If I search Apple, my filteredArray returns "Apple".
However, if I search "apple", it returns nothing.
I tried using the localizedCaseInsensitiveContains method posted as an answer in a similar question but it didn't seem to work for me. Any help is appreciated.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
filteredArray = dataArray.filter({$0.prefix((textField.text?.count)!) == textField.text!})
tableViewSearch.reloadData()
return true
}
I tested this on a playground:
let searchText = "apple"
let dataArray = ["Apple", "Pear", "Banana", "Orange"]
let filteredArray = dataArray.filter{ $0.lowercased().hasPrefix(searchText.lowercased()) }
print(filteredArray)
You have to use hasPrefix to achieve what you want, and in order for the query to be case insensitive you can lowercase both your array and the query string.
If you lowercase both the textField value and the values you are testing, the comparison will ignore capitalization:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let testString = textfield.text?.lowercased() else { return true }
filteredArray = dataArray.filter { $0.lowercased() == testString }
return true
}
you could also filter for qualifying results-- things that contain what you are typing:
filteredArray = dataArray.filter { $0.lowercased().contains(testString) }
or things that begin with what you are typing:
filteredArray = dataArray.filter { $0.lowercased().starts(with: testString) }
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.
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
I will get right to the question.
var a = 0
var b = 20
I want a user to input number into TextField and I could save that number into variable A. Then I want to do an if statement where
if a == b {
//code
}
What I am having trouble is getting that number input from the textfield.
You can check the input in the textfield for get the number
Swift4
let text = textField.text ?? ""
guard let number = Int(text) else {
print("Must be input the number")
return
}
// Do your task with number
a = number
Try this class Functions
class SourceVC: UIViewController
{
#IBOutlet weak var sampleTF: UITextField!{
didSet{
sampleTF.delegate = self
}
}
/// TF value
var a : Int = 0
var b : Int = 20
override func viewDidLoad() {
super.viewDidLoad()
}
/// Assuming this button action to get Value from TF
#IBAction func naviagteToDestination(_ sender: Any)
{
a = Int((sampleTF.text?.trimmingCharacters(in: .whitespaces))!)!
if a == b {
print("Execute")
}
else{
print("Dont Execute")
}
}
}
extension SourceVC : UITextFieldDelegate
{
/// This will Limit TF to accept only Numbers
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
{
let allowedCharacters = CharacterSet.decimalDigits
let characterSet = CharacterSet(charactersIn: string)
return allowedCharacters.isSuperset(of: characterSet)
}
}
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
}