1

I've been going round in circles trying to implement search functionality in my UITableView.

I've followed loads of instructions here on stackoverflow and watched a ton of videos on YouTube and I feel like I know how to implement UISearchController now but i'm struggling how to actually filter my array in the updateSearchResultsForSearchController function.

I've managed to get search working if I have a simple array of string values as you see in most of the online examples for implementing search but I have an array of dictionaries and have no idea how to use .filter to get at the key/value pairs in the dictionary.

My peopleData array is an array of dictionaries that comes from a JSON file which looks like this:

{
  "people": [
  {
    "ID" : "1",
    "firstname" : "Bob",
    "lastname" : "Smith",
    "age" : 25,
    "gender" : "Male"
  },
  {
    "ID" : "2",
    "firstname" : "Fred",
    "lastname" : "Smith",
    "age" : "52",
    "gender" : "Male"
  }
           ]
}  


My view controller looks like this:

//  Created by Elliot Wainwright on 26/02/2016.  
//  Copyright © 2016 Elliot Wainwright. All rights reserved.

import UIKit

class SelectPersonTableViewController: UITableViewController {

    var myList:NSMutableArray = []
    var personData:NSMutableArray = []
    var filteredPeopleData:NSMutableArray = []

    var searchController : UISearchController!
    var resultsController = UITableViewController()

    @IBOutlet var selectPersonTable: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        guard let path = NSBundle.mainBundle().pathForResource("PeopleData", ofType: "json") else {
            print("error finding file")
            return
        }

        do {
            let data: NSData? = NSData(contentsOfFile: path)
            if let jsonResult: NSDictionary =
                try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? NSDictionary {
                    personData = jsonResult["people"] as! NSMutableArray
                }
            } catch let error as NSError {
                print("Error:\n \(error)")
                return
            }

        self.resultsController.tableView.dataSource = self
        self.resultsController.tableView.delegate = self

        self.searchController = UISearchController(searchResultsController: self.resultsController)
        self.tableView.tableHeaderView = self.searchController.searchBar
        self.searchController.searchResultsUpdater = self
        definesPresentationContext = true

        selectPersonTable.reloadData()
    }

    func updateSearchResultsForSearchController(searchController: UISearchController) {
        //Filter through the array

            //---------------------------------------------
            //I have no idea how to do this for my array!!!
            //---------------------------------------------

        //Update the results TableView
        self.resultsController.tableView.reloadData()
    }
    // MARK: - Table view data source

    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if tableView == self.tableView {
            return self.peopleData.count
        } else {
            return self.filteredPeopleData.count
        }
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)

        if tableView == self.tableView {
            for person in personData {
                let thisPerson = personData.objectAtIndex(indexPath.row)
                cell.textLabel?.text = thisPerson["person"] as? String
            }
        } else {
            for person in filteredPeopleData {
                let thisPerson = filteredPeopleData.objectAtIndex(indexPath.row)
                cell.textLabel?.text = thisPerson["person"] as? String
            }
        }

        return cell
    }

    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        myList.addObject(personData.objectAtIndex(indexPath.row))
        navigationController?.popViewControllerAnimated(true)
    }

    // MARK: - Navigation

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        var vc = segue.destinationViewController as! ViewController
        vc.peopleList = myList
    }
}


As you can see, In ViewDidLoad I get the content of the JSON file within 'people' personData = jsonResult["people"] as! NSMutableArray so I end up with an array of dictionaries.

Ideally, users would be able to type something in the UISearchBar and the array (and so the table) would be filtered to show any elements that contain any values that include part of what the user has typed. So typing "ith" would return both rows as 'ith' appears in 'Smith' for both elements in the array.

At very least, i'd like to be able to search on at least one key/value.

Elliot
  • 83
  • 10

1 Answers1

3

Try this:

let results = personData.filter({ person in
    if let firstname = person["firstname"] as? String, lastname = person["lastname"] as? String, query = searchController.searchBar.text {
        return firstname.rangeOfString(query, options: [.CaseInsensitiveSearch, .DiacriticInsensitiveSearch]) != nil || lastname.rangeOfString(query, options: [.CaseInsensitiveSearch, .DiacriticInsensitiveSearch]) != nil
    }
    return false
})
filteredPeopleData = NSMutableArray(array: results)

This filters people that have a matching firstname property. You could implement something similar for lastname.

paulvs
  • 11,963
  • 3
  • 41
  • 66
  • I get a "Cannot assign value of type '[Element]' to type 'NSMutableArray' " error :( – Elliot May 16 '16 at 01:39
  • That fixes the error I was getting. Looks like that filter is working. I just have a weird issue where 1) when I tap into the searchbar, it takes ages before the keyboard comes up 2) when the keyboard comes up, the searchbar itself disappears so you can't see what you've typed. The search works though :) – Elliot May 16 '16 at 02:06
  • You should maintain one issue per question, feel free to open a new one if you have tried but failed to resolve the new issues. May help: http://stackoverflow.com/a/27487885/1305067, http://stackoverflow.com/questions/32687240/ios-9-searchbar-disappears-from-table-header-view-when-uisearchcontroller-is-act – paulvs May 16 '16 at 02:22
  • You're right. I'm still a bit of a stackoverflow noob so thanks for being patient but more importantly, thanks for being so so helpful. Those two links solved the other two issues I was having and everything is working pretty well! Can't believe how quickly I got an answer after losing pretty much all weekend trying to work this out! Now for me to try and figure out how I can get all the values into the search. Should be easier now you've shown me how to get one in! Thanks for your help! – Elliot May 16 '16 at 02:36
  • You're welcome @Elliot, once you've done a few apps these things become quite common. Feel free to mark this as the accepted answer if you are happy with it. – paulvs May 16 '16 at 02:57
  • I actually asked for two things in the original question. I put:
    "_Ideally, users would be able to type something in the UISearchBar and the array (and so the table) would be filtered to show any elements that contain any values that include part of what the user has typed. So typing "ith" would return both rows as 'ith' appears in 'Smith' for both elements in the array. At very least, i'd like to be able to search on at least one key/value._"
    So if I accept your answer, should I post the same question again asking for the solution for searching all items in the dictionary?
    – Elliot May 16 '16 at 14:25
  • If you need to search across multiple values (e.g. `lastname`), just add an OR operator and add the next value. I updated my answer to show this. – paulvs May 16 '16 at 17:24
  • Unfortunately that change doesn't work. lastname isn't a variable here. I would need to do something like: let lastname = person["firstname"]... in order to get a variable of lastname that I can use .rangeOfString on but I don't know where to put that. – Elliot May 16 '16 at 19:46
  • I'd forgotten to grab the `lastname` value, check updated answer. – paulvs May 16 '16 at 23:28
  • 1
    I could have sworn I had tried that! Anyway...It works! Thank you so much! P.S. It won't let me move this discussion to chat as I don't have a high enough rep – Elliot May 17 '16 at 01:25