网站网页设计在哪找,网站平台建设思路,王湛简历江苏,wordpress本地调试修改域名概览
在 SwiftUI 中#xff0c;正是自定义视图让我们的 App 变得与众不同#xff01;然而#xff0c;除了传统的视图接口定义方式以外#xff0c;我们其实还可以有更“银杏化”的选择。 如上图所示#xff1a;对于 SubView 子视图所需的参数我们一开始并没有操之过急正是自定义视图让我们的 App 变得与众不同然而除了传统的视图接口定义方式以外我们其实还可以有更“银杏化”的选择。 如上图所示对于 SubView 子视图所需的参数我们一开始并没有操之过急而是随后再以独立、灵活的方式将其传入到了 SubView 中这是怎么做到的呢 在本篇博文中您将学到如下内容 概览1. 一个简单的视图需求2. “传统”的调用方式3. 灵动的方式按需且独立4. 再次验证 SwiftUI 视图状态的稳定性总结 闲言少叙Let‘s go 1. 一个简单的视图需求
我们需要创建一个子视图它用来显示 Model 可观察对象的内容同时包括一个界面是否展开的状态并且可以自定义用户点击的行为
Observable
class Model {var name hopyvar power 5
}struct SubView: View {let model Model()Binding var isExpanding: Boolvar tapHandler: (()-Void)?//...
}从上面代码中可以清楚的看到SubView 子视图包含一个 Model 可观察对象并且还有 isExpanding 和 tapHandler 属性来分别表示自身展开的状态和用户点击时执行的代码。
我们可以这样实现 SubView 的 body
var body: some View {VStack {Text(model.name).font(.largeTitle.weight(.bold))if isExpanding {Divider()HStack {Text(POW: \(model.power)).foregroundStyle(.red).font(.headline.weight(.heavy))Spacer()Button(Add POW!) {model.power 1}.buttonStyle(.borderedProminent)}}}.onTapGesture {tapHandler?()}.padding().background(Color.black.opacity(0.2), in: RoundedRectangle(cornerRadius: 15.0)).overlay {RoundedRectangle(cornerRadius: 15).stroke(.black, lineWidth: 5.0)}.shadow(radius: 5)
}2. “传统”的调用方式
现在已经定义好了 SubView 视图我们可以这样在主视图中创建并使用它
State var isExpanding falseSubView(isExpanding: $isExpanding) {print(OK)
}SubView 的运行界面如下图所示 如上代码我们在创建 SubView 子视图时就需要将它所有必要的传入参数都考虑周全。
当然这样本身并没有什么不妥。只不过假若视图包含海量传入参数可能会出现一些不“银杏化”的地方
在视图创建时就需要考虑到它所有的传入参数即使有些可以暂时“忽略不计”在视图创建时就需要绞尽脑汁让这一坨冗长的传入参数在代码缩进和排版上看起来不那么“毛骨悚然”无法清晰的隔离视图自身创建和其状态创建的不同逻辑
那么除了视图“传统”的接口设计方式之外我们是否还有其它的解决方案呢
答案是肯定的
3. 灵动的方式按需且独立
回忆一下 SwiftUI 中视图的本质它其实只是状态的函数它本身很“廉价”更重要的是它是一个值对象。
这意味着我们可以随时创建它们的拷贝并改变拷贝所包含的属性然后再用修改后的拷贝替换原有的视图。
首先我们将 SubView 定义修改为如下形式
struct SubView: View {let model Model()private var isExpanding falseprivate var tapHandler: (()-Void)?
}这样做的好处是在 SubView 创建时无需传入任何参数我们完全将 SubView 自身和其状态分开了。
注意在上面代码中我们用 private 关键字修饰了它的各个属性那么我们必须找到随后改变它们的方法这该如何是好呢
因为私有属性只能在类型内部读写但类型扩展显然属于“内部”这一范畴所以我们可以在 SubView 的扩展中大展拳脚
extension SubView {func isExpanding(_ expanding: Bool) - Self {var view selfview.isExpanding expandingreturn view}func tapHandler(_ handler: escaping ()-()) - Self {var view selfview.tapHandler handlerreturn view}
}可以看到在上面 SubView 视图的扩展方法中我们像讨论过的那样显式拷贝了 SubView 对象的实例然后更改它的属性最后返回了更改后的视图。
现在我们可以这样创建 SubView 视图了
struct ContentView: View {State var isExpanding falsevar body: some View {NavigationStack {VStack {SubView().isExpanding(isExpanding).tapHandler {withAnimation(.snappy) {isExpanding.toggle()}}Button(Expanding!) {withAnimation(.bouncy) {isExpanding.toggle()}}.padding(.top, 100)}.padding()}}
}于是乎我们可以“赤裸裸的” 让 SubView 先诞生然后根据需要再以视图扩展的方式为其“注入”必要的参数。这样我们就可以有的放矢的将重点放在视图的某些属性上创建逻辑会更加清晰明了。
4. 再次验证 SwiftUI 视图状态的稳定性
如果小伙伴们观察的足够仔细就会发现上述代码每次子视图的展开属性isExpanding发生改变时其 Model 的 power 值就会被重置 这是因为每次 isExpanding 属性改变时 SubView 自身的重建也会导致其 Model 对象的重建。
在 Swift 5.9 新 Observable 对象在 SwiftUI 使用中的陷阱与解决 这篇博文中我们进行过 SwiftUI 视图 Observable 对象稳定性的讨论。我们得出的一个重要结论是如果想要 Observable 对象保持稳定必须将它用状态来承载
在本案例中为了达到这一目的我们可以有两种方法
在主视图中将 Model 实例传递到 SubView 中或者在 SubView 中用 State 修饰 Model 属性
这里我们采用第二种方法将 SubView 中的 model 对象用 State 属性包装器修饰
struct SubView: View {State var model Model()//...
}最后运行看一下结果 看到了吗现在无论 SubView 自身如何变化我们的 Model 状态都不会“始乱终弃”它的内容始终保持一致棒棒哒
总结
在本篇博文中我们讨论了 SwiftUI “传统”的视图接口定义在具有海量传入参数时的一些不便之处并且用更加“低耦合”的“环保”方法改善了这一情况。相信现在小伙伴们对于 SwiftUI 中视图的构建会有更写意、更灵活的方式啦
感谢观赏再会