iOS Change and Filter Indices in a TableView

I’m trying to set up a SearchViewController that has a Search Bar at the top then Segmented Control with 3 segments and a HitsTableView below.

I’d like to change the HitsTableView results when a segmented control is selected.

Each of the indices has a different styled custom TableViewCell.

Would this be handled through MultiHitsTableView or a regular HitsTableView?

Hey @phil.manning11,

If you’re only having one section in the tableView, then just use a HitsTableView, and then change the index of the hitsTableView when an item in the segmented control is selected

The MultiHitsTableView is useful when you want to showcase multiple indices in different sections of a tableView, which I don’t think is your case.

1 Like

Ok thanks for the tip @guy.daher!

So I’m not getting the new hit data at the moment. Where I’m handling the segmented control I used…

        self.hitsTableView.index = self.airlineIndex
        self.hitsTableView.reloadHits()

Is this the right place and code to call this?

EDIT: I’m changing the index and I can confirm it gets changed from printing it to the console, however the hit data still stays the same…

@IBAction func switchTableView(_ sender: UISegmentedControl) {
    setSegmentBarVwFrame()
}

func setSegmentBarVwFrame() {
    if (self.segmentedControl.selectedSegmentIndex == 0) {
        selectedIndex = 0
        hitsTableView.index = self.locationIndex
        print("The index is: ", hitsTableView.index)
        hitsTableView.reloadHits()
        
        UIView.animate(withDuration: 0.3, animations: {
            print("switched to location index")
        })
        
        let buttonFrame = CGRect(x:0,y:self.segmentBarVw.frame.origin.y,width:self.segmentBarVw.frame.size.width,height:self.segmentBarVw.frame.size.height)
        UIView.animate(withDuration: 0.3) {
            self.segmentBarVw.frame = buttonFrame
        }
    } else if (self.segmentedControl.selectedSegmentIndex == 1) {
        selectedIndex = 1
        hitsTableView.index = self.airlineIndex
        print("The index is: ", hitsTableView.index)
        hitsTableView.reloadHits()

        UIView.animate(withDuration: 0.3, animations: {
            print("switched to airline index")
        })
        
        let originX:CGFloat = (self.segmentedControl.frame.width / CGFloat(self.segmentedControl.numberOfSegments)) * CGFloat(self.segmentedControl.selectedSegmentIndex)
        let buttonFrame = CGRect(x:originX,y:self.segmentBarVw.frame.origin.y,width:self.segmentBarVw.frame.size.width,height:self.segmentBarVw.frame.size.height)
        
        UIView.animate(withDuration: 0.3) {
            self.segmentBarVw.frame = buttonFrame
        }
    } else if (self.segmentedControl.selectedSegmentIndex == 2) {
        selectedIndex = 2
        self.hitsTableView.index = self.userIndex
        print("The index is: ", hitsTableView.index)
        hitsTableView.reloadHits()

        UIView.animate(withDuration: 0.3, animations: {
            print("switched to user user index")
        })
        
        let originX:CGFloat = (self.segmentedControl.frame.width / 3) * 2
        let buttonFrame = CGRect(x:originX,y:self.segmentBarVw.frame.origin.y,width:self.segmentBarVw.frame.size.width,height:self.segmentBarVw.frame.size.height)
        
        UIView.animate(withDuration: 0.3) {
            self.segmentBarVw.frame = buttonFrame
        }
    } else {
        selectedIndex = 0
    }
    
}

@phil.manning11 I think I know what’s going on: reloadHits doesn’t actually do a search to Algolia, it just reload the hits that it has locally, if it has them locally. So if you’ve never executed a search on a specific index, you can do InstantSearch.shared.search() to search all indices again, or if you want to search only a specific index, then you can do something along the lines of InstantSearch.shared.getSearcher(index+variant).search().

This is something that we plan to improve in our next major release of InstantSearch so that we give a better predictable API. Also, we plan to ship a widget that changes the index, like you’re trying to do, and that would take care of all of this for you behind the scenes.

1 Like

Ahh ok that makes sense @guy.daher . I tried calling the InstantSearch.shared.search() each time the segmented control is changed, however it still does not update the search. I’ve tested each index separately and can see they do work, but still not when the segment is changed.

A widget with that functionality would be awesome :wink: ! Still trying to figure out this workaround tho -

APPDELEGATE.SWIFT

// --------ALGOLIA--------
    let searcherIds = [SearcherId(index: "airlines"),SearcherId(index: "airports"), SearcherId(index: "locations"), SearcherId(index: "users")]
    InstantSearch.shared.configure(appID: ALGOLIA_APP_ID, apiKey: ALGOLIA_API_KEY, searcherIds: searcherIds)

SEARCHVIEWCONTROLLER.SWIFT

// MARK: - Variables
var db:Firestore!
var locationIndex = "locations"
var airlineIndex = "airlines"
var userIndex = "users"
var searchViewModel: SearchViewModel!
let storage = Storage.storage().reference()
var selectedIndex: Int!

// MARK: - View Did Load
override func viewDidLoad() {
    super.viewDidLoad()
    // Roll tide...
    
    selectedIndex = 0
    
    // Configure the cell to the nib file
    let nib = UINib(nibName: "LocationCell", bundle: nil)
    tableView.register(nib, forCellReuseIdentifier: "locationCell")
    
    db = Firestore.firestore()

    // Set up the table view
    hitsTableView = tableView
    hitsTableView.index = locationIndex
    
    // Add all widgets in view to InstantSearch
    InstantSearch.shared.registerAllWidgets(in: self.view)
    
    searchViewModel = SearchViewModel(view: searchBar)
    InstantSearch.shared.register(viewModel: searchViewModel)
    InstantSearch.shared.search()
    searchBar.delegate = self
}

// MARK: - Actions
@IBAction func switchTableView(_ sender: UISegmentedControl) {
    setSegmentBarVwFrame()
}

// MARK: - Functions
func setSegmentBarVwFrame() {
    if (self.segmentedControl.selectedSegmentIndex == 0) {
        selectedIndex = 0
        hitsTableView.index = self.locationIndex
        print("The index is: ", hitsTableView.index) // prints correctly
        InstantSearch.shared.register(viewModel: searchViewModel)
        InstantSearch.shared.search()
        // InstantSearch.shared.getSearcher(named: self.userIndex)?.search()  also crashed
        self.tableView.reloadData()
    } else if (self.segmentedControl.selectedSegmentIndex == 1) {
        selectedIndex = 1
        hitsTableView.index = self.airlineIndex
        print("The index is: ", hitsTableView.index) // prints correctly
        InstantSearch.shared.register(viewModel: searchViewModel)
        InstantSearch.shared.search()
        // InstantSearch.shared.getSearcher(named: self.userIndex)?.search()  also crashed
        self.tableView.reloadData()
    } else if (self.segmentedControl.selectedSegmentIndex == 2) {
        selectedIndex = 2
        self.hitsTableView.index = self.userIndex
        print("The index is: ", hitsTableView.index)
        InstantSearch.shared.register(viewModel: searchViewModel)
        InstantSearch.shared.search()
        // InstantSearch.shared.getSearcher(named: self.userIndex)?.search()  also crashed

        self.tableView.reloadData()
    } else {
        selectedIndex = 0
    }
    
}

The data out is still from the initial index…in this case the locationIndex set in the viewdidload. Any thoughts?

Hey @phil.manning11,

So I might have an idea what is the issue:

The thing about calling InstantSearch.shared.search() or InstantSearch.shared.getSearcher(named: self.userIndex)?.search() is that it happens asynchronously, so even if you call self.tableView.reloadData(), the reload will happen before you actually get your new data, so that’s why your table won’t update.

So there are 2 solutions to this issue:

1- You use our widgets such as HitsTableWidget, and you register them to InstantSearch. By registering them, InstantSearch will be aware that this hitsTableView needs to be reloaded when new data comes from the index specified in hitsTableView.index.

2- You can do the reload yourself by doing something like the following (searchResultHandler will be called when you get new results for the specific index).

let searcher = InstantSearch.shared.getSearcher(named: self.userIndex)!
searcher.addResultHandler(searchResultHandler)

  func searchResultHandler(_ results: SearchResults?, _ error: Error?, _ userInfo: [String: Any]) {
    if let error = error {
      // do something with error
    } else {
     // hitsTableView.reloadData() or something along those lines 
    }
  }

Hey @guy.daher -

So the async task makes sense but there’s still an issue with getting the new indexes data. I have the widgets all registered, so I’m not sure what I would be doing wrong because I have simply changed the index and can confirm it changes by outputting the correct changed index name but the data is still from the original index that first loaded.

I also attempted the second solution of the custom searchResultHandler. This didn’t seem to call at all as I couldn’t get an error or callback to return.

@phil.manning11 Okay so I just double checked and tested it. I think I found the missing piece for this (which I’ll create a widget for it in the future so that we avoid these kind of bugs, thanks for being patient!)

After changing the index name, you need to register the widget again so that InstantSearch registers this new index and can react accordingly.

So basically something like:

        hitsTableView.index = "newIndexName"
        InstantSearch.shared.register(widget: hitsTableView)
        InstantSearch.shared.search()

I tested it and it worked for me, the index was changed. Let me know if it works for you!

1 Like

Thanks @guy.daher! Yup, I added the .register into each if statement of the setSegmentBar function and it’s working.

Thanks for keeping up and figuring this out!

1 Like