SwiftUI Tips: Preview 时如何让 Binding constant 可变

如果要在 Preview 便捷的使用 Binding 通常会使用 Binding.constant(value)。但是这个方式让人难受的地方在于因为是 constant value,所以 get 的时候永远都是初始值。这样我们在 View 内部修改了 Binding 的值,View 不会刷新体现出新的值。

比如有我们这样一个开关:

swift 复制代码
struct BindingPreview: View {
    @Binding var value: Bool
    
    var body: some View {
        Toggle(isOn: $value) {
            Text("开关")
        }
        .padding()
    }
}

在预览视图中,我们点击按钮就不会变成关闭状态。

swift 复制代码
#Preview("Constant:View不会更新") {
    BindingPreview(value: .constant(true))
}

基本思路:包在一个有状态的View里

最显而易见的思路是我们造一个 wrapper,声明一个状态值作为Binding的值。

swift 复制代码
struct Wrapper: View {
    @State var value: Bool
  
    var body: some View {
        BindingPreview(value: $value)
    }
}

#Preview("常规解法") {
    Wrapper(value: true)
}

但是以上的代码完全没有通用性。每次使用 Binding 时都要声明一个一次性 wrapper 还是挺繁琐。我们可以利用泛型把 wrapper 改造成一个通用的 StateWrapper。

swift 复制代码
struct StateWrapper<Value, Content:View>: View {
    @State var value: Value
    
    let content: (_ value: Binding<Value>) -> Content
    init(value: Value, @ViewBuilder content: @escaping (_ value: Binding<Value>) -> Content) {
        _value = .init(initialValue: value)
        self.content = content
    }
    var body: some View {
        content($value)
    }
}

#Preview("通用StateWrapper") {
    StateWrapper(value: true) { value in
        BindingPreview(value: value)
    }
}

这样 Binding 的值就可以像状态一样正常使用了。

多参数适配

有的时候还会出现预览的视图有多个 binding 参数,因此可以根据自己的场景写出几个多参数的辅助方法。

swift 复制代码
struct Bindings2<V0, V1, Content:View>: View {
    @State var v0: V0
    @State var v1: V1
    let content: (_ v0: Binding<V0>, _ v1: Binding<V1>) -> Content
    init(_ v0: V0, _ v1: V1, @ViewBuilder content: @escaping (_ v0: Binding<V0>, _ v1: Binding<V1>) -> Content) {
        _v0 = .init(initialValue: v0)
        _v1 = .init(initialValue: v1)
        self.content = content
    }
    var body: some View {
        content($v0, $v1)
    }
}

#Preview("多参数Binding") {
   Bindings2(true, "Mike") {
      AnotherComplexView(value: $0, name: $1)
   }
}

再简化一点:Binding.variable(value)

StateWrapper 在功能上已经达成我们的目的了。但是在使用的时候还是略显麻烦。参考 Binding.constant(value),我们也可以直接在 Binding 定义一个静态函数来提供 Binding。

swift 复制代码
extension Binding {
    public static func variable(_ value: Value) -> Binding<Value> {
        var state = value
        return Binding<Value> {
            state
        } set: {
            state = $0
        }
    }
}

使用的时候就非常有原生感了:

swift 复制代码
#Preview("自定义Variable") {
    BindingPreview(value: .variable(true))
}

但是 Binding.variable 有一个副作用,因为在函数里不能声明 @State,因此这个方式 binding value 的值改变后,不会触发整个 View 的重新刷新

举个例子:

swift 复制代码
struct BindingManyView: View {
    @Binding var value: Bool
    
    var body: some View {
        VStack {
            Toggle(isOn: $value) {
                Text("开关")
            }
            Text(value ? "true" : "false")
        }
        .padding()
    }
}

#Preview("自定义Variable副作用") {
    BindingManyView(value: .variable(true))
}

可以看到开关 UI 状态虽然会变化,但是下方的 Text 则没有刷新。

因此要选择 StateWrapper 还是 Binding.variable() 需要根据场景做一下选择。

相关推荐
货拉拉技术21 天前
货拉拉用户端SwiftUI踩坑之旅
ios·swiftui·swift
ZacJi24 天前
巧用 allowsHitTesting 自定义 SignInWithAppleButton
ios·swiftui·swift
刘争Stanley1 个月前
SwiftUI 是如何改变 iOS 开发游戏规则的?
ios·swiftui·swift
1024小神1 个月前
在swiftui中使用Alamofire发送请求获取github仓库里的txt文件内容并解析
ios·github·swiftui
大熊猫侯佩1 个月前
SwiftUI 撸码常见错误 2 例漫谈
swiftui·xcode·tag·tabview·preview·coredata·fetchrequest
东坡肘子1 个月前
肘子的 Swift 周报 #063|异种肾脏移植取得突破
swiftui·swift·apple
恋猫de小郭1 个月前
什么?Flutter 可能会被 SwiftUI/ArkUI 化?全新的 Flutter Roadmap
flutter·ios·swiftui
靴子学长1 个月前
iOS + watchOS Tourism App(含源码可简单复现)
mysql·ios·swiftui
hxx2212 个月前
iOS swift开发系列--如何给swiftui内容视图添加背景图片显示
ios·swiftui·swift
胖虎12 个月前
SwiftUI - (十九)组合视图
ios·swiftui·swift·组合视图