I\'m experimenting with SwiftUI and the Slider control like this:
struct MyView: View {
@State private var value = 0.5
var body: some View {
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()))
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())
}
}
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.
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 }
}
}
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:
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
}
}
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))
}
}
}