API Reference / InstantSearch iOS Widgets / Hierarchical Menu
Widget signature
HierarchicalConnector(
  searcher: SingleIndexSearcher,
  filterState: FilterState,
  hierarchicalAttributes: [Attribute],
  separator: String,
  controller: HierarchicalController,
  presenter: HierarchicalPresenter
)

About this widget

Hierarchical Menu is a filtering component that displays a hierarchy of facets that lets your users refine the search results.

Examples

Instantiate a HierarchicalConnector and launch an initial search on its Searcher.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let searcher: SingleIndexSearcher = .init(appID: "YourApplicationID",
                                          apiKey: "YourSearchOnlyAPIKey",
                                          indexName: "YourIndexName")

let filterState: FilterState = .init()

searcher.connectFilterState(filterState)

let hierarchicalAttributes: [Attribute] = [
  "hierarchicalCategories.lvl0",
  "hierarchicalCategories.lvl1",
  "hierarchicalCategories.lvl2",
]

let hierarchicalTableViewController: HierarchicalTableViewController = .init(tableView: UITableView())
    
let hierachicalConnector: HierarchicalConnector = .init(searcher: searcher,
                                                        filterState: filterState,
                                                        hierarchicalAttributes: hierarchicalAttributes,
                                                        separator: " > ",
                                                        controller: hierarchicalTableViewController,
                                                        presenter: DefaultPresenter.Hierarchical.present)
searcher.search()

Parameters

searcher
type: SingleIndexSearcher
Required

The Searcher that handles your searches.

filterState
type: FilterState
Required

The FilterState that holds your filters.

hierarchicalAttributes
type: [Attribute]
Required

The names of the hierarchical attributes that we need to target, in ascending order.

separator
type: String
Required

The string separating the facets in the hierarchical facets. Usually something like “ > “. Note that you should not forget the spaces in between if there are some in your separator.

controller
type: HierarchicalController
default: nil
Optional

The Controller interfacing with a concrete hierarchical view.

presenter
type: HierarchicalPresenter
default: nil
Optional

The Presenter defining how hierarchical facets appears in the controller.

Presenter

Hierarchical Presenter
type: (([HierarchicalFacet]) -> [HierarchicalFacet])?
default: nil
Optional

The presenter that defines the way we want to display the list of HierarchicalFacet.

Takes a list of HierarchicalFacet as input and returns a new list of HierarchicalFacet.

A Hierarchical Facet is a tuple of a Facet, its level and whether it is selected or not.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static let present: HierarchicalPresenter = { facets in
    let levels = Set(facets.map { $0.level }).sorted()

    guard !levels.isEmpty else { return facets }

    var output: [HierarchicalFacet] = []

    output.reserveCapacity(facets.count)

    levels.forEach { level in
      let facetsForLevel = facets
        .filter { $0.level == level }
        .sorted { $0.facet.value < $1.facet.value }
      let indexToInsert = output
        .lastIndex { $0.isSelected }
        .flatMap { output.index(after: $0) } ?? output.endIndex
      output.insert(contentsOf: facetsForLevel, at: indexToInsert)
    }

    return output
  }

Low-level API

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

  • Searcher: The Searcher that handles your searches.
  • FilterState: The current state of the filters.
  • HierarchicalInteractor: The logic applied to the hierarchical facets.
  • HierarchicalController: The controller that interfaces with a concrete hierarchical facet list view.
  • HierarchicalPresenter: Optional. The presenter that controls the sorting and other settings of the refinement facet list view.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let searcher: SingleIndexSearcher = .init(appID: "YourApplicationID",
                                          apiKey: "YourSearchOnlyAPIKey",
                                          indexName: "YourIndexName")

let filterState: FilterState = .init()

let hierarchicalAttributes: [Attribute] = [
  "hierarchicalCategories.lvl0",
  "hierarchicalCategories.lvl1",
  "hierarchicalCategories.lvl2",
]

let hierarchicalTableViewController: HierarchicalTableViewController = .init(tableView: UITableView())

let hierarchicalInteractor: HierarchicalInteractor = .init(hierarchicalAttributes: hierarchicalAttributes,
                                                           separator: " > ")

searcher.connectFilterState(filterState)
hierarchicalInteractor.connectSearcher(searcher: searcher)
hierarchicalInteractor.connectFilterState(filterState)
hierarchicalInteractor.connectController(hierarchicalTableViewController)

searcher.search()

Customizing your view

The default controllers, e.g., HierarchicalTableViewController, work well when you want to use native UIKit with their default behavior.

If you want to use another component such as a UICollectionView, a third-party input view, or you want to introduce some custom behavior to the already provided UIKit component, you can create your own controller conforming to the HierarchicalController protocol.

Protocol

var onClick: ((String) -> Void)?:

Closure to call when a new hierarchical facet is clicked.

func setItem(_ item: [HierarchicalFacet])

Function called when a new array of hierarchical facets is updated. This is the UI State of the refinement list. Make sure to reload your view here when you get the new items.

Example

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
45
46
47
48
49
50
51
52
53
open class HierarchicalTableViewController: NSObject, HierarchicalController {

  public var onClick: ((String) -> Void)?
  var items: [HierarchicalFacet]
  var tableView: UITableView
  let cellID: String

  public init(tableView: UITableView, cellID: String = "HierarchicalFacet") {
    self.tableView = tableView
    self.items = []
    self.cellID = cellID

    super.init()

    tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellID)
    tableView.dataSource = self
    tableView.delegate = self
  }

  public func setItem(_ item: [HierarchicalFacet]) {

    self.items = item

    tableView.reloadData()
  }

}

extension HierarchicalTableViewController: UITableViewDataSource {

  open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return items.count
  }

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

    let maxSelectedLevel = Set(items.filter { $0.isSelected }.map { $0.level }).max() ?? 0
    let item = items[indexPath.row]
    cell.textLabel?.text = "\(item.facet.description)"
    cell.indentationLevel = item.level
    cell.accessoryType = item.level == maxSelectedLevel && item.isSelected ? .checkmark : .none
    return cell

  }
}

extension HierarchicalTableViewController: UITableViewDelegate {
  open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let item = items[indexPath.row]
    onClick?(item.facet.value)
  }
}

SwiftUI

InstantSearch provides the HierarchicalList SwiftUI view which you can embed in your views. It uses HierarchicalObservableController as a data model, which is an implementation of the HierarchicalController protocol adapted for usage with SwiftUI. HierarchicalObservableController must be connected to the HierarchicalConnector or HierarchicalInteractor like any other HierarchicalController implementation. You can define the appearance of the view representing a single hierarchical facet and its selection state or use the HierarchicalFacetRow view provided by InstantSearch.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct ContentView: View {

  @ObservedObject let hierarchicalController: HierarchicalObservableController

  var body: some View {
    HierarchicalList(hierarchicalController) { facet, nestingLevel, isSelected in
      // Use the implementation provided by InstantSearch
      // HierarchicalFacetRow(facet: facet,
                              nestingLevel: nestingLevel,
                              isSelected: isSelected)
      // Or declare a custom single hierarchical facet view
      HStack(spacing: 10) {
        Image(systemName: isSelected ? "chevron.down" : "chevron.right")
          .font(.callout)
        Text("\(facet.value) (\(facet.count))")
          .fontWeight(isSelected ? .semibold : .regular)
          .contentShape(Rectangle())
        Spacer()
      }
      .padding(.leading, CGFloat(nestingLevel * 20))
    }
  }

}

If you prefer to create a custom SwiftUI view that presents the list of hierarchical facets, you can directly use the HierarchicalObservableController as a data model. It provides the hierarchicalFacets property along with the toggle function to streamline the design process of your custom SwiftUI view.

Did you find this page helpful?