是否可以使用SwiftUI

时间:2019-06-21 06:06:17

标签: ios swift iphone user-interface swiftui

我的数据模型中有一个UInt?(可选)属性,我试图通过SwiftUI绑定到ToggleSlider。我正在尝试达到这样的目标:

  • maximumRingsShownCount的值为4(而不是nil),那么切换应为 on ,并且该值绑定到滑块。
  • maximumExpandedRingsShownCount的值为nil,则切换开关应关闭,并且不会显示滑块。

Toggle and Sliders rendered with SwiftUI

我在这里面临2个问题:

  1. 似乎我们没有用于滑块的可选绑定
  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布局正确,但是我仍然遇到上述问题,因为:

  1. haveMaximumRingsShownCount状态未绑定到我的config.maximumRingsShownCount模型为零的状态
  2. 滑块当前仅显示一个常量,并且未绑定到展开的config.maximumRingsShownCount属性上

关于如何使用可选选项解决这些问题的任何想法?

[可以在https://github.com/lludo/SwiftSunburstDiagram的示例代码中复制它]

3 个答案:

答案 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处理UIntDouble值之间的转换。

注意: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
    }
}