I have a UInt? (optional) property in my data model that I am trying to bind to a Toggle and Slider with SwiftUI. I am trying to get to something like this:
maximumRingsShownCount have value 4 (not nil), then the toggle should be on and the value bound to the slider.maximumExpandedRingsShownCount value is nil, then the toggle should off and the slider is not shown.
I am facing 2 issues here:
So far, in my view I declared 2 properties in addition to my model:
@ObjectBinding var configuration: SunburstConfiguration
@State private var haveMaximumRingsShownCount: Bool = false
@State private var haveMaximumExpandedRingsShownCount: Bool = false
And my view body contains (for each property):
Toggle(isOn: $haveMaximumRingsShownCount) {
Text("maximumRingsShownCount")
}
if haveMaximumRingsShownCount {
VStack(alignment: .leading) {
Text("maximumRingsShownCount = \(config.maximumRingsShownCount!)")
Slider(value: .constant(4 /* no binding here :( */ ))
}
}
}
The UI layout is correct but I still have the issues mentioned above because:
haveMaximumRingsShownCount state is not bound to my config.maximumRingsShownCount model being nil or notconfig.maximumRingsShownCount propertyAny ideas on how to solve these issue with optionals?
[ This can be reproduced in the sample code at https://github.com/lludo/SwiftSunburstDiagram ]
I found it convenient to overload the nil coalescing operator (??) to work with this situation.
// OptionalBinding.swift
import Foundation
import SwiftUI
func OptionalBinding<T>(_ binding: Binding<T?>, _ defaultValue: T) -> Binding<T> {
return Binding<T>(get: {
return binding.wrappedValue ?? defaultValue
}, set: {
binding.wrappedValue = $0
})
}
func ??<T> (left: Binding<T?>, right: T) -> Binding<T> {
return OptionalBinding(left, right)
}
You can then use it as follows:
struct OptionalSlider: View {
@Binding var optionalDouble: Double?
var body: some View {
Slider(value: $optionalDouble ?? 0.0, in: 0.0...1.0)
}
}
It's a bit tricky, but creating manually the Binding (by providing the getter and the setter) required for the view is the best solution I have found so far.
class DataModel: BindableObject {
public let didChange = PassthroughSubject<Void, Never>()
var maximumRingsShownCount: UInt? = 50 {
didSet {
didChange.send(())
}
}
lazy private(set) var sliderBinding: Binding<Double> = {
return Binding<Double>(getValue: {
return Double(self.maximumRingsShownCount ?? 0) / 100.0
}) { (value) in
self.maximumRingsShownCount = UInt(value * 100)
}
}()
lazy private(set) var toggleBinding: Binding<Bool> = {
return Binding<Bool>(getValue: { () -> Bool in
return self.maximumRingsShownCount != nil
}, setValue: { (value) in
self.maximumRingsShownCount = value ? 0 : nil
})
}()
}
struct ContentView : View {
@ObjectBinding var model = DataModel()
var body: some View {
VStack {
Toggle(isOn: model.toggleBinding) {
Text("Enable")
}
if model.maximumRingsShownCount != nil {
Text("max rings: \(model.maximumRingsShownCount!)")
Slider(value: model.sliderBinding)
}
}.padding()
}
}
As the Slider can only accept floating point numbers, the Binding handle the conversion between UInt and Double values.
Note : There is still a weird behaviour with the Toggle the first time its state is updated by a view event. I couldn't find a way to avoid this for now, but the code might still help you.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With