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 })
Related
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
I am new to iOS app developing.
I am implementing search bar programatically in swift 3 .
when I updated content for search controller I am getting this error.
value of type 'string' has no member 'containsString'
on this line :
return categoryMatch &&
candy.name.lowercaseString.containsString(searchText.lowercaseString)
this is code :
func updateSearchResultsForSearchController(searchController: UISearchController) {
func filterContentForSearchText(searchText: String, scope: String = "All") {
filteredCandies = candies.filter { candy in
let categoryMatch = (scope == "All") || (candy.category == scope)
return categoryMatch && candy.name.lowercaseString.containsString(searchText.lowercaseString)
}
tableView.reloadData()
}
}
If you're using Swift 3 then change this line
return categoryMatch && candy.name.lowercaseString.containsString(searchText.lowercaseString)
to
return categoryMatch && candy.name.lowercased().contains(searchText.lowercased())
Ive been following this example https://www.raywenderlich.com/113772/uisearchcontroller-tutorial
Ive incorporated sqlite to fill the tableview, but currently the search is using substrings with .contains.
func filterContentForSearchText(searchText: String, scope: String = "All") {
filteredFood = food.filter { candy in
return candy.name.lowercaseString.containsString(searchText.lowercaseString)
}
tableView.reloadData()
}
Ive looked up a few different ways, NSPredicates and Regex, but Im not quite sure how to incorporate them correctly, or if thats what I even need to do.
Ex.Cell is "Stackoverflow is so amazing!"
If i search for Stackoverflow, the search is fine, but if I search "so is" I get no results.
You are looking for a more customized search method, which you would have to develop yourself.
For the example you provided, this code searches for each individual word to match:
let searchTerms = searchText.componentsSeparatedByString(" ").filter { $0 != "" }
filteredFood = food.filter { candy in
for term in searchTerms{
if !candy.name.lowercaseString.containsString(term.lowercaseString){
return false
}
}
return true
}
You can use regex for this using NSRegularExpression. For a regex to do a keyword search, I think the best way to do it is to do it in the following way: (str1|str2|str3).
So, in swift you can create replace the spaces with '|' and then use regular expressions:
let rtext = searchText.stringByReplacingOccurrencesOfString(" ", withString: "|");
let regex = NSRegularExpression(pattern: "(\(rtext))", .CaseInsensitive);
filteredFood = food.filter { candy in
return regex.numberOfMatchesInString(candy.name, options: 0, range: NSRange(0, candy.name.characters.count) > 0;
}
(Note haven't tested the code)
You can search in more than one label for example you have a class that contains state and city. So if the city does not exist it shows the rest of the cities in the state.
Here is what I am taking about:
// Set up the search text
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
// Create and divid the string into substring such as "You are" to "You" and "are"
let searchTerm = searchText.components(separatedBy: " ").filter{ $0 != "" }
// Whatever is being filtered is assigned to rilteredArrayPropertys
filteredArrayPropertys = arrayPropertys.filter({ (state) -> Bool in
// Search each term or substring
for term in searchTerm{
// Check which substring is equal state.propertyState, and if it statsifies it will return and assign to state.propertyState.
// .range(of: ): is what is being typed into search bar. state.propertyState is what has been setted.
if (state.propertyState.lowercased().range(of: term.lowercased()) != nil) || (state.propertyCity.lowercased().range(of: term.lowercased()) != nil) {
return true
}
}
return false
})
if searchText != "" {
shouldShowSearchResults = true
self.tableView.reloadData()
}
else {
shouldShowSearchResults = false
self.tableView.reloadData()
}
}
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
How can we compare two strings in swift ignoring case ?
for eg :
var a = "Cash"
var b = "cash"
Is there any method that will return true if we compare var a & var b
Try this :
For older swift:
var a : String = "Cash"
var b : String = "cash"
if(a.caseInsensitiveCompare(b) == NSComparisonResult.OrderedSame){
println("Et voila")
}
Swift 3+
var a : String = "Cash"
var b : String = "cash"
if(a.caseInsensitiveCompare(b) == .orderedSame){
print("Et voila")
}
Use caseInsensitiveCompare method:
let a = "Cash"
let b = "cash"
let c = a.caseInsensitiveCompare(b) == .orderedSame
print(c) // "true"
ComparisonResult tells you which word comes earlier than the other in lexicographic order (i.e. which one comes closer to the front of a dictionary). .orderedSame means the strings would end up in the same spot in the dictionary
if a.lowercaseString == b.lowercaseString {
//Strings match
}
Try this:
var a = "Cash"
var b = "cash"
let result: NSComparisonResult = a.compare(b, options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil, locale: nil)
// You can also ignore last two parameters(thanks 0x7fffffff)
//let result: NSComparisonResult = a.compare(b, options: NSStringCompareOptions.CaseInsensitiveSearch)
result is type of NSComparisonResult enum:
enum NSComparisonResult : Int {
case OrderedAscending
case OrderedSame
case OrderedDescending
}
So you can use if statement:
if result == .OrderedSame {
println("equal")
} else {
println("not equal")
}
localizedCaseInsensitiveContains : Returns whether the receiver contains a given string by performing a case-insensitive, locale-aware search
if a.localizedCaseInsensitiveContains(b) {
//returns true if a contains b (case insensitive)
}
Edited:
caseInsensitiveCompare : Returns the result of invoking compare(_:options:) with NSCaseInsensitiveSearch as the only option.
if a.caseInsensitiveCompare(b) == .orderedSame {
//returns true if a equals b (case insensitive)
}
CORRECT WAY:
let a: String = "Cash"
let b: String = "cash"
if a.caseInsensitiveCompare(b) == .orderedSame {
//Strings match
}
Please note: ComparisonResult.orderedSame can also be written as .orderedSame in shorthand.
OTHER WAYS:
a.
if a.lowercased() == b.lowercased() {
//Strings match
}
b.
if a.uppercased() == b.uppercased() {
//Strings match
}
c.
if a.capitalized() == b.capitalized() {
//Strings match
}
Could just roll your own:
func equalIgnoringCase(a:String, b:String) -> Bool {
return a.lowercaseString == b.lowercaseString
}
For Swift 5
Ignoring the case and compare two string
var a = "cash"
var b = "Cash"
if(a.caseInsensitiveCompare(b) == .orderedSame){
print("Ok")
}
Phone numbers comparison example; using swift 4.2
var selectPhone = [String]()
if selectPhone.index(where: {$0.caseInsensitiveCompare(contactsList[indexPath.row].phone!) == .orderedSame}) != nil {
print("Same value")
} else {
print("Not the same")
}
You can just write your String Extension for comparison in just a few line of code
extension String {
func compare(_ with : String)->Bool{
return self.caseInsensitiveCompare(with) == .orderedSame
}
}
Swift 4, I went the String extension route using caseInsensitiveCompare() as a template (but allowing the operand to be an optional). Here's the playground I used to put it together (new to Swift so feedback more than welcome).
import UIKit
extension String {
func caseInsensitiveEquals<T>(_ otherString: T?) -> Bool where T : StringProtocol {
guard let otherString = otherString else {
return false
}
return self.caseInsensitiveCompare(otherString) == ComparisonResult.orderedSame
}
}
"string 1".caseInsensitiveEquals("string 2") // false
"thingy".caseInsensitiveEquals("thingy") // true
let nilString1: String? = nil
"woohoo".caseInsensitiveEquals(nilString1) // false
Swift 3: You can define your own operator, e.g. ~=.
infix operator ~=
func ~=(lhs: String, rhs: String) -> Bool {
return lhs.caseInsensitiveCompare(rhs) == .orderedSame
}
Which you then can try in a playground
let low = "hej"
let up = "Hej"
func test() {
if low ~= up {
print("same")
} else {
print("not same")
}
}
test() // prints 'same'
You could also make all the letters uppercase (or lowercase) and see if they are the same.
var a = “Cash”
var b = “CASh”
if a.uppercaseString == b.uppercaseString{
//DO SOMETHING
}
This will make both variables as ”CASH” and thus they are equal.
You could also make a String extension
extension String{
func equalsIgnoreCase(string:String) -> Bool{
return self.uppercaseString == string.uppercaseString
}
}
if "Something ELSE".equalsIgnoreCase("something Else"){
print("TRUE")
}
Swift 3
if a.lowercased() == b.lowercased() {
}
Swift 3:
You can also use the localized case insensitive comparison between two strings function and it returns Bool
var a = "cash"
var b = "Cash"
if a.localizedCaseInsensitiveContains(b) {
print("Identical")
} else {
print("Non Identical")
}
extension String
{
func equalIgnoreCase(_ compare:String) -> Bool
{
return self.uppercased() == compare.uppercased()
}
}
sample of use
print("lala".equalIgnoreCase("LALA"))
print("l4la".equalIgnoreCase("LALA"))
print("laLa".equalIgnoreCase("LALA"))
print("LALa".equalIgnoreCase("LALA"))