API Reference / iOS InstantSearch Widgets / Multi Hits
Apr. 24, 2019

Multi Hits

About this widget

The multi hits widgets are made to display your search results, with each index appearing in a section. Indices are reloaded automatically when new hits are fetched from Algolia. There are two multi Hits widget in InstantSearch:

  • MultiHitsTableWidget built over a UITableView.
  • MultiHitsCollectionWidget built over a UICollectionView.

Examples

1
2
3
4
  var tableView = MultiHitsTableWidget(frame: CGRect.zero, style: .grouped)
  tableView.indices = "index1,index2"
  tableView.hitsPerSection = "5,3"
  tableView.showItemsOnEmptyQuery = true

Parameters

indices
type: string
default:
Required

The indices associated with this hit. The values are comma-separated with no spaces in between.

1
multiHitsWidget.indices = "index1,index2,index3"
variants
type: string
default:
Optional

Allows to distinguish between two widgets that target the same index, but with different search parameters. The variants are comma-separated with no space in between.

1
multiHitsWidget.variants = "variant1,variant2,vartiant3"
hitsPerSection
type: String
default: 20
Optional

How many hits are requested and displayed for each index.

1
multiHitsWidget.hitsPerSection = "5,3,3"
showItemsOnEmptyQuery
type: Bool
default: true
Optional

If false, displays an empty hits widget when there is no query text entered by the user.

1
multiHitsWidget.showItemsOnEmptyQuery = true

Layout and Content

Delegate and DataSource

InstantSearch provides two ways to handle the Delegate and DataSource of a MultiHits widget:

ViewController Inheritance

With this method, the ViewController inherits from MultiHitsTableViewController or MultiHitsCollectionViewController. These bases classes handle things like creating the necessary properties or hooking the delegate and the dataSource of the hits widget. What you need to do on your end is:

  • Have your ViewController inherit from MultiHitsTableViewController or MultiHitsCollectionViewController.
  • In viewDidLoad, assign hitsTableView to your hits widget, whether it was created programmatically or through Interface Builder.
  • At the end of ViewDidLoad, call InstantSearch.shared.registerAllWidgets(in: self.view) to add your widget to InstantSearch.
  • To specify the look and feel of your cell, override one of the two methods which gives you access to the hit:
    • tableView(_:cellForRowAt:containing:)
    • collectionView(_:cellForItemAt:containing:)
  • Optional: To specify what happens when a hit is selected, override one of these two methods:
    • tableView(_:didSelectRowAt:containing:)
    • collectionView(:didSelectItemAt:containing:)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import UIKit
import InstantSearch

class HitsViewController: MultiHitsTableViewController {

    @IBOutlet weak var tableView: MultiHitsTableWidget!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        hitsTableView = tableView

        InstantSearch.shared.registerAllWidgets(in: self.view)
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath, containing hit: [String : Any]) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "hitCell", for: indexPath)

        if indexPath.section == 0 { // index 1
            cell.textLabel?.highlightedTextColor = .blue
            cell.textLabel?.highlightedBackgroundColor = .yellow
            cell.textLabel?.highlightedText = SearchResults.highlightResult(hit: hit, path: "name")?.value
        } else { // index 2
            cell.textLabel?.highlightedTextColor = .white
            cell.textLabel?.highlightedBackgroundColor = .black
            cell.textLabel?.highlightedText = SearchResults.highlightResult(hit: hit, path: "name")?.value
        }

        return cell
    }

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let label = UILabel(frame: CGRect(x: 0, y: 0, width: 300, height: 30))
        let view = UIView()
        view.addSubview(label)
        view.backgroundColor = UIColor.gray

        return view
    }
}

Note that you can override any delegate or dataSource method like heightForRowAt. This way, you have full control over every aspect of your UITableView/UICollectionView.

ViewController Composition

With this method, the ViewController owns a MultiHitsController object. You need to specify what hit widget is controlled by the HitsController, whether this is a MultiHitsTableWidget or MultiHitsCollectionWidget, then assign the dataSource and delegate properties of the widget to the MultiHitsController. In that way, you’re letting the MultiHitsController take care of the delegate and dataSource.

You also need to specify the data and behavior of the widget. The MultiHitsController provides tableDataSource and tableDelegate properties for the MultiHitsTableWidget, as well as collectionDataSource and collectionDelegate for the MultiHitsCollectionWidget. The ViewController therefore needs to implement a few protocols to be able to specify the hit cells and their onClick behavior. The protocols to implement are:

  • HitsTableViewDataSource: specifiy the rendering of the table hit cells.
  • HitsTableViewDelegate: specifiy what happens when table hit cell is clicked.
  • HitsCollectionViewDataSource: specifiy the rendering of the collection hit cells.
  • HitsCollectionViewDelegate: specifiy what happens when collection hit cell is clicked.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import InstantSearch

class ViewController: UIViewController, HitsTableViewDataSource, HitsTableViewDelegate {

    var instantSearch: InstantSearch!
    @IBOutlet weak var hitsTable: MultiHitsTableWidget!
    var hitsController: MultiHitsController!

    override func viewDidLoad() {
        super.viewDidLoad()

        hitsController = MultiHitsController(table: hitsTable)
        hitsTable.dataSource = hitsController
        hitsTable.delegate = hitsController
        hitsController.tableDataSource = self
        hitsController.tableDelegate = self

        InstantSearch.shared.registerAllWidgets(in: self.view)
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath, containing hit: [String : Any]) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "hitCell", for: indexPath)

        if indexPath.section == 0 { // index 1
            cell.textLabel?.highlightedTextColor = .blue
            cell.textLabel?.highlightedBackgroundColor = .yellow
            cell.textLabel?.highlightedText = SearchResults.highlightResult(hit: hit, path: "name")?.value
        } else { // index 2
            cell.textLabel?.highlightedTextColor = .white
            cell.textLabel?.highlightedBackgroundColor = .black
            cell.textLabel?.highlightedText = SearchResults.highlightResult(hit: hit, path: "name")?.value
        }

        return cell
    }

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let label = UILabel(frame: CGRect(x: 0, y: 0, width: 300, height: 30))
        let view = UIView()
        view.addSubview(label)
        view.backgroundColor = UIColor.gray

        return view
    }

The downside of this method is that it makes it hard to specify additional delegate and dataSource methods, since they are assigned to the HitsController and not to the ViewController itself. We therefore recommend that you use the inheritance method.

MultiHitsViewModel

Widgets work well when you want default styles to be applied and don’t need heavy customization regarding its behavior.

ViewModels are useful when you need full control over the rendering without having to reimplement any business logic. As soon as you hit a feature wall using the default widgets, you can use ViewModels to have more flexibility and control over the delegate and datasource methods.

ViewModels encapsulate the logic for a specific search concept and provide a way to interact with InstantSearch. You can use them to customize a hits view with multiple indices.

Methods

  • numberOfRows(in:)
  • hitForRow(at:)
  • numberOfSections()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@IBOutlet weak var tableView: MultiHitsTableWidget!
var multiHitsViewModel: MultiHitsViewModel!

override func viewDidLoad() {
      super.viewDidLoad()

      multiHitsViewModel = MultiHitsViewModel(view: tableView)
      InstantSearch.shared.register(viewModel: multiHitsViewModel)

      // Now can access access the tableView's delegate and datasource methods.
      tableView.dataSource = self
      tableView.delegate = self
  }

  func numberOfSections(in tableView: UITableView) -> Int {
      return multiHitsViewModel.numberOfSections()
  }

  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
      return multiHitsViewModel.numberOfRows(in: section)
  }

      func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
      let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
      let hit = multiHitsViewModel.hitForRow(at: indexPath)

      if indexPath.section == 0 { // First index
          cell.textLabel?.text = getTextOutOfHitWithFirstIndex(hit)
      } else { // Second index
          cell.textLabel?.text = getTextOutOfHitWithSecondIndex(hit)
      }

      return cell
  }

Did you find this page helpful?

iOS InstantSearch v3