Avoid many if conditions - ios

i have 6 strings (text,year,age,....) i want to filter array based on them ... i want to check before if the string is empty or not .. if not empty then added to the if condition as an && .. but how to do something like this without the need to write many if conditions on each string with each other string ?
i want to avoid doing something like this:
if self.searchtext != "" && self.qualification != ""{
}else if self.searchtext != "" && self.qualification != "" && self.gender != ""{
}else if self.searchtext != "" && self.qualification != "" && self.gender != "" && self.year != ""{
}else if self.searchtext != "" && self.qualification != "" && self.gender != "" && self.year != "" && self.major != ""{
}
....
How to do this?
if self.searchtext != ""{
if user.name.contains(find: self.searchtext) || user.mobile.contains(find: self.searchtext) || user.email.contains(find: self.searchtext){
DispatchQueue.main.async {
self.phones.append(user.mobile)
self.names.append(user.name)
}
}
}else if self.qualification != ""{
if user.qualification.contains(find: self.qualification){
DispatchQueue.main.async {
self.phones.append(user.mobile)
self.names.append(user.name)
}
}
}else if self.gender != ""{
if user.gender.contains(find: self.gender){
DispatchQueue.main.async {
self.phones.append(user.mobile)
self.names.append(user.name)
}
}
}else if self.year != ""{
if user.graduateYear.contains(find: self.year){
DispatchQueue.main.async {
self.phones.append(user.mobile)
self.names.append(user.name)
}
}
}else if self.major != ""{
if user.specialization.contains(find: self.major){
DispatchQueue.main.async {
self.phones.append(user.mobile)
self.names.append(user.name)
}
}
}else if self.city != ""{
if user.city.contains(find: self.city){
DispatchQueue.main.async {
self.phones.append(user.mobile)
self.names.append(user.name)
}
}
}
}
This is what i have working .. but this works as an OR when i want it to be like AND for all none empty strings

Here's a paragraph copied from LinkedIn's style guide
Using guard Statements
3.11.1 In general, we prefer to use an "early return" strategy where applicable as opposed to nesting code in if statements. Using guard statements for this use-case is often helpful and can improve the readability of the code.
// PREFERRED
func eatDoughnut(at index: Int) {
guard index >= 0 && index < doughnuts.count else {
// return early because the index is out of bounds
return
}
let doughnut = doughnuts[index]
eat(doughnut)
}
// NOT PREFERRED
func eatDoughnut(at index: Int) {
if index >= 0 && index < doughnuts.count {
let doughnut = doughnuts[index]
eat(doughnut)
}
}
It's what I personally do, and it's a good convention to avoid nested if's.
In your case, you can do a couple of things. First and foremost, use .isEmpty instead of comparing the string to an empty string (""). If your only intent is to check if all your strings are empty, you can accomplish that like this:
let strings = [searchtext, qualification, ...]
guard strings.filter({ $0.isEmpty }).count == 0 else {
return
}
// Code that only works if all fields have values

We can certainly simplify things a lot.
I would start by wrapping search criteria into an object:
struct UserSearchCriteria {
var searchText = ""
var gender = ""
var qualification = ""
...
init() {}
}
Then you can use one variable instead of your X variables:
var searchCriteria = UserSearchCriteria()
then you can add a simple matching method:
extension UserSearchCriteria {
func matchesUser(_ user: User) -> Bool {
if !searchText.isEmpty && [user.name, user.mobile, user.email].contains(where: { $0.contains(find: searchText) }) {
return true
}
if !qualification.isEmpty && user.qualification.contains(find: qualification) {
return true
}
...
return false
}
}
Then your big condition is reduced to:
if self.searchCriteria.matches(user) {
DispatchQueue.main.async {
self.phones.append(user.mobile)
self.names.append(user.name)
}
}
You cannot really avoid the conditions, but you can simplify them and organize them better, without duplicating code.
It seems you want to match all search conditions then I would change the matching method to:
func matchesUser(_ user: User) -> Bool {
if !searchText.isEmpty && ![user.name, user.mobile, user.email].contains(where: { $0.contains(find: searchText) }) {
return false
}
if !qualification.isEmpty && !user.qualification.contains(find: qualification) {
return false
}
...
return true
}
(note the double negation - contains condition negated and returning false).

You can use Guard statement to Early exit a scope .
A guard statement is used to transfer program control out of a scope
if one or more conditions aren’t met.
This will pass only if all fields are not empty.
guard self.searchtext != "", self.qualification != "", self.gender != "", self.year != "", self.major != "" else { return }
Update:
guard let searchText = self.searchtext, !searchText.isEmpty else { return }
// do something with `searchText`. Here the above conditions will be true.
OR you can use if-let
if let searchText = self.searchtext, !searchText.isEmpty {
// do with `searchText`(non-optional).
} else {
// conditions failed.
}
If you are trying to filter your array, you can simply use array.filter
let filteredUsers = usersArray.filter {
let shouldIAddThisElement = $0.name.contains(searchText) // Do your logic here
return shouldIAddThisElement
}

Swift includes guard statement. The lines after the guard statement will only be executed if the guard condition is true. Like an if/else statement, the else clause runs if the condition is false.
guard condition else {
// false: execute some code
}
// true: execute some code
For your case:
guard self.searchtext && self.qualification && self.gender && self.year && self.major else { return }
// execute some code
The return statement will exit the function or method.
More information can be found in the documentation on "Early Exit" Section:
https://docs.swift.org/swift-book/LanguageGuide/ControlFlow.html

Related

Textfield is even empty but not considering as empty in Swift

I have some multiple textfields, And If all the fields are filled then only I have to call some method else I have to throw alert.
But, even textfields are empty, It is executing condition as false.
if genderTextField.text?.isEmpty == true && weightTextField.text?.isEmpty == true && heightTextField.text?.isEmpty == true {
self.showAlert(withTitle:"Title", withMessage: "Fill all the fields")
} else {
//call some function
}
But, If I print textfields text
po genderTextField.text
▿ Optional<String>
- some : ""
Any suggestions?
Swift 5.2
A more elegant way to do it would be to create an extension on UITextField
extension UITextField {
var isEmpty: Bool {
if let text = self.text, !text.isEmpty {
return false
} else {
return true
}
}
}
And then you check like this:
if genderTextField.isEmpty || weightTextField.isEmpty || heightTextField.isEmpty {
showAlert()
} else {
// do something else
}

Properly configuring closure to capture results

I am writing some custom store methods for in app purchases; sort of a wrapper for SwiftyStore. The problem I'm running into is the inability to get the results from the closures before they exit.
Any suggestions on how to properly set them up? IE: Closures...
I have a function that checks for an existing subscription and returns true if it finds one in firebase, if it doesn't then it goes out to the apple store to verify a previously purchased subscription:
func checkSubscription() -> Bool {
var RetVal: Bool = false
var retStat: String = ""
var myVal: Bool = false
self.rootRef.child("users").child(self.userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let mySubType = value?["subtyp"] as? String ?? ""
// set value
if mySubType == "" {
// get receipt
if self.myStore.getReceipt() == true {
(myVal, retStat) = self.myStore.verifyPurchase(product: "com.xxxxx.xxxxx.monthly")
if myVal == true && retStat == "Valid" {
// we have a valid product update firebase
print("Valid")
} else if myVal == true && retStat == "Expired" {
// we have a valid product that is expired
print("Expired")
}
}
} else {
// we have a purchase, verify its not expired.
print("Purchased")
RetVal = true
}
}) { (error) in
print(error.localizedDescription)
}
return RetVal
}
The problem here is its dropping down to the return RetVal before the closure is complete so the function could be returning an invalid value. Not sure how I can fix this in the current setup, but any suggestions or pointers would be appreciated.
To expand on Tom's comment, if you want to return a result when the nested asynchronous function is complete, you could pass in a completion handler closure that uses the Result type that Swift offers like the following:
func checkSubscription(completion: #escaping (Result<Bool, Error>) -> Void) {
var RetVal: Bool = false
var retStat: String = ""
var myVal: Bool = false
self.rootRef.child("users").child(self.userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let mySubType = value?["subtyp"] as? String ?? ""
// set value
if mySubType == "" {
// get receipt
if self.myStore.getReceipt() == true {
(myVal, retStat) = self.myStore.verifyPurchase(product: "com.xxxxx.xxxxx.monthly")
if myVal == true && retStat == "Valid" {
// we have a valid product update firebase
print("Valid")
} else if myVal == true && retStat == "Expired" {
// we have a valid product that is expired
print("Expired")
}
}
} else {
// we have a purchase, verify its not expired.
print("Purchased")
RetVal = true
}
completion(.success(RetVal))
}) { (error) in
print(error.localizedDescription)
completion(.failure(error))
}
}
Calling the function using this type of completion handler would look something like this:
checkSubscription { (result) in
switch result {
case .success(let boolValue):
// do something with resulting boolean
break
case .failure(let error):
// do something with resulting error
break
}
}
func checkSubscription(completion: (_ scuess:Bool) ->()){
var RetVal: Bool = false
var retStat: String = ""
var myVal: Bool = false
self.rootRef.child("users").child(self.userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let mySubType = value?["subtyp"] as? String ?? ""
// set value
if mySubType == "" {
// get receipt
if self.myStore.getReceipt() == true {
(myVal, retStat) = self.myStore.verifyPurchase(product: "com.xxxxx.xxxxx.monthly")
if myVal == true && retStat == "Valid" {
// we have a valid product update firebase
print("Valid")
} else if myVal == true && retStat == "Expired" {
// we have a valid product that is expired
print("Expired")
}
}
completion(false)
} else {
// we have a purchase, verify its not expired.
print("Purchased")
completion(true)
}
}) { (error) in
print(error.localizedDescription)
completion(false)
}
return RetVal
}
call completion(true) whenever your retValue supposed to be true and completion(false) whenever your retValue supposed to be true
Then call this function this way:
checkSubscription { (sucuess) in
if(sucuess){
print("OK")
}else{
print("BAD")
}
}

Search Controller for words

I have followed below example code for search feature in my application
https://www.raywenderlich.com/157864/uisearchcontroller-tutorial-getting-started
Everything is working fine and searching is also working perfectly. but when I enter the words in different order/sequence it does not give me the results. below is the example of what I want
var String = "High, Fever"
var String = "fever"
Now when I search like "fever" it gives me the both in response but When I search something like "fever h" it does not give me the string.. in my case they just enter the words regardlessly their sequence..
Below is my code
func filterContentForSearchText(_ searchText: String, scope: String = "Present") {
filteredsymptoms = symptoms.filter({( symptoms : Symptoms) -> Bool in
let doesCategoryMatch = (scope == "Present") || (scope == "Absent") || (scope == "Focus")
if searchBarIsEmpty() {
return doesCategoryMatch
} else {
return doesCategoryMatch && symptoms.name.lowercased().contains(searchText.lowercased())
}
})
print(filteredsymptoms)
self.tblListOfSymptoms.reloadData()
}
symptoms.name.lowercased().contains(searchText.lowercased()
This part of code will check if "symptoms.name" contains whole search text, "fever h" in your case.
If you want to search all parts of text which separated by spaces, you should use something like this:
func filterContentForSearchText(_ searchText: String, scope: String = "Present") {
filteredsymptoms = symptoms.filter({( symptoms : Symptoms) -> Bool in
let doesCategoryMatch = (scope == "Present") || (scope == "Absent") || (scope == "Focus")
if searchBarIsEmpty() {
return doesCategoryMatch
} else {
var result:Bool = false
let searchTerms = searchText.lowercased().components(separatedBy: CharacterSet.whitespacesAndNewlines)
for searchTerm in searchTerms
{
result = doesCategoryMatch && symptoms.name.lowercased().contains(searchTerm)
if result == true
{
break
}
}
return result
}
})
print(filteredsymptoms)
self.tblListOfSymptoms.reloadData()
}
I guess it depends on the result that you want to achieve. If you want the user to type multiple words and search for any of those use following code:
searchText.lowercased().components(separatedBy: " ").map({ symptoms.name.lowercased().contains($0) }).reduce(false, { $0 || $1 })

Swift 2: guard in for loop?

what is the correct way to use guard inside a for loop?
for (index,user) in myUsersArray.enumerate() {
guard user.id != nil else {
print("no userId")
//neither break / return will keep running the for loop
}
if user.id == myUser.id {
//do stuff
}
}
There are a few ways to make some conditionals:
You can put a condition for whole for. It will be called for each iteration
for (index, user) in myUsersArray.enumerate() where check() {}
for (index, user) in myUsersArray.enumerate() where flag == true {}
You can check something inside for and skip an iteration or stop the loop:
for (index, user) in myUsersArray.enumerate() {
guard check() else { continue }
guard flag else { break }
}
In your case I will be write something like this:
for (index, user) in myUsersArray.enumerate() {
guard let userId = user.id, userId == myUser.id else { continue }
// do stuff with userId
}
#Arsens answer is correct but I think this is easier to understand
let ints = [1,2,3,4,5]
for (index,value) in ints.enumerate() {
guard value != 1 else {
print("Guarded \(value)")
continue
}
print("Processed \(value)")
}
for (index,user) in myUsersArray.enumerate() {
guard let userId = user.id else {
print("no userId")
continue;
}
if userId == myUser.id {
//do stuff
}
}

Using KSTokenView in Swift 2

I'm trying to implement KSTokenView in my Swift 2 project. I have fixed all of the small errors in the conversion, but I have three instances of the same error that I can't figure out how to fix. The issue is with the advance method and I am getting a compile time error that says 'advance is unavailable: call the advancedBy(n)' method on the index. I've tried to look at another answer involving this method but after struggling for a while I can't figure it out.
The problem code is:
First instance is in the method below, I will mark it with a comment
private func _updateText() {
if (!_setupCompleted) {return}
_initPlaceholderLabel()
switch(_state) {
case .Opened:
text = KSTextEmpty
break
case .Closed:
if tokens.count == 0 {
text = KSTextEmpty
} else {
var title = KSTextEmpty
for token: KSToken in tokens {
title += "\(token.title)\(_separatorText!)"
}
if (title.characters.count > 0) {
//advance call made in the statement below
title = title.substringWithRange(Range<String.Index>(start: advance(title.startIndex, 0), end: advance(title.endIndex, -_separatorText!.characters.count)))
}
let width = KSUtils.widthOfString(title, font: font!)
if width + _leftViewRect().width > bounds.width {
text = "\(tokens.count) \(_descriptionText)"
} else {
text = title
}
}
break
}
_updatePlaceHolderVisibility()
}
Second and third instances are in this function called textField:shouldChangeCharactersInRange in the if statement if(string.isEmpty). I will also mark the if statement and the two advance method calls.
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
// If backspace is pressed
if (_tokenField.tokens.count > 0 && _tokenField.text == KSTextEmpty && string.isEmpty == true && shouldDeleteTokenOnBackspace) {
if (_lastToken() != nil) {
if (selectedToken() != nil) {
deleteSelectedToken()
} else {
_tokenField.selectToken(_lastToken()!)
}
}
return false
}
// Prevent removing KSEmptyString
if (string.isEmpty == true && _tokenField.text == KSTextEmpty) {
return false
}
var searchString: String
let olderText = _tokenField.text
// Check if character is removed at some index
// Remove character at that index
if (string.isEmpty) { //advance calls are made in this if statement
let first: String = olderText!.substringToIndex(advance(olderText!.startIndex, range.location)) as String // advance called here (1/2)
let second: String = olderText!.substringFromIndex(advance(olderText!.startIndex, range.location+1)) as String // advance called here (2/2)
searchString = first + second
} else { // new character added
if (tokenizingCharacters.contains(string) && olderText != KSTextEmpty && olderText!.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet()) != "") {
addTokenWithTitle(olderText!, tokenObject: nil)
return false
}
searchString = olderText!+string
}
// Allow all other characters
if (searchString.characters.count >= minimumCharactersToSearch && searchString != "\n") {
_lastSearchString = searchString
startSearchWithString(_lastSearchString)
}
_tokenField.scrollViewScrollToEnd()
return true
}
Edit: Figured it out. Take the first parameter and call advancedBy(n) on it. Then put the second parameter in the 'n' slot.
Example: let second: String = olderText!.substringFromIndex(olderText!.startIndex.advancedBy(range.location+1)) as String

Resources