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() 需要根据场景做一下选择。

相关推荐
初级代码游戏16 小时前
iOS开发 SwiftUI 14:ScrollView 滚动视图
ios·swiftui·swift
初级代码游戏18 小时前
iOS开发 SwitftUI 13:提示、弹窗、上下文菜单
ios·swiftui·swift·弹窗·消息框
zhyongrui21 小时前
托盘删除手势与引导体验修复:滚动冲突、画布消失动画、气泡边框
ios·性能优化·swiftui·swift
zhyongrui2 天前
SnipTrip 发热优化实战:从 60Hz 到 30Hz 的性能之旅
ios·swiftui·swift
大熊猫侯佩3 天前
赛博深渊(上):用 Apple Foundation Models 提炼“禁忌知识”的求生指南
llm·swiftui·大语言模型·foundationmodel·apple ai·apple 人工智能·summarize
zhyongrui4 天前
SwiftUI 光晕动画性能优化:消除托盘缩放卡顿的实战方案
ios·性能优化·swiftui
大熊猫侯佩5 天前
星际穿越:SwiftUI 如何让 ForEach 遍历异构数据(Heterogeneous)集合
swiftui·swift·遍历·foreach·any·异构集合·heterogeneous
符哥20085 天前
对比ArkTsUI和Flutter和 SwiftUI 和Jetpack Compose四个框架语法及使用场景。
flutter·ios·swiftui
大熊猫侯佩6 天前
越狱沙盒:SwiftUI fileImporter 的“数据偷渡”指南
swiftui·url·沙箱·sandbox·readfile·file importer·uniformtype
大熊猫侯佩11 天前
拯救巴别塔:WWDC24 全新 Translation API 实战
swiftui·wwdc·language·coreml·translation api·翻译接口·translationsess