serach bar not find all words

I have a problem with the search, it doesn't search for each letter individually, these only if it is word is together, for example: I search Kate it will find but if I type Kae (that still has the letters in it), he won't find it, why?

   filteredData = contacts.filter {name in
        return  name.firstName.lowercased().contains(searchText.lowercased()) || name.lastName.lowercased().contains(searchText.lowercased()) || name.telephone.removeCharacters(from: CharacterSet.decimalDigits.inverted).lowercased().contains(searchText.lowercased())
      }

i attach screenshot

That's the intended behaviour.

When you test substring, you test if Kae is as such, inside name.

It is not (Kae letters are all in Kate is not a substring of Kate).

So, what is your spec ?

  • get letters all present ? Then ake would match Kae, which you probably don't want
  • find letters in this order ? Then Akyabe would match Kae. Is it what you want ?
  • Would Kaa match ?

You could use this:

func isMatching(sub: String, inString: String) -> Bool {
    
    let sub = sub.lowercased()
    var inString = inString.lowercased()
    
    // Let us find the positions of each character of sub in inString
    var positions = [Int]()
    for c in sub {
        if let pos = inString.firstRange(of: String(c)) {
            let startPos = inString.distance(from: inString.startIndex, to: pos.lowerBound)
            positions.append(startPos)
            inString.removeSubrange(pos)    // Now that we have found c, let us remove it
        } else {
            return false
        }
    }
    
    // If 0 or 1 character in sub, it is in correct order
    if positions.count < 2 { return true }
    
    // Now that we have found all letters, check if they are in correct order
    for index in 0...positions.count-2 {
        if positions[index] > positions[index+1] { return false }
    }
    
    // All OK
    return true
}

Some tests:

var result = isMatching(sub: "Kae", inString: "Kate")
print("Kae", result)
 result = isMatching(sub: "Kate", inString: "Kate")
print("Kate", result)
 result = isMatching(sub: "Katy", inString: "Kate")
print("Katy", result)
result = isMatching(sub: "Kaa", inString: "Kate")
print("Kaa in Kate", result)
result = isMatching(sub: "Kaa", inString: "Katea")
print("Kaa in Katea", result)
result = isMatching(sub: "ake", inString: "Kate")
print("ake", result)
result = isMatching(sub: "kae", inString: "Akyabe")
print("kae in Akyabe", result)

Which results in:

Kae true
Kate true
Katy false
Kaa in Kate false
Kaa in Katea false
ake false
kae in Akyabe true

And replace the test

name.firstName.lowercased().contains(searchText.lowercased())

by

isMatching(sub: searchText, inString: name.firstName)

Code has to be slightly changed, so that

Kaa in Katea 

returns true

func isMatching(sub: String, inString: String) -> Bool {
    
    let sub = sub.lowercased()
    var inString = inString.lowercased()
    var offset = 0 // Keep the position of last found char
    
    // Let us find the positions of each character of sub in inString
    var positions = [Int]()

    for c in sub {
        if let pos = inString.firstRange(of: String(c)) {
            let startPos = inString.distance(from: inString.startIndex, to: pos.lowerBound)
            positions.append(startPos + offset)
            inString.removeFirst(startPos+1) //  Now that we have found c, let us remove characters before, including itself
            offset = startPos
        } else {
            return false
        }
    }

    // If 0 or 1 character in sub, it is in correct order
    if positions.count < 2 { return true }
    
    // Now that we have found all letters, check if they are in correct order
    for index in 0...positions.count-2 {
        if positions[index] > positions[index+1] { return false }
    }
    
    // All OK
    return true
}

Thank you very much, but

inString.firstRange

is available from ios 16

You did not answer the question about what is your "spec".

.

firstRange is available from ios 16

For sure. It is very similar, you could even have found yourself:

replace

        if let pos = inString.firstRange(of: String(c)) {
            let startPos = inString.distance(from: inString.startIndex, to: pos.lowerBound)

by

        if let pos = inString.firstIndex(of: c) {
            let startPos = inString.distance(from: inString.startIndex, to: pos)

Here a more compact version, using recursivity:

func isMatching(sub: String, inString: String) -> Bool {

    if sub.isEmpty { return true }  // It's over

    var newSub: String
    var newInString: String
    
    let c = sub.first! // As it is not empty

  // We loop over the first char of sub check if OK, then call recursively after trimming sub and inString
    if let pos = inString.firstIndex(of: c) {
        let startPos = inString.distance(from: inString.startIndex, to: pos)
        newInString = inString
        newInString.removeFirst(startPos+1) //  Now that we have found c, let us remove characters before
        newSub = sub
        newSub.removeFirst()
        return findSub(sub: newSub, inString: newInString)
    } else {
        return false      // first char not found, so we failed ; that stops recursion
    }

}

If you do not want to consider case, recursive version should of course use lowercase:

func isMatching(sub: String, inString: String) -> Bool {

    if sub.isEmpty { return true }  // It's over
    
    let sub = sub.lowercased()
    let inString = inString.lowercased()
    
    let c = sub.first! // As it is not empty
    
    // We loop over the first char of sub check if OK, then call recursively after trimming sub and inString
    if let pos = inString.firstIndex(of: c) {
        let startPos = inString.distance(from: inString.startIndex, to: pos)

        var newInString = inString
        newInString.removeFirst(startPos+1) //  Now that we have found c, let us remove characters before

        var newSub = sub
        newSub.removeFirst()

        return findSub(sub: newSub, inString: newInString)
    } else {
        return false    // first char not found, so we failed ; that stops recursion
    }
    
}
serach bar not find all words
 
 
Q