有没有一种方法可以使用 SwiftUI 将可选项绑定到 Toggle / Slider
Is there a way to bind an optional to a Toggle / Slider with SwiftUI
我的数据模型中有一个 UInt?
(可选)属性,我正尝试使用 Swift[=60= 绑定到 Toggle
和 Slider
].我正在尝试做这样的事情:
maximumRingsShownCount
的值为 4(不是零),则切换应 on 并且值绑定到滑块。
maximumExpandedRingsShownCount
值为 nil,则切换应 关闭 并且不显示滑块。
我在这里面临 2 个问题:
- 看起来我们不能有可选的绑定(对于滑块)
- 是否可以使用转换器将可选值转换为布尔值(对于 Toggle)?
到目前为止,在我看来,除了我的模型之外,我还声明了 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 的示例代码中重现]
这有点棘手,但是手动创建视图所需的 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
第一次通过视图事件更新其状态时仍然存在奇怪的行为。我暂时找不到避免这种情况的方法,但代码可能仍会对您有所帮助。
我找到了另一种不更改模型的方法,基本上,您只需在您感兴趣的视图中创建一个没有可选对象的绑定对象,然后将您的解包逻辑移到那里
即
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 = [=10=]
})
self.hide = value.wrappedValue == nil
}
}
我发现重载 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 = [=10=]
})
}
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)
}
}
我的数据模型中有一个 UInt?
(可选)属性,我正尝试使用 Swift[=60= 绑定到 Toggle
和 Slider
].我正在尝试做这样的事情:
maximumRingsShownCount
的值为 4(不是零),则切换应 on 并且值绑定到滑块。maximumExpandedRingsShownCount
值为 nil,则切换应 关闭 并且不显示滑块。
我在这里面临 2 个问题:
- 看起来我们不能有可选的绑定(对于滑块)
- 是否可以使用转换器将可选值转换为布尔值(对于 Toggle)?
到目前为止,在我看来,除了我的模型之外,我还声明了 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 的示例代码中重现]
这有点棘手,但是手动创建视图所需的 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
第一次通过视图事件更新其状态时仍然存在奇怪的行为。我暂时找不到避免这种情况的方法,但代码可能仍会对您有所帮助。
我找到了另一种不更改模型的方法,基本上,您只需在您感兴趣的视图中创建一个没有可选对象的绑定对象,然后将您的解包逻辑移到那里 即
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 = [=10=]
})
self.hide = value.wrappedValue == nil
}
}
我发现重载 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 = [=10=]
})
}
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)
}
}