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

About this widget

The Hits widget is used to display a list of results. They are reloaded automatically when new hits are fetched from Algolia. There are two Hits widget in InstantSearch:

  • HitsTableWidget built over a UITableView.
  • HitsCollectionWidget built over a UICollectionView.

Examples

1
2
3
4
5
6
  var tableView = HitsTableWidget(frame: CGRect.zero, style: .grouped)
  tableView.index = "someIndex"
  tableView.infiniteScrolling = false
  tableView.hitsPerPage = 5
  tableView.showItemsOnEmptyQuery = true
  tableView.remainingItemsBeforeLoading = 3

Parameters

index
type: string
default:
Optional

The index name associated with the widget.

1
hitsWidget.index = "myIndex"
variant
type: string
default:
Optional

Allows to distinguish between two widgets that target the same index, but with different search parameters.

1
hitsWidget.variant = "myVariant"
hitsPerPage
type: UInt
default: 20
Optional

How many hits are requested and displayed for each search query.

1
hitsWidget.hitsPerPage = 20
infiniteScrolling
type: Bool
default: true
Optional

If false, disables the infinite scroll of the widget.

1
hitsWidget.infiniteScrolling = true
remainingItemsBeforeLoading
type: UInt
default: 5
Optional

The minimum number of remaining hits to load the next page. For example, if set to 10, the next page is loaded when there are less than 10 items below the last visible item.

1
hitsWidget.remainingItemsBeforeLoading = 10
showItemsOnEmptyQuery
type: Bool
default: true
Optional

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

1
hitsWidget.showItemsOnEmptyQuery = true

Layout and Content

Delegate and DataSource

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

ViewController Inheritance

With this method, the ViewController inherits from HitsTableViewController or HitsCollectionViewController. 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 HitsTableViewController or HitsCollectionViewController.
  • 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
class HitsTableViewControllerDemo: HitsTableViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        hitsTableView = HitsTableWidget()
        hitsTableView.frame = self.view.frame
        hitsTableView.register(UITableViewCell.self, forCellReuseIdentifier: "hitTableCell")

        self.view.addSubview(hitsTableView)

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

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath, containing hit: [String : Any]) -> UITableViewCell {
        let cell = hitsTableView.dequeueReusableCell(withIdentifier: "hitTableCell", for: indexPath)
        cell.textLabel?.text = hit["name"] as? String

        return cell
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath, containing hit: [String : Any]) {
        print("hit \(String(describing: hit["name"]!)) has been clicked")
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        print("I can also edit height! or any other method")
        return 50
    }

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 HitsController object. You need to specify what hit widget is controlled by the HitsController, whether this is a HitsTableWidget or HitsCollectionWidget, then assign the dataSource and delegate properties of the widget to the HitsController. In that way, you’re letting the HitsController take care of the delegate and dataSource.

You also need to specify the data and behavior of the widget. The HitsController provides tableDataSource and tableDelegate properties for the HitsTableWidget, as well as collectionDataSource and collectionDelegate for the HitsCollectionWidget. 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
import InstantSearch

class ViewController: UIViewController, HitsTableViewDataSource, HitsTableViewDelegate {

    var instantSearch: InstantSearch!
    @IBOutlet weak var hitsTable: HitsTableWidget!
    var hitsController: HitsController!

    override func viewDidLoad() {
        super.viewDidLoad()

        hitsController = HitsController(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 = hitsTable.dequeueReusableCell(withIdentifier: "hitCell", for: indexPath)
        cell.textLabel?.text = hit["name"] as? String

        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath, containing hit: [String: Any]) {
        print("hit \(String(describing: hit["name"]!)) has been clicked")
    }

    func viewForNoResults(in tableView: UITableView) -> UIView {
      // Specify a View when no results are returned from Algolia
    }

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.

Empty View

The Hits widget implements an empty view mechanism to display an alternative view if there are no results to display. For that, you can use the HitsTableViewDataSource or the HitsCollectionViewDataSource method:

  • viewForNoResults(in tableView: UITableView) -> UIView
  • viewForNoResults(in collectionView: UICollectionView) -> UIView.

HitsViewModel

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 your hits view with a single index.

Methods

  • numberOfRows()
  • hitForRow(at:)
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
  @IBOutlet weak var tableView: HitsTableWidget!
  var hitsViewModel: HitsViewModel!

  override func viewDidLoad() {
          super.viewDidLoad()

          hitsViewModel = HitsViewModel(view: tableView)
          InstantSearch.shared.register(viewModel: hitsViewModel)

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

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

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

          let hit = hitsViewModel.hitForRow(at: indexPath)
          cell.textLabel?.text = getTextOutOfHit(hit)

          return cell
      }

Did you find this page helpful?