Swift - Search large array based on multiple conditions

时间:2017-08-05 12:30:01

标签: ios arrays swift uisearchbar predicate

I have an array of 4K+ items, using UISearchBar and UISearchController to sort the list for matches. I would like to get sorted array by few criteria, but most importantly by the order user types first

Search controller has 3 scope buttons for

  • category
  • subCategory
  • allCat

The items in the search array are using struct class to access the conditions:

struct Item {
    var title: String
    var category: String
    var subCategory: String
    var allCat: String
}

The standard method of filtering would be using something like this:

func filterContentForSearchText(_ searchText: String, scope: String = "All") {

    self.filteredItems = allItems.filter({( item : Item) -> Bool in

        let categoryMatch = (item.allCat == scope) || (item.category == scope)
        return categoryMatch && item.title.lowercased().contains(searchText.lowercased())
        //return categoryMatch && (item.title.lowercased().range(of:searchText.lowercased()) != nil)
    })

    tableView.reloadData()
}

Which is fine in case of few items, but if I have large array and when using the block above, plenty unrelated items with the string typed contained would be included.

I can use predicate to get better result using BEGINSWITH, but I am failing to meet the conditions for categories. I am pretty sure the code block below isn't effective and would appreciate for more economic advise. Another obstacle is that the array contains strings with multiple words. For example:

array = ["Apple", "Apple Freshly Picked", "Apple Green", "Pear", "Melon", "Pear Yellow",....]

So the result when user starts to type "Ap" should be

  • Apple
  • Apple Green
  • Apple Freshly Picked

I kinda made it work without the conditions for search categories (struct) being met:

func filterContentForSearchText(_ searchText: String, scope: String = "All") {

    let words = searchText.components(separatedBy: " ")

    var word1 = ""
    var word2 = ""
    var word3 = ""

    var predicate = NSPredicate()
    var array = [Item]()

    if scope == "All" {

        if words.count == 1{
            word1 = (words[0])
            predicate = NSPredicate(format: "SELF BEGINSWITH[cd] %@ OR SELF LIKE[cd] %@", word1, word1)
        }
        else if words.count == 2{
            word1 = (words[0])
            word2 = (words[1])
            predicate = NSPredicate(format: "SELF BEGINSWITH[cd] %@ AND SELF CONTAINS[cd] %@", word1, word2)
        }
        else if words.count == 3{
            word1 = (words[0])
            word2 = (words[1])
            word3 = (words[2])
            predicate = NSPredicate(format: "SELF BEGINSWITH[cd] %@ AND SELF CONTAINS[cd] %@ AND SELF CONTAINS[cd] %@", word1, word2, word3)
        }

    } else {

        predicate = NSPredicate(format: "title BEGINSWITH[cd] %@ AND category == %@", searchText, scope)

        if words.count == 1{
            word1 = (words[0])
            predicate = NSPredicate(format: "SELF BEGINSWITH[cd] %@ OR SELF LIKE[cd] %@ AND category == %@", word1, word1, scope)
        }
        else if words.count == 2{
            word1 = (words[0])
            word2 = (words[1])
            predicate = NSPredicate(format: "SELF BEGINSWITH[cd] %@ AND SELF CONTAINS[cd] %@ AND category == %@", word1, word2, scope)
        }
        else if words.count == 3{
            word1 = (words[0])
            word2 = (words[1])
            word3 = (words[2])
            predicate = NSPredicate(format: "SELF BEGINSWITH[cd] %@ AND SELF CONTAINS[cd] %@ AND SELF CONTAINS[cd] %@ AND category == %@", word1, word2, word3, scope)
        }

    }

    array = (allItems as NSArray).filtered(using: predicate) as! [Item]
    self.filteredItems = array

    let lengthSort = NSSortDescriptor(key: "length", ascending: true)
    let sortedArr = (self.filteredItems as NSArray).sortedArray(using: [lengthSort])
    self.filteredItems = sortedArr as! [Item]

    self.tableView.reloadData()
}

How do I approach this logic satisfying the categories as well as the typing string match and length/range of the string (first word) please?

Thank you

1 个答案:

答案 0 :(得分:0)

我找到了方法。我将.filter和.sort结合起来得到了结果:

func filterContentForSearchText(_ searchText: String, scope: String = "All") {

    let options = NSString.CompareOptions.caseInsensitive

    if scope == "All"{

        print("filtering All")
        self.filteredItems = allItems
            .filter{$0.title.range(of: searchText, options: options) != nil && $0.allCat == scope}
            .sorted{ ($0.title.hasPrefix(searchText) ? 0 : 1) < ($1.title.hasPrefix(searchText) ? 0 : 1) }
    }
    else{

        print("filtering \(scope)")
        self.filteredItems = allItems
            .filter{$0.title.range(of: searchText, options: options) != nil && $0.category == scope}
            .sorted{ ($0.title.hasPrefix(searchText) ? 0 : 1) < ($1.title.hasPrefix(searchText) ? 0 : 1) }

    }
    tableView.reloadData()
}

希望这有助于某人