4

I have array of structs and I don't know really how to use search by one of the struct's parameter.

My struct looks like:

struct Actor {
   var name: String!
   var posterURL: String!

    init(_ dictionary: [String: Any]) {
       name = dictionary["name"] as! String
       posterURL = dictionary["image"] as! String
    }
}

So, I've tried to use predicate

let actorSearchPredicate = NSPredicate(format: "name contains[c] %@", text)
filterredActors = (actors as NSArray).filtered(using: actorSearchPredicate)

But, when I'm trying to type anything, it crashes with error

[<_SwiftValue 0x10657ffb0> valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.'

What is the right way of using predicate search with structs? Thank you

10
  • CHeck stackoverflow.com/questions/44762460/… Commented Jan 12, 2018 at 20:08
  • 6
    Why bother with NSPredicate? actors.filtered{ $0.name.contains(text) } Commented Jan 12, 2018 at 20:09
  • Only thing missing with $0.name.contains(text) is making it case insensitive. Commented Jan 12, 2018 at 20:19
  • 1
    One really easy way of making it case insensitive is $0.name.lowercased().contains(text.lowercased()). Commented Jan 12, 2018 at 20:23
  • 1
    Why are you casting as AnyObject? You shouldn't be using that at all in Swift 4. Try actors.filtered{( $0.name.lowercased().contains(text.lowercased()) )}. Also, there is a difference between filter and filtered. Make sure you're using filtered. Commented Jan 12, 2018 at 20:40

2 Answers 2

3

NSPredicate is not needed, Swift got convenience methods to check case insensitive

whether the receiver contains a given string by performing a case insensitive, locale-aware search.

filterredActors = actors.filter { $0.name.localizedCaseInsensitiveContains(text) }

and case and diacritic insensitive

whether the receiver contains a given string by performing a case and diacritic insensitive, locale-aware search.

filterredActors = actors.filter { $0.name.localizedStandardContains(text) }
Sign up to request clarification or add additional context in comments.

Comments

2

If you need to use a predicate to filter structs, you can do it without going into @objc territory or converting your data into NSArray:

func filter(actors: [Actor], named: String) -> [Actor] {
    let predicate = NSPredicate(format: "SELF contains[cd] %@", named)
    return actors.filter {
        predicate.evaluate(with: $0.name)
    }
}

Example:

struct Actor {
    let name: String
    let imdb: String
}

let actors = [
    Actor(name: "John Travolta", imdb: "http://www.imdb.com/name/nm0000237"),
    Actor(name: "Uma Thurman", imdb: "http://www.imdb.com/name/nm0000235"),
    Actor(name: "Samuel L. Jackson", imdb: "http://www.imdb.com/name/nm0000 168"),
    Actor(name: "Penélope Cruz", imdb: "http://www.imdb.com/name/nm0004851"),
    Actor(name: "Penelope Ann Miller", imdb: "http://www.imdb.com/name/nm0000542")
]

print(filter(actors: actors, named: "Uma").map { $0.name })
// ["Uma Thurman"]
print(filter(actors: actors, named: "J").map { $0.name })
// ["John Travolta", "Samuel L. Jackson"]

Filtering using a predicate is useful, for example, to take into account diacritics. Let's search an actor name with an accented character:

print(filter(actors: actors, named: "Penelope").map { $0.name })
// ["Penélope Cruz", "Penelope Ann Miller"]

It finds both "Penelope", with and without accented "e".

Using String.contains won't work in that case:

let text = "Penelope"
let hits = actors.filter { ( $0.name.lowercased().contains(text.lowercased()) )}
print(hits.map { $0.name })
// ["Penelope Ann Miller"]

There are better alternatives (String localized search, regular expressions, String.range(of:options:range:locale), ...). Using predicates is not faster (not much slower either). It may be easier to reason about complex queries using predicate syntax if you already know it.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.