Filter Numeric Range
NumberRangeConnector<Number>( filterState: FilterState, attribute: Attribute, bounds: ClosedRange<Number>?, range: ClosedRange<Number>, operator: RefinementOperator, groupName: String?, controller: NumberRangeController )
About this widget
Filter Numeric Range
is a filtering component made to filter between two numeric values. The most common interface for this is a slider.
Examples
Instantiate a NumberRangeConnector
and launch an initial search on its searcher.
1
2
3
4
5
6
7
8
9
10
11
12
13
let searcher = HitsSearcher(appID: "YourApplicationID",
apiKey: "YourApiKey",
indexName: "YourIndexName")
let filterState: FilterState = .init()
let numberRangeController: RangeSliderController = .init(rangeSlider: .init())
let numericRangeConnector: NumberRangeConnector<Double> = .init(searcher: searcher,
filterState: filterState,
attribute: "price",
controller: numberRangeController)
searcher.search()
Parameters
searcher
|
type: HitsSearcher
Required
The |
filterState
|
type: FilterState
Required
The |
attribute
|
type: Attribute
Required
The attribute to filter. |
interactor
|
type: NumberRangeInteractor<Number>
default: .init()
Required
The logic applied to the numeric range. |
bounds
|
type: ClosedRange<Number>?
default: nil
Optional
The Bounds limiting the max and the min value of the range. |
range
|
type: ClosedRange<Number>
default: nil
Optional
The initial range value. |
operator
|
type: RefinementOperator
default: .and
Optional
Whether the filter is added to a conjuncitve( |
groupName
|
type: String
default: The value of the `attribute` parameter
Optional
Filter group name in the |
controller
|
type: NumberRangeController
default: nil
Optional
The Controller interfacing with a concrete number range view. |
Low-level API
If you want to fully control the Filter Numeric Range
components and connect them manually, you can use the following components:
Searcher
: TheSearcher
that handles your searches.FilterState
: The current state of the filters.NumberRangeInteractor
: The logic applied to the numeric ranges.NumberRangeController
: The controller that interfaces with a concrete numeric range view.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let searcher = HitsSearcher(appID: "YourApplicationID",
apiKey: "YourApiKey",
indexName: "YourIndexName")
let filterState: FilterState = .init()
let numericRangeInteractor: NumberRangeInteractor<Double> = .init()
let numberRangeController: RangeSliderController = .init(rangeSlider: .init())
let priceAttribute: Attribute = "price"
searcher.connectFilterState(filterState)
numericRangeInteractor.connectFilterState(filterState, attribute: priceAttribute)
numericRangeInteractor.connectSearcher(searcher, attribute: priceAttribute)
numericRangeInteractor.connectController(numberRangeController)
searcher.search()
Customizing your view
If you want to use 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 NumberRangeController
protocol.
Protocol
func setBounds(_ bounds: ClosedRange<Double>)
:
Function called when the minimum and maximum of numeric filter view have been defined.
var onRangeChanged: ((ClosedRange<Double>) -> Void)?
Closure to call when new bounds is set for the numeric range filter.
Example
Let’s assume you have a range slider control providing the following API:
1
2
3
4
5
6
class RangeSlider: UIControl {
var lowerValue: Double
var upperValue: Double
var minimumValue: Double
var maximumValue: Double
}
Then, you could implement the NumberRangeController
including this control as follows:
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
class RangeSliderController: NumberRangeController {
let rangeSlider: RangeSlider
var onRangeChanged: ((ClosedRange<Double>) -> Void)?
func setBounds(_ bounds: ClosedRange<Double>) {
rangeSlider.minimumValue = bounds.lowerBound
rangeSlider.maximumValue = bounds.upperBound
setItem(bounds)
}
func setItem(_ item: ClosedRange<Double>) {
rangeSlider.lowerValue = item.lowerBound
rangeSlider.upperValue = item.upperBound
}
init(rangeSlider: RangeSlider) {
self.rangeSlider = rangeSlider
rangeSlider.addTarget(self, action: #selector(onValueChanged(sender:)), for: [.touchUpInside, .touchUpOutside])
}
@objc func onValueChanged(sender: RangeSlider) {
onRangeChanged?(sender.lowerValue...sender.upperValue)
}
}
SwiftUI
InstantSearch provides the NumberRangeObservableController
data model, which is an implementation of the NumberRangeController
protocol adapted for usage with SwiftUI.
NumberRangeObservableController
must be connected to the NumberRangeConnector
or NumberRangeConnector
like any other NumberRangeController
implementation.
The example of the number range view using two Stepper
components provided by SwiftUI.
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
struct ContentView: View {
@ObservedObject var numberRangeController: NumberRangeObservableController<Int>
var body: some View {
let range = numberRangeController.range
VStack{
Stepper(onIncrement: {
if let range = makeRange(range.lowerBound+1, range.upperBound) {
numberRangeController.range = range
}
},
onDecrement: {
if let range = makeRange(range.lowerBound-1, range.upperBound) {
numberRangeController.range = range
}
},
label: {
Text("Lower bound: \(range.lowerBound)")
})
Stepper(onIncrement: {
if let range = makeRange(range.lowerBound, range.upperBound+1) {
numberRangeController.range = range
}
},
onDecrement: {
if let range = makeRange(range.lowerBound, range.upperBound-1) {
numberRangeController.range = range
}
},
label: {
Text("Upper bound: \(numberRangeController.range.upperBound)")
})
}
}
func makeRange(_ lowerBound: Int, _ upperBound: Int) -> ClosedRange<Int>? {
if lowerBound < upperBound &&
numberRangeController.bounds.contains(lowerBound) &&
numberRangeController.bounds.contains(upperBound) {
return lowerBound...upperBound
} else {
return nil
}
}
}
If you prefer to create a custom SwiftUI view that presents the numeric range, you can directly use the NumberRangeObservableController
as a data model.
It provides range
and bounds
properties to streamline the design process of your custom SwiftUI view.
Check out the example to see this widget in action.