API Reference / InstantSearch iOS Widgets / Multi Hits

Multi Hits

Widget signature
MultiIndexHitsConnector(
  appID: ApplicationID,
  apiKey: APIKey,
  indexModules: [IndexModule],
  controller: MultiIndexHitsController
)

About this widget

MultiHits manages and displays a paginated list of search results from multiple indices. It performs multiple search requests simultaneously and enables some features of a good search experience like Query Suggestions.

This is useful if you want to show all the search results from different indices in one view. Otherwise, you can keep references to HitsInteractors tied to individual indices and create a separate HitsController for each of them.

Examples

Instantiate a HitsInteractor for each index parametrized with a Codable type representing the hit in the index.

1
2
3
4
5
6
7
8
9
10
struct Actor: Codable {
  ...
}

struct Movie: Codable {
  ...
}

let actorHitsInteractor: HitsInteractor<Actor> = .init(infiniteScrolling: .off)
let movieHitsInteractor: HitsInteractor<Movie> = .init(infiniteScrolling: .off)

Then, you can initialize a MultiIndexHitsConnector, providing your application ID and search API key and instantiating an IndexModule for each index. You can also provide an optional FilterState while instantiating an IndexModule.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let actorHitsInteractor: HitsInteractor<Actor> = .init(infiniteScrolling: .off)
let movieHitsInteractor: HitsInteractor<Movie> = .init(infiniteScrolling: .off)

let movieFilterState: FilterState = .init()
let hitsTableViewController = MultiIndexHitsTableController(tableView: .init())

let multiIndexHitsConnector = MultiIndexHitsConnector(appID: "YourApplicationID",
                                                      apiKey: "YourSearchOnlyAPIKey",
                                                      indexModules: [
                                                        .init(indexName: "actors",
                                                              hitsInteractor: actorHitsInteractor),
                                                        .init(indexName: "movies",
                                                              hitsInteractor: movieHitsInteractor,
                                                              filterState: movieFilterState)
                                                      ],
                                                      controller: hitsTableViewController)  

Parameters

appID
type: ApplicationID
Required

The ID of your application.

apiKey
type: APIKey
Required

Your application API Key. Be sure to use your Search-only API key.

indexModules
type: [IndexModule]
Required

The list of components representing the single index, containing its name, hits interactor and an optional filter state.

searcher
type: MultiIndexSearcher
Required

The Searcher that handles your searches.

interactor
type: MultIndexHitsInteractor
Required

The logic applied to the hits.

filterStates
type: [FilterState?]
Optional

The list of FilterStates that will hold your filters separately for each index.

controller
type: MultiIndexHitsController
default: nil
Optional

The Controller interfacing with a concrete multi-index hits view.

Low-level API

If you want to fully control the MultiHits components and connect them manually, you can use the following components:

  • MultiIndexSearcher: The Searcher that handles your searches.
  • MultiIndexHitsInteractor: The logic applied to the multi-index search results.
  • MultiIndexHitsController: The controller that interfaces with a concrete multi-index hits view.

Example

Instantiate MultiIndexSearcher with your application ID, search API key, and list of index names.

1
2
3
  let searcher: MultiIndexSearcher = .init(appID: "YourApplicationID",
                                           apiKey: "YourSearchOnlyAPIKey",
                                           indexNames: ["actors", "movies"])

Instantiate a HitsInteractor for each index.

1
2
  let actorHitsInteractor: HitsInteractor<Actor> = .init(infiniteScrolling: .off)
  let movieHitsInteractor: HitsInteractor<Movie> = .init(infiniteScrolling: .off)

Instantiate the MultiIndexHitsInteractor providing a list of the previously instantiated HitsInteractors as a parameter.

When instantiating a MultiIndexSearcher, the position of an index in the list of indices must match the position of the corresponding HitsInteractor in the list used when instantiating the MultiIndexInteractor parameter.

1
  let hitsInteractor: MultiIndexHitsInteractor = .init(hitsInteractors: [actorHitsInteractor, movieHitsInteractor])

Connect the MultiIndexHitsInteractor with the MultiIndexSearcher using the connectSearcher method.

1
  hitsInteractor.connectSearcher(searcher)

Instantiate the MultiIndexHitsController implementation and connect it with the MultiIndexHitsInteractor using the connectController method.

1
2
  let hitsTableViewController = MultiIndexHitsTableController(tableView: .init())
  hitsInteractor.connectController(hitsTableViewController)

If you need a FilterState for an index, instantiate and connect it with a concrete HitsInteractor and MultiIndexSearcher using the position of the index in list of indices you used when instantiating the MultiIndexSearcher

1
2
3
  let movieFilterState: FilterState = .init()
  movieHitsInteractor.connectFilterState(movieFilterState)
  searcher.connectFilterState(movieFilterState, withQueryAtIndex: 1)

Now, each time you launch a new search:

  • The Searcher receives new results and transmit them to HitsInteractor
  • The HitsInteractor parses search results and notifies HitsController
  • The HitsController refreshes the view presenting the hits

## Connecting separate Controllers

To display each index’s hits in a different Controller, you can use theHitsTableViewController or the HitsCollectionViewController described in the Hits guide. Using these Controllers, you can connect each index’s hits to its own HitsInteractors.

1
2
3
4
5
6
7
8
9
  let actorHitsInteractor: HitsInteractor<Actor> = .init(infiniteScrolling: .off)
  let actorsTableViewController = HitsTableViewController<ActorCellConfigurator>()
  actorHitsInteractor.connectController(actorsTableViewController)

  let movieHitsInteractor: HitsInteractor<Movie> = .init(infiniteScrolling: .off)
  let moviesTableViewController = HitsTableViewController<MovieCellConfigurator>()
  movieHitsInteractor.connectController(moviesTableViewController)
  
  let hitsInteractor: MultiIndexHitsInteractor = .init(hitsInteractors: [actorHitsInteractor, movieHitsInteractor])

You can connect your own implementation of the HitsController protocol for Hits.

## Customizing your view

The default controllers provided, e.g. HitsTableViewController and HitsCollectionViewController, work well when you want to use native UIKit components with their default behavior. They are not suitable if you want to present the hits from the different indices in the same view, for example in different sections of one UITableView. For this use case, you must create your own controller conforming to the MultiIndexHitsController protocol.

### Protocol

var hitsSource: MultiIndexHitsSource?:

Reference to an entity providing a list of hits per section.

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
21
  public class MultiIndexHitsTableController: NSObject, MultiIndexHitsController {

    public let tableView: UITableView

    public weak var hitsSource: MultiIndexHitsSource?

    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)
    }

  }

Did you find this page helpful?