UI libraries / InstantSearch iOS / Widgets
Signature
CurrentFiltersConnector(
  filterState: FilterState,
  groupIDs: Set<FilterGroup.ID>?,
  interactor: CurrentFiltersInteractor,
  controller: CurrentFiltersController,
  presenter: Presenter<Filter, Output>
)

About this widget

Shows the currently active filters within a given FilterState and lets users remove filters individually.

Examples

Instantiate a CurrentFiltersConnector

1
2
3
4
5
6
let filterState = FilterState()
let groupID: FilterGroup.ID = .and(name: "color")
let currenfFiltersTableController: CurrentFilterListTableController = .init(tableView: UITableView())
let currentFiltersConnector = CurrentFiltersConnector(filterState: filterState,
                                                      groupIDs: [groupID],
                                                      controller: currenfFiltersTableController)

Parameters

Parameter Description
filterState
type: FilterState
Required

The FilterState that holds your filters.

groupIDs
type: Set<FilterGroup.ID>?
default: nil
Required

When specified, only display current filters matching these filter group ids.

interactor
type: CurrentFiltersInteractor
default: .init()
Required

The logic applied to the current filters.

controller
type: CurrentFiltersController
default: nil
Optional

The Controller interfacing with a concrete current filters view.

presenter
type: Presenter<Filter, Output>
default: DefaultPresenter.Filter.present
Optional

The Presenter defining how filters appears in the controller.

Presenter

Parameter Description
Filter Presenter
type: (Filter) -> String
Optional

The presenter that defines the way you want to display a filter.

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
currentFiltersInteractor.connectController(currentFiltersController, customPresenter)

    let customPresenter: (Filter) -> String = { filter in
      let attributeName = filter.filter.attribute.name

      switch filter {
      case .facet(let facetFilter):
        switch facetFilter.value {
        case .bool:
          return filter.filter.attribute.name

        case .float(let floatValue):
          return "\(attributeName): \(floatValue)"

        case .string(let stringValue):
          return stringValue
        }

      case .numeric(let numericFilter):

        switch numericFilter.value {
        case .comparison(let comp):
          return "\(attributeName) \(comp.0) \(comp.1)"

        case .range(let range):
          return "\(attributeName): \(range.lowerBound) to \(range.upperBound)"
        }

      case .tag(let tagFilter):
        return tagFilter.value
      }
    }

Low-level API

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

  • CurrentFiltersInteractor: The logic for current filters in the FilterState.
  • FilterState: The current state of the filters.
  • CurrentFiltersController: The controller that interfaces with a concrete current filter view.
  • Presenter: Optional. The presenter that defines the way you want to display a filter.
1
2
3
4
5
6
let filterState = FilterState()
let groupID: FilterGroup.ID = .and(name: "color")
let currentFiltersTableController: CurrentFilterListTableController = .init(tableView: UITableView())
let currentFiltersInteractor = CurrentFiltersInteractor()
currentFiltersInteractor.connectFilterState(filterState, filterGroupID: groupID)
currentFiltersInteractor.connectController(currentFiltersTableController)  

Customizing your view

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

If you want to use another component (other than a UITableView) 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 CurrentFiltersController protocol.

Protocol

func setItems(_ item: [FilterAndID]):

Function called when current filters are refreshed and need to be updated.

Note that FilterAndID is a struct that contains the filter, its ID, and the text representation of the filter

var onRemoveItem: ((FilterAndID) -> Void)?:

Closure to call when a “remove filter” intention is detected on the corresponding current filter.

func reload():

Function called when the view needs to reload itself with new data.

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
open class CurrentFilterListTableController: NSObject, CurrentFiltersController, UITableViewDataSource, UITableViewDelegate {

  open var onRemoveItem: ((FilterAndID) -> Void)?

  public let tableView: UITableView

  public var items: [FilterAndID] = []

  private let cellIdentifier = "CurrentFilterListTableControllerCellID"

  public init(tableView: UITableView) {
    self.tableView = tableView
    super.init()
    tableView.dataSource = self
    tableView.delegate = self
    tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellIdentifier)
  }

  open func setItems(_ item: [FilterAndID]) {
    items = item
  }

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

  // MARK: - 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: cellIdentifier, for: indexPath)
    let filterAndID = items[indexPath.row]
    cell.textLabel?.text = filterAndID.text

    return cell
  }

  // MARK: - UITableViewDelegate

  open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    onRemoveItem?(items[indexPath.row])
  }

}

SwiftUI

InstantSearch provides the CurrentFiltersObservableController data model, which is an implementation of the CurrentFiltersController protocol adapted for usage with SwiftUI. CurrentFiltersObservableController must be connected to the CurrentFiltersConnector or CurrentFiltersConnector like any other CurrentFiltersController implementation.

The example of the current filters view presenting the grouped filters.

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
struct ContentView: View {
  
  @ObservedObject var currentFiltersController: CurrentFiltersObservableController
  
  var body: some View {
    VStack {
      Text("Filters")
        .font(.title)
      let filtersPerGroup = Dictionary(grouping: currentFiltersController.filters) { el in
        el.id
      }
      .mapValues { $0.map(\.filter) }
      .map { $0 }
      ForEach(filtersPerGroup, id: \.key) { (group, filters) in
        HStack {
          Text(group.description)
            .bold()
            .padding(.leading, 5)
          Spacer()
        }
        .padding(.vertical, 5)
        .background(Color(.systemGray5))
        ForEach(filters, id: \.self) { filter in
          HStack {
            Text(filter.description)
              .padding(.leading, 5)
            Spacer()
          }
        }
      }
      Spacer()
    }
  }
  
}

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

Did you find this page helpful?