0

I’m building search functionality. I have an array of User objects and each User has a dictionary of tags. I’m trying to return users with searched tags.

My user class is:

class User: NSObject { 
   var name: String? 
   var tags = Dictionary<String, String>() 
}

An example of the tags is:

tags: { 
    “entry1” : “test1”, 
    “entry2” : “test2”, 
    “entry3” : “test3” 
}

I’ve been trying variances of:

let predicate = NSPredicate(format: “tags contains[c] %@", “test1”); 
 let filteredArray = self.usersArray!.filter { predicate.evaluate(with: $0) };  print(“users = ,\(filteredArray)");

It’s throwing “this class is not key value coding-compliant for the key tags.’ I'm not sure how to search inside the User object.

Update: I've change the dictionary to an array and filter { $0.tags.contains(searchTerm) } works for exact search match, as suggested.

.filter( {$0.tags.reduce(false, {$1.contains(searchTerm)} )} ) does not work for a partial match however, which is what i'm looking for.

Here is what is printed:

Search term: ale
Result: []
Tags array inside each object:
["Alex", "Will", ""]
["Bob", "Dan", ""]
["First", "Second", "Third"]
["Testing 1", "Testing 2", ""]

2 Answers2

1

Your implementation of tags seems cumbersome. Here is a simpler one.

class User {
    var name: String
    var tags: [String]
}

Now let's say you have an array of users in users.

var users: [User]

// Store users in the array

Now, this is how you check which user contains a particular tag.

let tag = "yourTag"

users.filter( { $0.tags.contains(tag) } )

However, if you are adamant that you need the tags in a dictionary, then you could do it like this,

users.filter( {$0.tags.values.contains(tag)} )

If you want to check if the tag is part of any of the tags in a particular user, you could do it like this,

let filteredUsers = users.filter( {$0.tags.reduce(false, {$1.contains(tag)} )} )

P.S - we don't use ; in Swift.

Rakesha Shastri
  • 11,053
  • 3
  • 37
  • 50
  • Thanks for the reply and suggestion. There is a reason it needs to be stored in the dictionary. Since this is the case, is there a way to still search it? Thanks, – Daniel Shaffer Dec 19 '18 at 03:26
  • @DanielShaffer what is the reason? That code looks very redundant because your keys are always the array indices. – Rakesha Shastri Dec 19 '18 at 03:26
  • Users can add other types of tags, which the server parses based on special key names. I can see if the server can be changed, but for now I have to work in these constraints. – Daniel Shaffer Dec 19 '18 at 03:29
  • @DanielShaffer can you show us a real example? Btw the answer has been edited for the case of your dictionary. – Rakesha Shastri Dec 19 '18 at 03:30
  • My answer satisfies the condition, you can use `.filter` and `.enumerated()` to return the value and key for each element in your tags dictionary, then compare it to a search predicate. – RLoniello Dec 19 '18 at 03:34
  • Thanks very much. The filter worked. One question: .contains only returns when there is an exact match, instead of just containing the word. Is there a way to actually return when it contains part of the search word? Example: your in yourTag would return yourTag. Thanks – Daniel Shaffer Dec 19 '18 at 03:59
  • @DanielShaffer that goes against the whole concept of tags. What is the actual requirement? – Rakesha Shastri Dec 19 '18 at 06:33
  • 1
    It seems like tags should be wrapped into a struct with potential flags and properties on them. For the server they can be encoded differently but there is no need to keep server data model structure on the client. – Sulthan Dec 19 '18 at 06:47
  • @Sulthan i agree, but that still doesn't explain the partial match he is ok with for the tag. Seems to me like those aren't actually tags in the first place. – Rakesha Shastri Dec 19 '18 at 06:48
1

It's good practice to make your models conform to Codeable:

struct User: Codable {
    var userId: UUID
    var name: String = ""
    var referenceTags: [String: String] = [:]
}


    // Create some users:
    let Bob = User(userId: UUID(), name: "Bob", referenceTags: ["tag1": "no", "tag2": "yes"])
    let Steve = User(userId: UUID(), name: "Steve", referenceTags: ["tag2": "no", "tag3": "no"])

Create an array of users to filter.

    let users:[User] = [Bob, Steve]

Get a search predicate or string from your Search bar or other.

    let searchPredicate = "yes"

Filter and iterate over tags for the required value.

    let results = users.filter { (user) -> Bool in
        for (key, _) in user.referenceTags.enumerated() {
            if (user.referenceTags[key] == searchPredicate) {
                return true
            }
        }
    }
RLoniello
  • 2,309
  • 2
  • 19
  • 26