Hits
HitsConnector<Hit: Codable>( appID: ApplicationID, apiKey: APIKey, indexName: IndexName, infiniteScrolling: InfiniteScrolling, showItemsOnEmptyQuery: Bool, filterState: FilterState, controller: HitsController )
About this widget
The Hits
component displays a list of search results. The component automatically reloads results when new hits are fetched from Algolia.
The HitsConnector
is a generic class that you can customize by implementing a class or structure that represents your record and conforms to the Codable protocol.
If, for some reason, your record can’t be parsed into the type you provided, the onError
event of the Interactor
instance will be triggered.
In case you prefer to deal with raw JSON objects, set JSON
as the type of record and use the rawHitAtIndex(_ row: Int) -> [String: Any]?
method to access a hit.
Examples
Define the custom type representing the hits in your index conforming to Codable
protocol.
1
2
3
struct CustomHitModel: Codable {
let name: String
}
Instantiate a HitsConnector
and launch an initial search on its searcher.
1
2
3
4
5
6
7
8
9
10
let filterState: FilterState = .init()
let hitsTableViewController = CustomHitsTableViewController()
let hitsConnector = HitsConnector<CustomHitModel>(appID: "YourApplicationID",
apiKey: "YourSearchOnlyAPIKey",
indexName: "YourIndexName",
filterState: filterState,
controller: hitsTableViewController)
hitsConnector.searcher.search()
Parameters
searcher
|
type: HitsSearcher | PlacesSearcher
Required
The |
interactor
|
type: HitsInteractor
Required
The logic applied to the hits. |
filterState
|
type: FilterState
Optional
The |
appID
|
type: ApplicationID
Required
The ID of your application. |
placesAppID
|
type: ApplicationID
Required
The ID of your Places application. |
apiKey
|
type: APIKey
Required
Your application API Key. Be sure to use your Search-only API key. |
indexName
|
type: IndexName
Required
Name of the index to search. |
infiniteScrolling
|
type: InfiniteScrolling
default: .on(withOffset: 5)
Optional
Whether infinite scrolling is enabled. Offset defines the threshold for loading the next “page” of results.
For example, if the index of the last visible hit is 10, and you set |
showItemsOnEmptyQuery
|
type: Bool
default: true
Optional
If |
controller
|
type: HitsController
default: nil
Optional
The controller interfacing with a concrete hits view. |
Low-level API
If you want to fully control the Hits
components and connect them manually, you can use the following components:
Searcher
: TheSearcher
that handles your searches.HitsInteractor
: The logic applied to the hits.HitsController
: The controller that interfaces with a concrete hits view.FilterState
: The current state of the filters.
1
2
3
4
5
6
7
8
9
10
11
12
let searcher = HitsSearcher(appID: "YourApplicationID",
apiKey: "YourSearchOnlyAPIKey",
indexName: "YourIndexName")
let filterState: FilterState = .init()
let hitsInteractor: HitsInteractor<CustomHitModel> = .init()
let hitsTableViewController = CustomHitsTableViewController()
hitsInteractor.connectSearcher(searcher)
hitsInteractor.connectFilterState(filterState)
hitsInteractor.connectController(hitsTableViewController)
searcher.search()
Now, each time you launch a new search:
- The
Searcher
receives new results and transmit them toHitsInteractor
- The
HitsInteractor
parses search results and notifiesHitsController
- The
HitsController
refreshes the view presenting the hits
HitsController
The default controllers, e.g., HitsTableViewController
and HitsCollectionViewController
, allow you to create a basic Hits
view based on UITableView
and UICollectionView
components from UIKit.
You must configure these controllers using the TableViewCellConfigurable
/CollectionViewCellConfigurable
implementation protocols that define how your hit model is bound to a concrete cell class.
Here is an example of the implementation using the UITableViewCell
and CustomHitModel
defined above.
1
2
3
4
5
6
7
8
9
10
11
struct CustomCellConfigurator: TableViewCellConfigurable {
let model: CustomHitModel
init(model: CustomHitModel, indexPath: IndexPath) {
self.model = model
}
func configure(_ cell: UITableViewCell) {
cell.textLabel?.text = model.name
}
}
Define a convenient type alias for your view controller using the CellConfigurable
implementation.
1
typealias CustomHitsTableViewController = HitsTableViewController<CustomCellConfigurator>
Customization
You can subclass the HitsTableViewController
or HitsCollectionViewController
to customize their behavior.
1
2
3
class MoreCustomHitsTableViewController: HitsTableViewController<CustomCellConfigurator> {
...
}
Customizing your view
The default controllers, e.g. HitsTableViewController
and HitsCollectionViewController
, work well when you want to use native UIKit with their default behavior.
If you want to use another component as a hits view, or want to introduce some custom behavior to the already provided UIKit component, you can create your own controller conforming to the HitsController
protocol.
Protocol
var hitsSource: DataSource?
:
Reference to an entity providing a list of hits.
func reload()
:
Function called when we require a reload of the hits view.
func scrollToTop()
:
Function called when we have to scroll to the top of the hits view.
Implementation example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class CustomHitsTableViewController: NSObject, HitsController {
public let tableView: UITableView
public weak var hitsSource: HitsInteractor<Item>?
public init(tableView: UITableView) {
self.tableView = tableView
}
public func reload() {
tableView.reloadData()
}
public func scrollToTop() {
guard tableView.numberOfRows(inSection: 0) != 0 else { return }
let indexPath = IndexPath(row: 0, section: 0)
self.tableView.scrollToRow(at: indexPath, at: .top, animated: false)
}
}
SwiftUI
InstantSearch provides the HitsList
SwiftUI view, which you can embed in your views.
It uses HitsObservableController
as a data model. HitsObservableController
is an implementation of the HitsController
protocol adapted for usage with SwiftUI.
HitsObservableController
must be connected to the HitsConnector
or HitsInteractor
like any other HitsController
implementation.
You should define the view representing a single hit.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct HitItem: Codable {
let name: String
}
struct ContentView: View {
@ObservedObject var hitsController: HitsObservableController<HitItem>
var body: some View {
HitsList(hitsController) { (hit, _) in
// declare the view representing a single hit
VStack(alignment: .leading, spacing: 10) {
Text(hit?.name ?? "")
Divider()
}
} noResults: {
Text("No Results")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}
If you prefer to create a custom SwiftUI view that presents the list hits, you can directly use the HitsObservableController
as a data model.
It provides the hits
property to streamline the design process of your custom SwiftUI view.
Please note, that notifyAppearanceOfHit(atIndex)
function of HitsObservableController
might be called on appearance of each hit to ensure the correct operation of the infinite scrolling feature.
Result metadata and Hit structure
Each hit returned by Algolia is enriched with search metadata, like highlightResult
, objectID
, and snippetResult
.
InstantSearch provides an easy way to parse these using the Hit
wrapper structure. This is a generic structure that encapsulates the type of your record, and gives a strongly typed access to metadata of a hit.
For example, consider the following record structure:
1
2
3
4
5
6
7
8
9
10
11
12
struct Movie: Codable {
let title: String
let year: Int
}
/* An appropriate structure for representing a record in the following json format:
{
"title": "Titanic",
"year": 1997
}
*/
Conforming to the Codable
protocol, it is ready to use with the HitsInteractor
as follows:
1
let hitsInteractor = HitsInteractor<Movie>()
However, by doing this, all the metadata which comes with each hit will be ignored.
To keep search metadata, wrap your record structure into the provided Hit
structure.
1
let hitsInteractor = HitsInteractor<Hit<Movie>>()
You can still extract your Movie
object by accessing the object
field of Hit
:
1
2
let movieHit: Hit<Movie> = hitsInteractor.hit(atIndex: ...)
let movie: Movie = movieHit.object
The Hit
structure gives access to the following fields:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Hit identifier attributed by Algolia
let objectID: String
// Wrapped record object
let object: T
// Snippeted attributes.
let snippetResult: TreeModel<SnippetResult>?
// Highlighted attributes. Each attribute contains an object or an array of objects (if the attribute in question is an array) with the following attributes.
let highlightResult: TreeModel<HighlightResult>?
// Ranking information.
let rankingInfo: RankingInfo?
// Geolocation information
let geolocation: Point?