有没有办法使用 SwiftUI 将选项绑定到 Toggle / Slider
Posted
技术标签:
【中文标题】有没有办法使用 SwiftUI 将选项绑定到 Toggle / Slider【英文标题】:Is there a way to bind an optional to a Toggle / Slider with SwiftUI 【发布时间】:2019-06-21 06:06:17 【问题描述】:我的数据模型中有一个UInt?
(可选)属性,我正在尝试使用SwiftUI 绑定到Toggle
和Slider
。我正在尝试这样的事情:
maximumRingsShownCount
的值为 4(不是 nil),那么切换开关应该是on,并且值绑定到滑块。
maximumExpandedRingsShownCount
值为 nil,则切换应关闭且滑块不显示。
我在这里面临两个问题:
-
看起来我们不能有可选绑定(用于 Slider)
是否可以使用转换器将可选项转换为布尔值(用于切换)?
到目前为止,在我看来,除了我的模型之外,我还声明了 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的示例代码中重现]
【问题讨论】:
您能否添加 预期 它的外观以及 SwiftUI 抛出的错误? UI 布局正确,在问题中。对于错误,如果我将一个可选参数传递给 Slider,我会得到:无法转换“UInt”类型的值?到预期的参数类型'Binding<_>',并且对于Toggle,绑定Double 没有意义?到 Binding我发现重载 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)
【讨论】:
这是一个非常方便的解决方案。【参考方案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
第一次通过视图事件更新其状态时仍有一个奇怪的行为。我暂时找不到避免这种情况的方法,但代码可能仍然对您有所帮助。
【讨论】:
谢谢,这正是正在寻找的“转换”。我将这些计算属性(sliderBinding、toggleBinding)包装到我的模型的扩展中,看起来它运行良好。【参考方案3】:我找到了另一种不更改模型的方法,基本上,您只需在您感兴趣的视图中创建一个没有可选选项的 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
【讨论】:
以上是关于有没有办法使用 SwiftUI 将选项绑定到 Toggle / Slider的主要内容,如果未能解决你的问题,请参考以下文章
SwiftUI |警告:绑定首选项 _ 尝试每帧更新多次。可能的原因?