Concepts / Building Search UI / Getting started programmatically
Aug. 21, 2019

Getting Started Programmatically

Welcome to InstantSearch iOS

In this guide, we will walk through the few steps needed to start a project with InstantSearch iOS. We will start from an empty iOS project, and create a full search experience from scratch!

Before we start

To use InstantSearch iOS, you need an Algolia account. You can create a new account, or use the following credentials:

  • APP ID: latency
  • Search API Key: 1f6fd3a6fb973cb08419fe7d288fa4db
  • Index name: bestbuy

These credentials give access to a preloaded dataset of products appropriate for this guide.

Create a new project

Let’s get started! In Xcode, create a new Project:

  • On the Template screen, select Single View Application and click next
  • Specify your Product name, select Swift as the language and iPhone as the Device, and then create.

Xcode newproject

We will use CocoaPods for adding the dependency to InstantSearch.

  • If you don’t have CocoaPods installed on your machine, open your terminal and run sudo gem install cocoapods.
  • Go to the root of your project then type pod init. A Podfile will be created for you.
  • Open your Podfile and add pod 'InstantSearch', '~> 4.0' below your target.
  • On your terminal, run pod update.
  • Close your Xcode project, and then, at the root of your project, type open projectName.xcworkspace (replacing projectName with the actual name of your project).

Initialize your searcher

The central part of our search experience is the Searcher. The Searcher performs search requests and obtains search results. Almost all InstantSearch components are connected with the Searcher. In this tutorial we only target one index, so we will instantiate a SearcherSingleIndex with the proper credentials.

  • Open the ViewController.swift file, and add import InstantSearch at the top.
  • Below your class definition, declare and instantiate your searcher.
1
2
3
let searcher = SingleIndexSearcher(appID: "latency",
                                   apiKey: "1f6fd3a6fb973cb08419fe7d288fa4db",
                                   indexName: "bestbuy")

InstantSearch iOS provides a set of single-responsibility components. These allow you to construct a flexible search experience perfectly satisfying your needs.
Let’s add the SearchBox to your application, since every search experience requires one.

The SearchBox component

To create the SearchBox, you must declare a QueryInputInteractor and SearchBarController.

  • SearchBarController updates the state and captures the query text of UISearchBar.
  • QueryInputInteractor launches a new search each time the controller triggers it.
  • Declare and instantiate them in your ViewController class below the Searcher.
1
2
3
4
5
6
let searcher: SingleIndexSearcher = SingleIndexSearcher(appID: "latency",
                                                        apiKey: "1f6fd3a6fb973cb08419fe7d288fa4db",
                                                        indexName: "bestbuy")

let queryInputInteractor: QueryInputInteractor = .init()
let searchBarController: SearchBarController = .init(searchBar: UISearchBar())

Now, let’s add the Stats component to show how the number of results changes when you type a query in your SearchBox. Similarly to the SearchBox, it consists of two components: StatsInteractor and LabelStatsController.

  • LabelStatsController updates the text of the UILabel once new search stats information is received.
  • StatsInteractor picks search stats information from our search result, and forwards it to the LabelStatsController .
  • Declare and instantiate them after our previous declarations:
1
2
3
4
5
6
7
8
9
10
  let searcher: SingleIndexSearcher = SingleIndexSearcher(appID: "latency",
                                                          apiKey: "1f6fd3a6fb973cb08419fe7d288fa4db",
                                                          indexName: "bestbuy")

  let queryInputInteractor: QueryInputInteractor = .init()
  let searchBarController: SearchBarController = .init(searchBar: UISearchBar())

  let statsInteractor: StatsInteractor = .init()
  let statsController: LabelStatsController = .init(label: UILabel())
}
  • Declare the setup() method, connecting all components together.
  • Call the setup() method inside your viewDidLoad method:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
override func viewDidLoad() {
    super.viewDidLoad()
    setup()
}

func setup() {
    // Notify searcher about query text changes and launch a new search
    queryInputInteractor.connectSearcher(searcher)

    // Update query text in interactor each time query text changed in a UISearchBar
    queryInputInteractor.connectController(searchBarController)

    // Update search stats each time searcher receives new search results
    statsInteractor.connectSearcher(searcher)

    // Update label with up-to-date stats data
    statsInteractor.connectController(statsController)

    // Launch initial search
    searcher.search()
}
  • Finally, we need to add the views to the ViewController’s view, and specify the autolayout constraints so that the layout looks good on any device. You don’t have to focus too much on understanding this part since it is not related to InstantSearch, but related to iOS layout. Add the configureUI function to your file and call it from viewDidLoad:
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
override func viewDidLoad() {
    super.viewDidLoad()
    setup()
    configureUI()
}

func configureUI() {
    
    view.backgroundColor = .white
    
    // Declare a stack view containing all the components
    let stackView = UIStackView()
    stackView.translatesAutoresizingMaskIntoConstraints = false
    stackView.spacing = 16
    stackView.axis = .vertical
    stackView.layoutMargins = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 0)
    stackView.isLayoutMarginsRelativeArrangement = true
    
    // Add searchBar
    let searchBar = searchBarController.searchBar
    searchBar.translatesAutoresizingMaskIntoConstraints = false
    searchBar.heightAnchor.constraint(equalToConstant: 40).isActive = true
    searchBar.searchBarStyle = .minimal
    stackView.addArrangedSubview(searchBar)
    
    // Add statsLabel
    let statsLabel = statsController.label
    statsLabel.translatesAutoresizingMaskIntoConstraints = false
    statsLabel.heightAnchor.constraint(equalToConstant: 16).isActive = true
    stackView.addArrangedSubview(statsLabel)

    stackView.addArrangedSubview(UIView())

    // Pin stackView to ViewController's view
    view.addSubview(stackView)

    NSLayoutConstraint.activate([
      stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
      stackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
      stackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
      stackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
    ])

}

You can now build and run your application: you now have a basic search experience! You should see that the results are changing on each key stroke.

Recap

You just used your first InstantSearch components. In this part, you’ve learned:

  • How to create a Searcher
  • How to create a SearchBox
  • How to create a Stats
  • How to connect all these components together

Display your data: Hits

The whole point of a search experience is to display the dataset that best matches the query entered by the user. We will implement this by making use of the Hits component.

Guide hits

Hits consists of two components: HitsInteractor and HitsTableController.

  • HitsTableController updates the content of UITableView once it receives new search results.
  • HitsInteractor extracts the results from the search response, and provides them to the HitsTableController.
  • Declare and instantiate these components after our previous declarations as followed:
1
2
3
4
5
6
7
8
9
10
11
12
let searcher: SingleIndexSearcher = SingleIndexSearcher(appID: "latency",
                                                        apiKey: "1f6fd3a6fb973cb08419fe7d288fa4db",
                                                        indexName: "bestbuy")

let queryInputInteractor: QueryInputInteractor = .init()
let searchBarController: SearchBarController = .init(searchBar: UISearchBar())

let statsInteractor: StatsInteractor = .init()
let statsController: LabelStatsController = .init(label: UILabel())

let hitsInteractor: HitsInteractor<JSON> = .init()
let hitsTableController: HitsTableController<HitsInteractor<JSON>> = .init(tableView: UITableView())
  • Connect the Hits components with each other and with the Searcher in the setup method:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func setup() {
    // Notify searcher about query text changes and launch a new search
    queryInputInteractor.connectSearcher(searcher)

    // Update query text in interactor each time query text changed in a UISearchBar
    queryInputInteractor.connectController(searchBarController)

    // Update search stats each time searcher receives new search results
    statsInteractor.connectSearcher(searcher)

    // Update label with up-to-date stats data
    statsInteractor.connectController(statsController)

    // Update hitsInteractor with up-to-date search results
    hitsInteractor.connectSearcher(searcher)

    // Dispatch search results to tableView
    hitsInteractor.connectController(hitsTableController)

    // Launch initial search
    searcher.search()
}

Now that we have our tableView set up, we need to specify which fields from the search response we want to show. To do this:

  • Register UITableViewCell in tableView in classical way
  • Use the dataSource in the hitsTableController, providing a closure-based way to de-queue your cell and set it up with a search result hit.
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
func setup() {
    // Notify searcher about query text changes and launch a new search
    queryInputInteractor.connectSearcher(searcher)

    // Update query text in interactor each time query text changed in a UISearchBar
    queryInputInteractor.connectController(searchBarController)

    // Update search stats each time searcher receives new search results
    statsInteractor.connectSearcher(searcher)

    // Update label with up-to-date stats data
    statsInteractor.connectController(statsController)

    // Update hitsInteractor with up-to-date search results
    hitsInteractor.connectSearcher(searcher)

    // Dispatch search results to tableView
    hitsInteractor.connectController(hitsTableController)

    // Register cell in tableView
    hitsTableController.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cellID")
    
    // Dequeue and setup cell with hit data
    hitsTableController.dataSource = .init() { tableView, hit, indexPath in
        let cell = tableView.dequeueReusableCell(withIdentifier: "cellID", for: indexPath)
        cell.textLabel?.text = [String: Any](hit)?["name"] as? String
        return cell
    }

    // Launch initial search
    searcher.search()
}

Here we extract the name of the product from the corresponding JSON record and assign it to the text property of the cell’s textLabel.

  • Then we will add the table to a stackView of our ViewController.
  • Replace your configureUI method with the following (again, no need to worry about understanding this part)
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
func configureUI() {

    view.backgroundColor = .white

    // Declare a stack view containing all the components
    let stackView = UIStackView()
    stackView.translatesAutoresizingMaskIntoConstraints = false
    stackView.spacing = 16
    stackView.axis = .vertical
    stackView.layoutMargins = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 0)
    stackView.isLayoutMarginsRelativeArrangement = true
    
    // Add searchBar
    let searchBar = searchBarController.searchBar
    searchBar.translatesAutoresizingMaskIntoConstraints = false
    searchBar.heightAnchor.constraint(equalToConstant: 40).isActive = true
    searchBar.searchBarStyle = .minimal
    stackView.addArrangedSubview(searchBar)
    
    // Add statsLabel
    let statsLabel = statsController.label
    statsLabel.translatesAutoresizingMaskIntoConstraints = false
    statsLabel.heightAnchor.constraint(equalToConstant: 16).isActive = true
    stackView.addArrangedSubview(statsLabel)
    
    // Add hits tableView
    stackView.addArrangedSubview(hitsTableController.tableView)

    // Pin stackView to ViewController's view
    view.addSubview(stackView)

    NSLayoutConstraint.activate([
      stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
      stackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
      stackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
      stackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
    ])

}

You can now build and run your application: you now have a more advanced search experience! With this in place, you can also enable infinite scrolling in your example by setting the parameter to true.

In this part, you learned:

  • How to build your interface with InstantSearch components by adding the Hits component.

Filter your results: RefinementList

With your app, you can search more than 10000 products. However, you don’t want to scroll to the bottom of the list to find the exact product you’re looking for. We can more accurately filter our results by making use of the RefinementList components. We’ll build a filter that allows us to filter products by their category.

First of all, add a FilterState. These components provide a convenient way to manage the state of your filters. In our case, we will add one refinement attribute: category. Finally we have to add the RefinementList components to other components in our search experience, such as SelectableFacetInteractor, FacetListTableController and UITableViewController. The UITableViewController which will actually present a facet list. As a result, property definitions of your ViewController must look like this:

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

let queryInputInteractor: QueryInputInteractor = .init()
let searchBarController: SearchBarController = .init(searchBar: UISearchBar())

let statsInteractor: StatsInteractor = .init()
let statsController: LabelStatsController = .init(label: UILabel())

let hitsInteractor: HitsInteractor<JSON> = .init()
let hitsTableController: HitsTableController<HitsInteractor<JSON>> = .init(tableView: UITableView())

let categoryAttribute: Attribute = "category"
let filterState: FilterState = .init()

let categoryInteractor: FacetListInteractor = .init(selectionMode: .single)
let categoryTableViewController: UITableViewController = .init()
lazy var categoryListController: FacetListTableController = {
  return .init(tableView: categoryTableViewController.tableView, titleDescriptor: .none)
}()

To make it all work together, we must connect the new components. Searcher must be connected with FilterState in order to modify the search query and relaunch a search once the filters have changed. HitsInteractor must be connected to the FilterState too in order to be notified when the query changed so it can invalidate cached hits. Finally, we connect RefinementList components with each other and with the FilterState, through which it will communicate with a Searcher. Update your setup function as followed:

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
func setup() {
  searcher.connectFilterState(filterState)
  
  queryInputInteractor.connectSearcher(searcher)
  queryInputInteractor.connectController(searchBarController)
  
  statsInteractor.connectSearcher(searcher)
  statsInteractor.connectController(statsController)
  
  hitsInteractor.connectSearcher(searcher)
  hitsInteractor.connectController(hitsTableController)
  hitsInteractor.connectFilterState(filterState)

  hitsTableController.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cellID")
  hitsTableController.dataSource = .init(cellConfigurator: { tableView, hit, indexPath in
    let cell = tableView.dequeueReusableCell(withIdentifier: "cellID", for: indexPath)
    cell.textLabel?.text = [String: Any](hit)?["name"] as? String
    return cell
  })
  
  categoryInteractor.connectSearcher(searcher, with: categoryAttribute)
  categoryInteractor.connectFilterState(filterState, with: categoryAttribute, operator: .and)
  categoryInteractor.connectController(categoryListController, with: FacetListPresenter(sortBy: [.isRefined]))
  
  searcher.search()
}

Replace your configureUI method with the following (again, no need to worry about understanding this part):

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
func configureUI() {

  view.backgroundColor = .white
  
  let stackView = UIStackView()
  stackView.translatesAutoresizingMaskIntoConstraints = false
  stackView.spacing = 16
  stackView.axis = .vertical
  stackView.layoutMargins = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 0)
  stackView.isLayoutMarginsRelativeArrangement = true
  
  let searchBar = searchBarController.searchBar
  searchBar.translatesAutoresizingMaskIntoConstraints = false
  searchBar.heightAnchor.constraint(equalToConstant: 40).isActive = true
  searchBar.searchBarStyle = .minimal

  let filterButton = UIButton()
  filterButton.setTitleColor(.black, for: .normal)
  filterButton.setTitle("Filter", for: .normal)
  filterButton.addTarget(self, action: #selector(showFilters), for: .touchUpInside)
  
  let searchBarFilterButtonStackView = UIStackView()
  searchBarFilterButtonStackView.translatesAutoresizingMaskIntoConstraints = false
  searchBarFilterButtonStackView.spacing = 4
  searchBarFilterButtonStackView.axis = .horizontal
  searchBarFilterButtonStackView.addArrangedSubview(searchBar)
  searchBarFilterButtonStackView.addArrangedSubview(filterButton)
  let spacer = UIView()
  spacer.widthAnchor.constraint(equalToConstant: 4).isActive = true
  searchBarFilterButtonStackView.addArrangedSubview(spacer)
  
  stackView.addArrangedSubview(searchBarFilterButtonStackView)
  
  let statsLabel = statsController.label
  statsLabel.translatesAutoresizingMaskIntoConstraints = false
  statsLabel.heightAnchor.constraint(equalToConstant: 16).isActive = true
  stackView.addArrangedSubview(statsLabel)

  stackView.addArrangedSubview(hitsTableController.tableView)
  
  view.addSubview(stackView)
  
  NSLayoutConstraint.activate([
    stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
    stackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
    stackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
    stackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
    ])
  
  categoryTableViewController.title = "Category"
  categoryTableViewController.view.backgroundColor = .white
  
}

Finally, add these functions in the end of you ViewController. They are responsible for presenting and dismissing your RefinementList.

1
2
3
4
5
6
7
8
9
  @objc func showFilters() {
    let navigationController = UINavigationController(rootViewController: categoryTableViewController)
    categoryTableViewController.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissFilters))
    present(navigationController, animated: true, completion: .none)
  }
  
  @objc func dismissFilters() {
    dismiss(animated: true, completion: .none)
  }

In the end your ViewController will look something like this:

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import UIKit
import InstantSearch

class ViewController: UIViewController {
  
  let searcher: SingleIndexSearcher = .init(appID: "latency",
                                            apiKey: "1f6fd3a6fb973cb08419fe7d288fa4db",
                                            indexName: "bestbuy")
  
  let queryInputInteractor: QueryInputInteractor = .init()
  let searchBarController: SearchBarController = .init(searchBar: UISearchBar())
  
  let statsInteractor: StatsInteractor = .init()
  let statsController: LabelStatsController = .init(label: UILabel())
  
  let hitsInteractor: HitsInteractor<JSON> = .init()
  let hitsTableController: HitsTableController<HitsInteractor<JSON>> = .init(tableView: UITableView())
  
  let categoryAttribute: Attribute = "category"
  let filterState: FilterState = .init()
  
  let categoryInteractor: FacetListInteractor = .init(selectionMode: .single)
  let categoryTableViewController: UITableViewController = .init()
  lazy var categoryListController: FacetListTableController = {
    return .init(tableView: categoryTableViewController.tableView, titleDescriptor: .none)
  }()
  
  override func viewDidLoad() {
    super.viewDidLoad()
    setup()
    configureUI()
    navigationController?.setNavigationBarHidden(true, animated: false)
  }
    
  func setup() {
    
    searcher.connectFilterState(filterState)
    
    queryInputInteractor.connectSearcher(searcher)
    queryInputInteractor.connectController(searchBarController)
    
    statsInteractor.connectSearcher(searcher)
    statsInteractor.connectController(statsController)
    
    hitsInteractor.connectSearcher(searcher)
    hitsInteractor.connectController(hitsTableController)
    hitsInteractor.connectFilterState(filterState)

    hitsTableController.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cellID")
    hitsTableController.dataSource = .init(cellConfigurator: { tableView, hit, indexPath in
      let cell = tableView.dequeueReusableCell(withIdentifier: "cellID", for: indexPath)
      cell.textLabel?.text = [String: Any](hit)?["name"] as? String
      return cell
    })
    
    categoryInteractor.connectSearcher(searcher, with: categoryAttribute)
    categoryInteractor.connectFilterState(filterState, with: categoryAttribute, operator: .and)
    categoryInteractor.connectController(categoryListController, with: FacetListPresenter(sortBy: [.isRefined]))
    
    searcher.search()
  }
  
  func configureUI() {
  
    view.backgroundColor = .white
    
    let stackView = UIStackView()
    stackView.translatesAutoresizingMaskIntoConstraints = false
    stackView.spacing = 16
    stackView.axis = .vertical
    stackView.layoutMargins = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 0)
    stackView.isLayoutMarginsRelativeArrangement = true
    
    let searchBar = searchBarController.searchBar
    searchBar.translatesAutoresizingMaskIntoConstraints = false
    searchBar.heightAnchor.constraint(equalToConstant: 40).isActive = true
    searchBar.searchBarStyle = .minimal
    
    let filterButton = UIButton()
    filterButton.setTitleColor(.black, for: .normal)
    filterButton.setTitle("Filter", for: .normal)
    filterButton.addTarget(self, action: #selector(showFilters), for: .touchUpInside)
    
    let searchBarFilterButtonStackView = UIStackView()
    searchBarFilterButtonStackView.translatesAutoresizingMaskIntoConstraints = false
    searchBarFilterButtonStackView.spacing = 4
    searchBarFilterButtonStackView.axis = .horizontal
    searchBarFilterButtonStackView.addArrangedSubview(searchBar)
    searchBarFilterButtonStackView.addArrangedSubview(filterButton)
    let spacer = UIView()
    spacer.widthAnchor.constraint(equalToConstant: 4).isActive = true
    searchBarFilterButtonStackView.addArrangedSubview(spacer)
    
    stackView.addArrangedSubview(searchBarFilterButtonStackView)
    
    let statsLabel = statsController.label
    statsLabel.translatesAutoresizingMaskIntoConstraints = false
    statsLabel.heightAnchor.constraint(equalToConstant: 16).isActive = true
    stackView.addArrangedSubview(statsLabel)

    stackView.addArrangedSubview(hitsTableController.tableView)
    
    view.addSubview(stackView)
    
    NSLayoutConstraint.activate([
      stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
      stackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
      stackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
      stackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
      ])
    
    categoryTableViewController.title = "Category"
    categoryTableViewController.view.backgroundColor = .white
    
  }
  
  @objc func showFilters() {
    let navigationController = UINavigationController(rootViewController: categoryTableViewController)
    categoryTableViewController.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissFilters))
    present(navigationController, animated: true, completion: .none)
  }
  
  @objc func dismissFilters() {
    dismiss(animated: true, completion: .none)
  }
  
}

You can now build and run your application: you now have a search experience with filtering using the RefinementList!

Guide refinements1

Guide refinements2

Going further

Your users can enter a query, and your application shows them results as they type. It also provides a possibility to filter the results even further using RefinementList. That is pretty nice already! However, we can go further and improve on that.

  • You can have a look at our examples to see more complex examples of applications built with InstantSearch.
  • You can head to our components page to see other components that you could use.

Did you find this page helpful?

InstantSearch iOS v5