Search code examples
iosswift3xcode8swift4

trying to use the "search" method but I still have an error


Hi this is my first time trying to use the search method, I am using the Search Bar Guide on Apple Developer website but I have this issue and can't seem to figure it out. I have my Data stored in a Class DataServices and I have my tableView cells working just perfect in my ViewController, but I can't seem to figure out what I am supposed to return in the search method to complete the method, When I do it just as the search Guide instructions Xcode screams at me. Can you bless me with your knowlegde

class ListVC: UIViewController,UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate {

    @IBOutlet weak var searchBar: UISearchBar!

    @IBOutlet weak var tableView: UITableView!

    var filteredData: [List]!
    let data = DataServices.instance.getList()

    override func viewDidLoad() {
        super.viewDidLoad()
        searchBar.delegate = self
        tableView.dataSource = self
        tableView.dataSource = self
        filteredData = data
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return DataServices.instance.getList().count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if let cell = tableView.dequeueReusableCell(withIdentifier: "ListingCell") as? ListCell {
            let listing = DataServices.instance.getList()[indexPath.row]
            cell.updateView(lists: listing)
            return cell
        }else {
            return ListCell()
        }
    }

    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        filteredData = searchText.isEmpty ? data: data.filter({ (List: String) -> Bool in
            return List.range(of: searchText, options: .caseInsensitive, range: nil, locale: nil) != nil    

> Value of type 'List' has no member 'range' is the error I get

        })

        tableView.reloadData()
    }

    func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
        self.searchBar.showsCancelButton = true
    }

    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        searchBar.showsCancelButton = false
        searchBar.text = ""
        searchBar.resignFirstResponder()

    }

}

class DataServices { static let instance = DataServices()

private let currentList = [
    List(firstText: "Name", secondText: "Shannon"),
    List(firstText: "Car", secondText: "Infinity"),
    List(firstText: "City", secondText: "Brandon"),
    List(firstText: "Wife", secondText: "Naedra"),
    List(firstText: "Child", secondText: "Shantae"),
    List(firstText: "Job", secondText: "Driver"),
    List(firstText: "Cell", secondText: "iPhone"),
    List(firstText: "Computer", secondText: "Imac")

]

func getList() -> [List] {
return currentList
}

}

struct List {

private (set) public var toptitle: String
private (set) public var bottomtitle: String

init(firstText: String, secondText: String) {
    self.toptitle = firstText
    self.bottomtitle = secondText
}

}


Solution

  • Closure parameters represent instances not types and the filter closure applied to an array takes only one argument. The checking for empty string is redundant.

    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        filteredData = data.filter ({ (list) -> Bool in
            return list.firstText.range(of: searchText, options: .caseInsensitive) != nil ||
                   list.secondText.range(of: searchText, options: .caseInsensitive) != nil
        })
        tableView.reloadData()
    }
    

    or with simplified syntax

    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        filteredData = data.filter { $0.firstText.range(of: searchText, options: .caseInsensitive) != nil ||
                                     $0.secondText.range(of: searchText, options: .caseInsensitive) != nil }  
        tableView.reloadData()
    }
    

    I recommend to declare filteredData as non-optional empty array to avoid unnecessary unwrapping

    var filteredData = [List]()
    

    and force downcast the cell, if the code crashes it reveals a design mistake.

    let cell = tableView.dequeueReusableCell(withIdentifier: "ListingCell", for: indexPath) as! ListCell
    

    If you want constants in your List struct simply write

    struct List {
        let toptitle: String
        let bottomtitle: String
    }
    

    you get the initializer for free. private (set) var is very objective-c-ish

    Finally getting the same (static) list on each call of cellForRow is unnecessarily expensive, get the item from data

    let listing = data[indexPath.row]