Guides / Building Search UI / Widgets

Create your own InstantSearch iOS widgets

As of May 1st, 2024, Apple requires all iOS apps to include a privacy manifest. Ensure you incorporate our provided privacy manifest files into your documentation. For more details, see Privacy Manifest.

On this page

If none of the existing widgets fit your use-case, you could implement your own widget.

You are trying to create your own widget with InstantSearch iOS and that’s awesome but that also means that you couldn’t find the widgets or built-in options you were looking for. Algolia would love to hear about your use case as the aim with the InstantSearch libraries is to provide the best out-of-the-box experience. Don’t hesitate to send a quick message explaining what you were trying to achieve either using the form at the end of that page or directly by submitting a feature request.

Creating a widget takes three steps:

  • Create the MyWidgetInteractor, containing the business logic for your widget.
  • Create a MyWidgetController interface, describing the rendering of the widget data.
    • Implement it in a MyConcreteWidgetController that you will use.
  • Create the connection methods between your Interactor and every other component:
    • Create a connectController() to connect your Interactor to its Controller.
    • If it uses the Searcher, a connectSearcher().
    • If it uses the FilterState, a connectFilterState().

Example

You will build a widget that displays the number of searches made since it was last clicked.

Create the interactor

The Interactor will be quite straightforward: it stores a sum that can be incremented or reseted to 0. You will use InstantSearch’s Observer to allow subscribing to changes of the sum’s value.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class SumSearchesInteractor {

  var sum: Int = 0 {
    didSet {
      onSumChanged.fire(sum)
    }
  }

  public let onSumChanged: Observer<Int> = .init()

  func increment() {
    sum += 1
  }

  func reset() {
    sum = 0
  }

}

Create the controller interface

To interact with the data in the ViewModel, you need a view than can display a number, and handle clicks to reset the counter.

1
2
3
4
protocol SumSearchesController {
    func setSum(sum: Int) // will be called on new sum
    var onReset: (() -> Void)? { get set } // will hold the callback to reset the sum
}

Implementing our Controller

We can now implement a SumSearchesButtonController: it should display the data received in setSum and trigger onReset when clicked.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class SumSearchesButtonController : SumSearchesController {

  let button: UIButton
  var onReset: (() -> Void)?

  init(button: UIButton) {
    self.button = button
    button.addTarget(self, action: #selector(didPressButton), for: .touchUpInside)
  }

  func setSum(sum: Int) {
    button.setTitle("\(sum)", for: .normal)
  }

  @objc func didPressButton() {
    onReset?()
  }

}

Create the connectController method

To link our Interactor and its Controller(s), we’ll define a connection method to describe what should happen when we connect them (subscribe to sum and set the reset callback).

You can do this in the Interactor’s extension.

1
2
3
4
5
6
7
8
9
10
11
12
13
extension SumSearchesInteractor {

  func connectController<Controller: SumSearchesController>(_ controller: Controller) {
    onSumChanged.subscribePast(with: self) { (interactor, sum) in
      controller.setSum(sum: sum)
    }

    controller.onReset = { [weak self] in
      self?.reset()
    }
  }

}

Create the connectSearcher method

Because the widget needs to be aware of searches to count them, it needs to be connected to a Searcher.

You’ll subscribe to the Searcher’s onResults, and call increment() on every new search response.

1
2
3
4
5
6
7
8
9
extension SumSearchesInteractor {

  func connectSearcher(_ searcher: HitsSearcher) {
    searcher.onResults.subscribe(with: self) { (interactor, _) in
      interactor.increment()
    }
  }

}

Final touches

You just created your first custom widget and can now use it in your application, like any other widget:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

// Initialize your Searcher as usual
let searcher = HitsSearcher(appID: "YourApplicationID",
                        apiKey: "YourSearchOnlyAPIKey",
                        indexName: "YourIndexName")


// Create your Interactor and Controller implementation
let sumSearchesInteractor = SumSearchesInteractor()
let sumSearchesButton = UIButton()
let sumSearchesButtonController = SumSearchesButtonController(button: sumSearchesButton)

// Connect your Interactor to start displaying the count of searches
sumSearchesInteractor.connectSearcher(searcher)
sumSearchesInteractor.connectController(sumSearchesButtonController)

Did you find this page helpful?