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

About this widget

The Hits components display a list of search results. They are reloaded automatically when new hits are fetched from Algolia.

To add Hits to your search experience, use these components:

  • Searcher: The Searcher that handles your searches.
  • HitsInteractor: The logic applied to the hits.
  • HitsController: The controller that interfaces with a concrete hits view.

HitsInteractor has a generic Record parameter that represents a record of your index and must conform to the Codable protocol. If, for some reason, your record can’t be parsed into the type you provided, the onError event of the Interactor instance will be triggered. In case you prefer to deal with raw JSON objects, set JSON as the type of record and use the rawHitAtIndex(_ row: Int) -> [String: Any]? method to access a hit.

Examples

Connect HitsInteractor and HitsController with each other using the provided connection method. Connect HitsInteractor with Searcher. In this example, we use the HitsTableController provided by InstantSearch.

Now, each time you launch a new search:

  • Searcher receives new results and transmit them to HitsInteractor
  • HitsInteractor parses search results and notifies HitsController
  • HitsController refreshes the view presenting the hits
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let searcher: SingleIndexSearcher = SingleIndexSearcher(appID: "YourApplicationID",
                                                        apiKey: "YourSearchOnlyAPIKey",
                                                        indexName: "YourIndexName")
let hitsInteractor: HitsInteractor<JSON> = .init()
let hitsTableController: HitsTableController<HitsInteractor<JSON>> = .init(tableView: UITableView())

override func viewDidLoad() {
  super.viewDidLoad()
  setup()
}

func setup() {
  hitsInteractor.connectSearcher(searcher)
  hitsInteractor.connectController(hitsTableController)
  searcher.search()
}

Parameters

infiniteScrolling
type: InfiniteScrolling
default: .on(withOffset: 5)
Optional

Infinite scrolling setting. Offset defines the threshold of loading next page. For example, if the index of the last visible hit is 10, and offset is set to 3, the next page will be loaded if the hit at index 13 (10+3) is not loaded. If set to .off, disables infinite scrolling.

1
let hitsInteractor: HitsInteractor<JSON> = .init(infiniteScrolling: .on(withOffset: 20))
showItemsOnEmptyQuery
type: Bool
default: true
Optional

If false, provides an empty search result when there is no query text entered by the user.

1
let hitsInteractor: HitsInteractor<JSON> = .init(showItemsOnEmptyQuery: true)

HitsController

The controllers provided by default, like the HitsTableController or the HitsCollectionController, allow you to create a basic Hits view based on UITableView and UICollectionView components from UIKit. These controllers can optionally be configured using HitsTableViewDataSource and HitsTableViewDelegate instances which are simplified, closure-based alternatives to the UITableViewDataSource and UITableViewDelegate protocols.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func configureTableController() {

  hitsInteractor.connectSearcher(searcher)
  hitsInteractor.connectController(hitsTableController)

  hitsTableController.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cellID")

  hitsTableController.dataSource = .init(cellConfigurator: { tableView, hit, indexPath in
    let cell = tableView.dequeueReusableCell(withIdentifier: "cellID", for: indexPath)
    cell.textLabel?.text = [String: Any](hit)?["title"] as? String
    return cell
  })

  hitsTableController.delegate = .init(clickHandler: { tableView, hit, indexPath in
    // action when a hit is selected
  })

}

Customization

HitsTableViewDataSource and HitsTableViewDelegate (as well as their UICollectionView equivalents) can be subclassed to provide a customized behavior.

If you prefer having more control over your UITableView or UICollectionView, you can implement and assign your own dataSource and delegates. HitsInteractor provides all the necessary methods to build your own dataSource:

  • numberOfHits() -> Int:
    number of hits stored in HitsInteractor

  • hit(atIndex index: Int) -> Record?:
    an object representing a hit for specified index

Customize your view

The controllers provided by default, like the HitsTableController or the HitsCollectionController work well when you want to use native UIKit with their default behavior.

If you want to use another component as a hits view, or want to introduce some custom behavior to the already provided UIKit component, you can create your own controller conforming to the HitsController protocol.

Protocol

var hitsSource: DataSource?:

Reference to an entity providing a list of hits.

func reload():

Function called when we require a reload of the hits view.

func scrollToTop():

Function called when we have to scroll to the top of the hits view.

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class HitsTableController<Source: HitsSource>: NSObject, InstantSearchCore.HitsController {

  public let tableView: UITableView
  public weak var hitsSource: Source?
  
  public init(tableView: UITableView) {
    self.tableView = tableView
  }
  
  public func reload() {
    tableView.reloadData()
  }
  
  public func scrollToTop() {
    guard tableView.numberOfRows(inSection: 0) != 0 else { return }
    let indexPath = IndexPath(row: 0, section: 0)
    self.tableView.scrollToRow(at: indexPath, at: .top, animated: false)
  }

}

Result metadata and Hit structure

Each hit returned by Algolia is enriched with search metadata, like highlightResult, objectID, and snippetResult. InstantSearch provides an easy way to parse these using the Hit wrapper structure. This is a generic structure that encapsulates the type of your record, and gives a strongly typed access to metadata of a hit.

For example, consider the following record structure:

1
2
3
4
5
6
7
8
9
10
11
12
struct Movie: Codable {
  let title: String
  let year: Int
}

/* An appropriate structure for representing a record in the following json format:
{
  "title": "Titanic",
  "year": 1997
}
*/

Conforming to the Codable protocol, it is ready to use with the HitsInteractor as follows:

1
let hitsInteractor = HitsInteractor<Movie>()

However, by doing this, all the metadata which comes with each hit will be ignored. To keep search metadata, wrap your record structure into the provided Hit structure.

1
let hitsInteractor = HitsInteractor<Hit<Movie>>()

You can still extract your Movie object by accessing the object field of Hit:

1
2
let movieHit: Hit<Movie> = hitsInteractor.hit(atIndex: ...)
let movie: Movie = movieHit.object

The Hit structure gives access to the following fields:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Hit identifier attributed by Algolia
let objectID: String

// Wrapped record object
let object: T

// Snippeted attributes.
let snippetResult: [String: SnippetResult]?

// Highlighted attributes. Each attribute contains an object or an array of objects (if the attribute in question is an array) with the following attributes.
let highlightResult: [String: [HighlightResult]]?

// Ranking information.
let rankingInfo: RankingInfo?

Did you find this page helpful?