我的数据模型中有一个UInt?
(可选)属性,我试图通过SwiftUI绑定到Toggle
和Slider
。我正在尝试达到这样的目标:
maximumRingsShownCount
的值为4(而不是nil),那么切换应为 on ,并且该值绑定到滑块。maximumExpandedRingsShownCount
的值为nil,则切换开关应关闭,并且不会显示滑块。我在这里面临2个问题:
到目前为止,在我看来,除了模型之外,我还声明了2个属性:
@ObjectBinding var configuration: SunburstConfiguration
@State private var haveMaximumRingsShownCount: Bool = false
@State private var haveMaximumExpandedRingsShownCount: Bool = false
我的视图主体包含(针对每个属性):
Toggle(isOn: $haveMaximumRingsShownCount) {
Text("maximumRingsShownCount")
}
if haveMaximumRingsShownCount {
VStack(alignment: .leading) {
Text("maximumRingsShownCount = \(config.maximumRingsShownCount!)")
Slider(value: .constant(4 /* no binding here :( */ ))
}
}
}
UI布局正确,但是我仍然遇到上述问题,因为:
haveMaximumRingsShownCount
状态未绑定到我的config.maximumRingsShownCount
模型为零的状态config.maximumRingsShownCount
属性上关于如何使用可选选项解决这些问题的任何想法?
[可以在https://github.com/lludo/SwiftSunburstDiagram的示例代码中复制它]
答案 0 :(得分:3)
我发现重载nil合并运算符(??)来处理这种情况很方便。
// 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)
}
然后可以按以下方式使用它:
struct OptionalSlider: View {
@Binding var optionalDouble: Double?
var body: some View {
Slider(value: $optionalDouble ?? 0.0, in: 0.0...1.0)
}
}
答案 1 :(得分:2)
这有点棘手,但是到目前为止,我发现的最佳解决方案是手动创建视图所需的Binding
(通过提供getter和setter)。
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()
}
}
由于Slider
仅接受浮点数,因此Binding
处理UInt
和Double
值之间的转换。
注意::Toggle
第一次通过视图事件更新其状态时,仍然存在怪异的行为。我暂时无法找到避免这种情况的方法,但是代码仍然可以为您提供帮助。
答案 2 :(得分:0)
我发现了另一种无需更改模型的方法,基本上,您只是创建一个Binding对象,而在感兴趣的视图内部没有可选对象,然后将展开逻辑移到那里 即
struct OptionalSlider: View {
private var optionalValue: Binding<Double>
private var label: String
private var hide: Bool
var body: some View {
Group {
if hide {
EmptyView()
} else {
HStack() {
Text(label)
Slider(value: optionalValue, in: 0.0...1.0)
}
}
}
}
init(value: Binding<Double?>, label: String) {
self.label = label
self.optionalValue = Binding<Double>(get: {
return value.wrappedValue ?? 0.0
}, set: {
value.wrappedValue = $0
})
self.hide = value.wrappedValue == nil
}
}