UI libraries / InstantSearch iOS / Widgets
Signature
FacetListConnector(
  searcher: SingleIndexSearcher,
  filterState: FilterState,
  attribute: Attribute,
  selectionMode: SelectionMode,
  facets: [Facet],
  operator: RefinementOperator,
  groupName: String,
  controller: FacetListController,
  presenter: FacetListPresenter
)

About this widget

RefinementList is a filtering view that displays facets, and lets users refine the search results by filtering on specific values.

Requirements

You must include the attribute provided to the widget in attributesForFaceting, either using the dashboard or using attributesForFaceting with the API.

Examples

Instantiate a FacetListConnector 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
let searcher = HitsSearcher(appID: "YourApplicationID",
                            apiKey: "YourSearchOnlyAPIKey",
                            indexName: "YourIndexName")

let filterState: FilterState = .init()

let categoryTableViewController: UITableViewController = .init()
let categoryListController: FacetListTableController = .init(tableView: categoryTableViewController.tableView)
let facetListPresenter: FacetListPresenter = .init(sortBy: [.count(order: .descending)], limit: 5, showEmptyFacets: false)

let categoryConnector: FacetListConnector = .init(searcher: searcher,
                                                  filterState: filterState,
                                                  attribute: "category",
                                                  selectionMode: .multiple,
                                                  facets: [.init(value: "initial facet", count: 10)],
                                                  operator: .and,
                                                  controller: categoryListController,
                                                  presenter: facetListPresenter)

searcher.search()

Parameters

Parameter Description
searcher
type: HitsSearcher
Required

The Searcher that handles your searches.

filterState
type: FilterState
Required

The FilterState that holds your filters.

attribute
type: Attribute
Required

The attribute to filter.

selectionMode
type: SelectionMode
default: .multiple
Optional

Whether a user can select .single or .multiple values.

facets
type: [Facet]
default: []

If specified, the default facet value(s) to apply.

operator
type: RefinementOperator
Required

Whether we apply an and or or behavior to the facets in the filterState.

For example if we select color as the attribute and an or behavior,
the filter sent to Algolia will be color:red OR color:green.

groupName
type: String
default: The raw value of the `attribute` parameter
Optional

Filter group name.

controller
type: FacetListController
default: nil
Optional

Controller interfacing with a concrete facet list view.

presenter
type: SelectableListPresentable
default: nil
Optional

Presenter defining how a facet appears in the controller.

Presenter

Parameter Description
sortBy
type: [FacetSortCriterion]
default: [.count(order: .descending)]
Optional

How to sort facets. Must be one or more of the following values:

  • .count(order: .descending)
  • .count(order: .ascending)
  • .alphabetical(order: .descending)
  • .alphabetical(order: .ascending)
  • .isRefined
1
2
3
4
5
6
7
// Tie breaking algorithm where we show refined values first.
// In case of a tie, we show the facets that have the biggest counts.
// In case of a tie, we show the facets in alphabetical order.
facetListPresenter =
    FacetListPresenter(
      sortBy: [.isRefined, .count(order: .descending), .alphabetical(order: .ascending)]
    )
limit
type: Int
default: 10
Optional

The number of facet values to retrieve.

1
facetListPresenter =  .init(limit: 5)
showEmptyFacets
type: Bool
default: true
Optional

Whether we show facets that have a facet count of 0.

1
2
facetListPresenter =  .init(showEmptyFacets: false)
facetListPresenter =  .init(showEmptyFacets: true)

Low-level API

If you want to fully control the RefinementList 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.
  • FacetListInteractor: The logic applied to the facets.
  • FacetListController: The controller that interfaces with a concrete facet list view.
  • FacetListPresenter: Optional. The presenter that controls the sorting and other settings of the facet list view.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let searcher = HitsSearcher(appID: "YourApplicationID",
                            apiKey: "YourSearchOnlyAPIKey",
                            indexName: "YourIndexName")
let filterState: FilterState = .init()

let facetListInteractor: FacetListInteractor  = .init(facets: [.init(value: "initial facet", count: 10)], selectionMode: .multiple)

let categoryTableViewController: UITableViewController = .init()
let categoryListController: FacetListTableController = .init(tableView: categoryTableViewController.tableView)
let facetListPresenter: FacetListPresenter = .init(sortBy: [.count(order: .descending)], limit: 5, showEmptyFacets: false)

facetListInteractor.connectSearcher(searcher, with: "category")
facetListInteractor.connectFilterState(filterState, with: "category", operator: .or)
facetListInteractor.connectController(categoryListController)
facetListInteractor.connectController(categoryListController, with: facetListPresenter)
searcher.connectFilterState(filterState)

searcher.search()

Customizing your view

The controllers provided by default, like the FacetListTableViewController 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 FacetListController protocol.

Protocol

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

Closure to call when a new facet is clicked.

func setSelectableItems(selectableItems: [SelectableItem<Facet>])

Function called when a new array of selectable facets is updated. This is the UI State of the refinement list.

func reload()

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

Implementation 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
54
55
56
57
58
59
60
61
62
open class FacetListTableViewController: NSObject, FacetListController {

  public var onClick: ((Facet) -> Void)?

  public var tableView: UITableView

  var selectableItems: [SelectableItem<Facet>] = []
  var cellID: String

  public init(tableView: UITableView, cellID: String = "FacetList") {
    self.tableView = tableView
    self.cellID = cellID
    super.init()
    tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellID)
    tableView.dataSource = self
    tableView.delegate = self
  }

  public func setSelectableItems(selectableItems: [SelectableItem<Facet>]) {
    self.selectableItems = selectableItems
  }

  public func reload() {
    tableView.reloadData()
  }

}

extension FacetListTableViewController: UITableViewDataSource {

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

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

    let selectableRefinement: SelectableItem<Facet> = selectableItems[indexPath.row]

    let facetAttributedString = NSMutableAttributedString(string: selectableRefinement.item.value)
    let facetCountStringColor = [NSAttributedString.Key.foregroundColor: UIColor.gray, .font: UIFont.systemFont(ofSize: 14)]
    let facetCountString = NSAttributedString(string: " (\(selectableRefinement.item.count))", attributes: facetCountStringColor)
    facetAttributedString.append(facetCountString)

    cell.textLabel?.attributedText = facetAttributedString

    cell.accessoryType = selectableRefinement.isSelected ? .checkmark : .none

    return cell
  }

}

extension FacetListTableViewController: UITableViewDelegate {

  open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let selectableItem = selectableItems[indexPath.row]

    self.onClick?(selectableItem.item)
  }

}

SwiftUI

InstantSearch provides the FacetList SwiftUI view which you can embed in your views. It uses FacetListObservableController as a data model, which is an implementation of the FacetListController protocol adapted for usage with SwiftUI. FacetListObservableController must be connected to the FacetListConnector or FacetListInteractor, like any other FacetListController implementation. You can define the appearance of the view representing a single facet and its selection state or use the FacetRow` 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 var facetListController: FacetListObservableController

  var body: some View {
    FacetList(facetListController) { facet, isSelected in
      // Use the implementation provided by InstantSearch
      // FacetRow(facet: facet, 
                  isSelected: isSelected)
      // Or declare a custom single facet view
      HStack {
        Text(facet.value)
        Spacer()
        if isSelected {
          Image(systemName: "checkmark")
            .foregroundColor(.accentColor)
        }
      }
      .contentShape(Rectangle())
      .frame(idealHeight: 44)
      .padding(.horizontal, 5)
    }
  }
}

If you prefer to create a custom SwiftUI view that presents the list of facets, you can directly use the FacetListObservableController as a data model. It provides facets and selections properties along with convenient toggle and isSelected functions to streamline the design process of your custom SwiftUI view.

Did you find this page helpful?