Initializing a Slider value in SwiftUI - ios

In the process of learning SwiftUI, I am having this issue.
I have this view with a slider:
struct RangeSpanView: View {
var minimumValue,maximumValue:Double
init(minSlide: UInt64,
maxSlide: UInt64) {
self.minimumValue = Double(minSlide)
self.maximumValue = Double(maxSlide)
}
#State var sliderValue = 0.0
var body: some View {
VStack {
HStack {
Text("\(Int(minimumValue))")
Slider(value: $sliderValue,
in: minimumValue...maximumValue)
Text("\(Int(maximumValue))")
}.padding()
Text("\(Int(sliderValue))")
Spacer()
}
}
}
And this is the code where the view is loaded.
var body: some View {
NavigationLink(destination: RangeSpanView(minSlide: myMinVal,
maxSlide: myMaxVal),
label: {
.......... // Useful code.
})
}
It works fine using the slider. There is only a little inconsistency detail concerning the initialization of the sliderValue that I would like to fix.
The way it is in the current code is that sliderValue is initialized to 0.0, which is not exactly what I like when the min and max values are respectively 150.0 and 967.0 (for example).
I have tried various solutions to initialize sliderValue to minimumValue (for example) to make it consistent. But it always fails with some error message complaining about some "expected argument type 'Binding" in the Slider object.
So, is there a proper way to solve this problem?

You can initialize your #State variable with the lowest min value of the slider. So you won't need to do this in the willApear() method.
struct RangeSpanView: View {
var minimumValue,maximumValue:Double
//declare State here
#State var sliderValue : Double
init(minSlide: UInt64,
maxSlide: UInt64) {
self.minimumValue = Double(minSlide)
self.maximumValue = Double(maxSlide)
//Initialize the State with the lowest value
self._sliderValue = State(initialValue: self.minimumValue)
}

How about something like this:
struct TestView: View {
let myMinVal: UInt64 = UInt64.random(in: 100...200)
let myMaxVal: UInt64 = UInt64.random(in: 400...600)
#State var sliderValue: Double = 0
var body: some View {
RangeSpanView(
minimumValue: Double(myMinVal),
maximumValue: Double(myMaxVal),
sliderValue: $sliderValue
).onAppear {
self.sliderValue = Double(self.myMinVal)
}
}
}
struct RangeSpanView: View {
var minimumValue: Double
var maximumValue: Double
#Binding var sliderValue: Double
var body: some View {
VStack {
HStack {
Text("\(Int(minimumValue))")
Slider(value: $sliderValue,
in: minimumValue...maximumValue)
Text("\(Int(maximumValue))")
}.padding()
Text("\(Int(sliderValue))")
Spacer()
}
}
}
You can use a #Binding var on the SliderValue so that you can set starting value before you present it.
I used onAppear but you can set the sliderValue on wherever you are setting the myMinVal

I think Ludyem is onto somthing. On this site I found an explanation as to why setting the value in the init wont work.
But if we use the onAppear function on the slider we can ensure the default value is within range like this
.onAppear {
if(self.sliderValue < self.minimumValue) {
self.sliderValue = self.minimumValue
}
}
So the whole thing would look like this
struct RangeSpanView: View {
let minimumValue,maximumValue:Double
init(minSlide: UInt64, maxSlide: UInt64) {
self.minimumValue = Double(minSlide)
self.maximumValue = Double(maxSlide)
}
#State var sliderValue = 0.0
var body: some View {
VStack {
Text("hi guys")
HStack {
Text("\(Int(minimumValue))")
Slider(value: $sliderValue,
in: minimumValue...maximumValue)
.onAppear {
if(self.sliderValue < self.minimumValue) {
self.sliderValue = self.minimumValue
}
}
Text("\(Int(maximumValue))")
}.padding()
Text("\(Int(sliderValue))")
Spacer()
}
}
}

Related

SwiftUI: Changing a private var with a slider

I have a private variable in a struct which I can only access using a setter and getter. I want to change this variable using a slider, so I am attempting to bind a different var with this var using willSet:
struct MyStruct {
private var myVar: Float? = nil
mutating func setMyVar(newVal: Float) {
if (SomeCondition) { // always true when the slider is in use
myVar = newVal
} else {
// stuff
}
}
func getMyVar() -> Float {
myVar == nil ? 0.0 : myVar!
}
}
struct MyView: View {
#State var myStructToEdit = MyStruct()
#State var tempVar: Double = 0.0 {
willSet {
myStructToEdit.setMyVar(newVal: Float(newValue))
}
}
var body: some View {
VStack {
Text(String(tempVar))
Text(String(myStructToEdit.getMyVar()))
Slider(value: $tempVar, in: 1.0...20.0, step: 1.0)
}
}
}
As the slider moves, tempVar changes but MyVar doesn't. What is the correct way to achieve this binding?
Property observers won't work with SwiftUI #State variables.
Use .onChange(of:) to act upon changes to tempVar:
struct MyView: View {
#State var myStructToEdit = MyStruct()
#State var tempVar: Double = 0.0
var body: some View {
VStack {
Text(String(tempVar))
Text(String(myStructToEdit.getMyVar()))
Slider(value: $tempVar, in: 1.0...20.0, step: 1.0)
.onChange(of: tempVar) { value in
myStructToEdit.setMyVar(newVal: Float(value))
}
}
}
}
Use Binding(get:set:) to directly set and get the value in your struct:
You don't need tempVar. You can directly set and get the value to and from your struct.
struct ContentView: View {
#State var myStructToEdit = MyStruct()
var body: some View {
VStack {
Text(String(myStructToEdit.getMyVar()))
Slider(value: Binding(get: {
myStructToEdit.getMyVar()
}, set: { value in
myStructToEdit.setMyVar(newVal: value)
}), in: 1.0...20.0, step: 1.0)
}
}
}
or assign the Binding to a let to make it cleaner:
struct ContentView: View {
#State var myStructToEdit = MyStruct()
var body: some View {
let myVarBinding = Binding(
get: { myStructToEdit.getMyVar() },
set: { value in myStructToEdit.setMyVar(newVal: value) }
)
VStack {
Text(String(myStructToEdit.getMyVar()))
Slider(value: myVarBinding, in: 1.0...20.0, step: 1.0)
}
}
}
Note: Since myVarBinding is already a binding, you do not need to use a $ to turn it into a binding.

SwiftUI: Reset TextField value from CoreData on change in value of another state variable

I have a ContentView and a NumericView. The selectedIndex is a #State in ContentView. What I want is that if selectedIndex changes, it should reset the answer field in NumericView from CoreData. Below is the code. "av" contains the value of the answer field from CoreData.
struct NumericEntry: View {
#EnvironmentObject var questionAnswerStore: QuestionAnswerStore
#State var answer: String
var questionIndex: Int
init(questionIndex: Int, av: String) {
self.questionIndex = questionIndex
_answer = State(initialValue: av)
}
var body: some View {
VStack {
TextField("Answer", text: $answer)
.textFieldStyle(CustomTextFieldStyle())
.onChange(of: answer) { newValue in
self.questionAnswerStore.getUserAttemptData(selectedIndex: questionIndex).answer = newValue
PersistenceController.shared.saveContext()
}
.keyboardType(.decimalPad)
.padding()
}
}
private struct CustomTextFieldStyle : TextFieldStyle {
public func _body(configuration: TextField<Self._Label>) -> some View {
configuration
.padding(10)
.background(
RoundedRectangle(cornerRadius: 5)
.strokeBorder(Color.secondary.opacity(0.5), lineWidth: 1))
}
}
}
struct ContentView: View {
#State var selectedIndex: Int = 0
#ObservedObject private var questionAnswerStore: QuestionAnswerStore = QuestionAnswerStore.sharedInstance
var body: some View {
NumericEntry(questionIndex: selectedIndex, av: self.questionAnswerStore.getUserAttemptData(selectedIndex: selectedIndex).answer ?? "")
// some code that keeps changing the value of selectedIndex
}
}
I read somewhere that _stateVariable = State(initialValue: "abcd") should set the state of #State stateVariable. In the above code the code
_answer = State(initialValue: av)
executes fine but when it reaches
TextField("Answer", text: $answer)
$answer is still "".
I would prefer a solution where I don't even have to send "av" from the parent component, just selectedIndex should check QuestionAnswerStore for the value of "answer". This could be solved using .onAppear but in my case, the NumericView appears only once and then its parent just keeps changing selectedIndex value, so onAppear doesn't get called again.
Of course, if that is not possible then what's the way out using "av" as above?

How does one use a Stepper to update a value in the model in SwiftUI?

I am trying to increment or decrement a value using Stepper, however I cannot fathom how to take the new value and update my model.
I've also tried to use the onIncrement, onDecrement version, but here there appears to be no option to disable the + or - when the range has been reached.
Here's my example code:
import SwiftUI
struct ContentView: View {
var body: some View {
MainView()
}
}
struct MainView: View {
#ObservedObject var updateAge = UpdateAge()
#State private var showAgeEditor = false
#State var age: Int = 0
var body: some View {
VStack {
/// Show the current age.
Text("Age \(age)")
Image(systemName: "keyboard")
.onTapGesture {
self.showAgeEditor = true
}
/// Present the sheet to update the age.
.sheet(isPresented: $showAgeEditor) {
SheetView(showAgeEditor: self.$showAgeEditor)
.environmentObject(self.updateAge)
.frame(minWidth: 300, minHeight: 400)
}
}
/// Calls the viewModel (UpdateAge) to fetch the age from the model.
.onAppear(perform: {self.age = self.updateAge.withAge})
}
}
struct SheetView: View {
#EnvironmentObject var updateAge: UpdateAge
#Binding var showAgeEditor: Bool
/// Steppers state.
#State private var age: Int = 18
let maxAge = 21
let minAge = 18
var body: some View {
return VStack {
Text("Age of person = \(age)")
/// When I increment or Decrement the stepper I want the Age to increase or decrease.
Stepper<Text>(value: $age, in: minAge...maxAge, step: 1) {
/// And when it does, to store the value in the model via the viewModel.
/// However I appear to have created an infinite loop. Just uncomment the line below.
// self.updateAge.with(age: age)
/// Then display the value in the label.
return Text("\(age)")
}
}.onAppear(perform: {self.age = self.updateAge.withAge})
}
}
class UpdateAge: ObservableObject {
#Published var model = Model()
func with(age value: Int) {
print("Value passed \(value)")
self.model.setDrinkingAge(with: value)
}
var withAge: Int {
get { model.drinkingAge }
}
}
struct Model {
var drinkingAge: Int = 18
mutating func setDrinkingAge(with value: Int) {
drinkingAge = value
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
You can bind to model directly via view model projected value, like below (so local state age is redundant)
Stepper(value: $updateAge.model.drinkingAge, in: minAge...maxAge, step: 1) {
Text("\(updateAge.model.drinkingAge)")
}

How to modify/reset #State when other variables are updated in SwiftUI

I would like to reset some #State each time an #ObservedObject is "reassigned". How can this be accomplished?
class Selection: ObservableObject {
let id = UUID()
}
struct ContentView: View {
#State var selectedIndex: Int = 0
var selections: [Selection] = [Selection(), Selection(), Selection()]
var body: some View {
VStack {
// Assume the user can select something so selectedIndex changes all the time
// SwiftUI will just keep updating the same presentation layer with new data
Button("Next") {
self.selectedIndex += 1
if self.selectedIndex >= self.selections.count {
self.selectedIndex = 0
}
}
SelectionDisplayer(selection: self.selections[self.selectedIndex])
}
}
}
struct SelectionDisplayer: View {
#ObservedObject var selection: Selection {
didSet { // Wish there were something *like* this
print("will never happen")
self.tabIndex = 0
}
}
#State var tapCount: Int = 0 // Reset this to 0 when `selection` changes from one object to another
var body: some View {
Text(self.selection.id.description)
.onReceive(self.selection.objectWillChange) {
print("will never happen")
}
Button("Tap Count: \(self.tapCount)") {
self.tapCount += 1
}
}
}
I'm aware of onReceive but I'm not looking to modify state in response to objectWillChange but rather when the object itself is switched out. In UIKit with reference types I would use didSet but that doesn't work here.
I did try using a PreferenceKey for this (gist) but it seems like too much of a hack.
Currently (Beta 5), the best way may be to use the constructor plus a generic ObservableObject for items that I want to reset when the data changes. This allows some #State to be preserved, while some is reset.
In the example below, tapCount is reset each time selection changes, while allTimeTaps is not.
class StateHolder<Value>: ObservableObject {
#Published var value: Value
init(value: Value) {
self.value = value
}
}
struct ContentView: View {
#State var selectedIndex: Int = 0
var selections: [Selection] = [Selection(), Selection(), Selection()]
var body: some View {
VStack {
// Assume the user can select something so selectedIndex changes all the time
// SwiftUI will just keep updating the same presentation though
Button("Next") {
self.selectedIndex += 1
if self.selectedIndex >= self.selections.count {
self.selectedIndex = 0
}
}
SelectionDisplayer(selection: self.selections[self.selectedIndex])
}
}
}
struct SelectionDisplayer: View {
struct SelectionState {
var tapCount: Int = 0
}
#ObservedObject var selection: Selection
#ObservedObject var stateHolder: StateHolder<SelectionState>
#State var allTimeTaps: Int = 0
init(selection: Selection) {
let state = SelectionState()
self.stateHolder = StateHolder(value: state)
self.selection = selection
}
var body: some View {
VStack {
Text(self.selection.id.description)
Text("All Time Taps: \(self.allTimeTaps)")
Text("Tap Count: \(self.stateHolder.value.tapCount)")
Button("Tap") {
self.stateHolder.value.tapCount += 1
self.allTimeTaps += 1
}
}
}
}
While looking for a solution I was quite interested to discover that you cannot initialize #State variables in init. The compiler will complain that all properties have not yet been set before accessing self.
The only way to get this done for me was to force the parent view to redraw the child by temporarily hiding it. It's a hack, but the alternative was to pass a $tapCount in, which is worse since then the parent does not only have to know that it has to be redrawn, but must also know of state inside.
This can probably be refactored into it's own view, which make it not as dirty.
import Foundation
import SwiftUI
import Combine
class Selection {
let id = UUID()
}
struct RedirectStateChangeView: View {
#State var selectedIndex: Int = 0
#State var isDisabled = false
var selections: [Selection] = [Selection(), Selection(), Selection()]
var body: some View {
VStack {
// Assume the user can select something so selectedIndex changes all the time
// SwiftUI will just keep updating the same presentation layer with new data
Button("Next") {
self.selectedIndex += 1
if self.selectedIndex >= self.selections.count {
self.selectedIndex = 0
}
self.isDisabled = true
DispatchQueue.main.asyncAfter(deadline: .now() + 0.005) {
self.isDisabled = false
}
}
if !isDisabled {
SelectionDisplayer(selection: selections[selectedIndex])
}
}
}
}
struct SelectionDisplayer: View {
var selection: Selection
#State var tapCount: Int = 0 // Reset this to 0 when `selection` changes from one object to another
var body: some View {
VStack{
Text(selection.id.description)
Button("Tap Count: \(self.tapCount)") {
self.tapCount += 1
}
}
}
}
This does what you want I believe:
class Selection: ObservableObject { let id = UUID() }
struct ContentView: View {
#State var selectedIndex: Int = 0
var selections: [Selection] = [Selection(), Selection(), Selection()]
#State var count = 0
var body: some View {
VStack {
Button("Next") {
self.count = 0
self.selectedIndex += 1
if self.selectedIndex >= self.selections.count {
self.selectedIndex = 0
}
}
SelectionDisplayer(selection: self.selections[self.selectedIndex], count: $count)
}
}
}
struct SelectionDisplayer: View {
#ObservedObject var selection: Selection
#Binding var count: Int
var body: some View {
VStack {
Text("\(self.selection.id)")
Button("Tap Count: \(self.count)") { self.count += 1 }
}
}
}
My Xcode didn't like your code so I needed to make a few other changes than just moving the count into the parent

SwiftUI: How to get continuous updates from Slider

I'm experimenting with SwiftUI and the Slider control like this:
struct MyView: View {
#State private var value = 0.5
var body: some View {
Slider(value: $value) { pressed in
}
}
}
I'm trying to get continuous updates from the Slider as the user drags it, however it appears that it only updates the value at the end of the value change.
Anyone played with this? know how to get a SwiftUI Slider to issue a stream of value changes? Combine perhaps?
In SwiftUI, you can bind UI elements such as slider to properties in your data model and implement your business logic there.
For example, to get continuous slider updates:
import SwiftUI
import Combine
final class SliderData: BindableObject {
let didChange = PassthroughSubject<SliderData,Never>()
var sliderValue: Float = 0 {
willSet {
print(newValue)
didChange.send(self)
}
}
}
struct ContentView : View {
#EnvironmentObject var sliderData: SliderData
var body: some View {
Slider(value: $sliderData.sliderValue)
}
}
Note that to have your scene use the data model object, you need to update your window.rootViewController to something like below inside SceneDelegate class, otherwise the app crashes.
window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(SliderData()))
After much playing around I ended up with the following code. It's a little cut down to keep the answer short, but here goes. There was a couple of things I needed:
To read value changes from the slider and round them to the nearest integer before setting an external binding.
To set a localized hint value based on the integer.
struct AspectSlider: View {
// The first part of the hint text localization key.
private let hintKey: String
// An external integer binding to be set when the rounded value of the slider
changes to a different integer.
private let value: Binding<Int>
// A local binding that is used to track the value of the slider.
#State var sliderValue: Double = 0.0
init(value: Binding<Int>, hintKey: String) {
self.value = value
self.hintKey = hintKey
}
var body: some View {
VStack(alignment: .trailing) {
// The localized text hint built from the hint key and the rounded slider value.
Text(LocalizedStringKey("\(hintKey).\(self.value.value)"))
HStack {
Text(LocalizedStringKey(self.hintKey))
Slider(value: Binding<Double>(
getValue: { self.$sliderValue.value },
setValue: { self.sliderChanged(toValue: $0) }
),
through: 4.0) { if !$0 { self.slideEnded() } }
}
}
}
private func slideEnded() {
print("Moving slider to nearest whole value")
self.sliderValue = self.sliderValue.rounded()
}
private func sliderChanged(toValue value: Double) {
$sliderValue.value = value
let roundedValue = Int(value.rounded())
if roundedValue == self.value.value {
return
}
print("Updating value")
self.value.value = roundedValue
}
}
We can go without custom bindings, custom inits, ObservableObjects, PassthroughSubjects, #Published and other complications. Slider has .onChange(of: perform:) modifier which is perfect for this case.
This answer can be rewritten as follows:
struct AspectSlider2: View {
#Binding var value: Int
let hintKey: String
#State private var sliderValue: Double = 0.0
var body: some View {
VStack(alignment: .trailing) {
Text(LocalizedStringKey("\(hintKey)\(value)"))
HStack {
Slider(value: $sliderValue, in: 0...5)
.onChange(of: sliderValue, perform: sliderChanged)
}
}
}
private func sliderChanged(to newValue: Double) {
sliderValue = newValue.rounded()
let roundedValue = Int(sliderValue)
if roundedValue == value {
return
}
print("Updating value")
value = roundedValue
}
}
In Version 11.4.1 (11E503a) & Swift 5. I didn't reproduce it.
By using Combine, I could get continuously update from slider changes.
class SliderData: ObservableObject {
#Published var sliderValue: Double = 0
...
}
struct ContentView: View {
#ObservedObject var slider = SliderData()
var body: some View {
VStack {
Slider(value: $slider.sliderValue)
Text(String(slider.sliderValue))
}
}
}
I am not able to reproduce this issue on iOS 13 Beta 2. Which operating system are you targeting?
Using a custom binding, the value is printed for every small change, not only after editing ended.
Slider(value: Binding<Double>(getValue: {0}, setValue: {print($0)}))
Note, that the closure ({ pressed in }) only reports when editing end starts and ends, the value stream is only passed into the binding.
What about like this:
(1) First you need the observable ...
import SwiftUI
import PlaygroundSupport
// make your observable double for the slider value:
class SliderValue: ObservableObject {
#Published var position: Double = 11.0
}
(2) When you make the slider, you have to PASS IN an instance of the observable:
So in HandySlider it is declared as an ObservedObject. (Don't forget, you're not "making" it there. Only declare it as a StateObject where you are "making" it.)
(3) AND you use the "$" for the Slider value as usual in a slider
(It seems the syntax is to use it on the "whole thing" like this "$sliderValue.position" rather than on the value per se, like "sliderValue.$position".)
struct HandySlider: View {
// don't forget to PASS IN a state object when you make a HandySlider
#ObservedObject var sliderValue: SliderValue
var body: some View {
HStack {
Text("0")
Slider(value: $sliderValue.position, in: 0...20)
Text("20")
}
}
}
(4) Actually make the state object somewhere.
(So, you use "StateObject" to do that, not "ObservedObject".)
And then
(5) use it freely where you want to display the value.
struct ContentView: View {
// here we literally make the state object
// (you'd just make it a "global" but not possible in playground)
#StateObject var sliderValue = SliderValue()
var body: some View {
HandySlider(sliderValue: sliderValue)
.frame(width: 400)
Text(String(sliderValue.position))
}
}
PlaygroundPage.current.setLiveView(ContentView())
Test it ...
Here's the whole thing to paste in a playground ...
import SwiftUI
import PlaygroundSupport
class SliderValue: ObservableObject {
#Published var position: Double = 11.0
}
struct HandySlider: View {
#ObservedObject var sliderValue: SliderValue
var body: some View {
HStack {
Text("0")
Slider(value: $sliderValue.position, in: 0...20)
Text("20")
}
}
}
struct ContentView: View {
#StateObject var sliderValue = SliderValue()
var body: some View {
HandySlider(sliderValue: sliderValue)
.frame(width: 400)
Text(String(sliderValue.position))
}
}
PlaygroundPage.current.setLiveView(ContentView())
Summary ...
You'll need an ObservableObject class: those contain Published variables.
Somewhere (obviously one place only) you will literally make that observable object class, and that's StateObject
Finally you can use that observable object class anywhere you want (as many places as needed), and that's ObservedObject
And in a slider ...
In the tricky case of a slider in particular, the desired syntax seems to be
Slider(value: $ooc.pitooc, in: 0...20)
ooc - your observable object class
pitooc - a property in that observable object class
You would not create the observable object class inside the slider, you create it elsewhere and pass it in to the slider. (So indeed in the slider class it is an observed object, not a state object.)
iOS 13.4, Swift 5.x
An answer based on Mohammid excellent solution, only I didn't want to use environmental variables.
class SliderData: ObservableObject {
let didChange = PassthroughSubject<SliderData,Never>()
#Published var sliderValue: Double = 0 {
didSet {
print("sliderValue \(sliderValue)")
didChange.send(self)
}
}
}
#ObservedObject var sliderData:SliderData
Slider(value: $sliderData.sliderValue, in: 0...Double(self.textColors.count))
With a small change to ContentView_Preview and the same in SceneDelegate.
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(sliderData: SliderData.init())
}
}
If the value is in a navigation, child view:
Here's the case if the slider is, say, a popup which allows you to adjust a value.
It's actually simpler, nothing needs to be passed in to the slider. Just use an #EnvironmentObject.
Don't forget environment objects must be in the ancestor chain (you can't unfortunately go "sideways").
EnvironmentObject is only for parent-child chains.
Somewhat confusingly, you can't use the simple EnvironmentObject system if the items in question are in the same "environment!" EnvironmentObject should perhaps be named something like "ParentChainObject" or "NavigationViewChainObject".
EnvironmentObject is only used when you are using NavigationView.
import SwiftUI
import PlaygroundSupport
// using ancestor views ...
class SliderValue: ObservableObject {
#Published var position: Double = 11.0
}
struct HandySliderPopUp: View {
#EnvironmentObject var sv: SliderValue
var body: some View {
Slider(value: $sv.position, in: 0...10)
}
}
struct ContentView: View {
#StateObject var sliderValue = SliderValue()
var body: some View {
NavigationView {
VStack{
NavigationLink(destination:
HandySliderPopUp().frame(width: 400)) {
Text("click me")
}
Text(String(sliderValue.position))
}
}
.environmentObject(sliderValue) //HERE
}
}
PlaygroundPage.current.setLiveView(ContentView())
Note that //HERE is where you "set" the environment object.
For the "usual" situation, where it's the "same" view, see other answer.
Late to the party, this is what I did:
struct DoubleSlider: View {
#State var value: Double
let range: ClosedRange<Double>
let step: Double
let onChange: (Double) -> Void
init(initialValue: Double, range: ClosedRange<Double>, step: Double, onChange: #escaping (Double) -> Void) {
self.value = initialValue
self.range = range
self.step = step
self.onChange = onChange
}
var body: some View {
let binding = Binding<Double> {
return value
} set: { newValue in
value = newValue
onChange(newValue)
}
Slider(value: binding, in: range, step: step)
}
}
Usage:
DoubleSlider(initialValue: state.tipRate, range: 0...0.3, step: 0.01) { rate in
viewModel.state.tipRate = rate
}
Just use the onEditingChanged parameter of Slider. The argument is true while the user is moving the slider or still in contact with it. I do my updates when the argument changes from true to false.
struct MyView: View {
#State private var value = 0.5
func update(changing: Bool) -> Void {
// Do whatever
}
var body: some View {
Slider(value: $value, onEditingChanged: {changing in self.update(changing) })
{ pressed in }
}
}

Resources