SwiftUI之View Tree实战1
2020/7/15 23:10:12
本文主要是介绍SwiftUI之View Tree实战1,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
在之前的两篇文章中,讲解了高层次的视图如何获取低层次视图信息的方法,在本篇文章中,我将给大家演示这些技术在开发中的实际用处。
本篇文章的主要思想来自swiftui-lab.com/communicati…,我并不会对原作者的文章做一个简单的翻译,而是把他的思想进行一个总结,用另一种更简单,更容易理解的方式表达出来。
我们先看一下最终的效果图:
细心的读者应该发现了,左边较小的视图正是右边视图的一个预览,仿佛镜子一般,把右边视图的变化映射出来。
其实,这个效果非常有意思,如果你不了解我们之前讲解的技术,实现这个效果对你来说实在是太难了,这就是我一直想表达的一个观点,某些功能或者动画,在SwiftUI中的实现实在是太简单了。
要实现上述的功能,整体的步骤为:
- 设计需要传递的数据结构,这些信息会从子view传递到上层的view中
- 通过modifier绑定数据
- 根据数据生成视图
MyPreferenceData
struct MyPreferenceData: Identifiable { let id = UUID() let viewType: ViewType let bounds: Anchor<CGRect> func getColor() -> Color { switch self.viewType { case .parent: return Color.orange.opacity(0.5) case .son(let c): return c default: return Color.gray.opacity(0.3) } } func show() -> Bool { switch self.viewType { case .parent: return true case .son: return true default: return false } } } 复制代码
在我们这个例子中,我们需要知道3种类型view的位置信息:
enum ViewType: Equatable { case parent case son(Color) case miniMapArea } 复制代码
其中parent
对应的是下图的view:
son(Color)
对应下图的view:
miniMapArea
对应左边灰色的视图,我们知道了这些信息后,才能把右边的视图映射到左边。
MypreferenceKey
struct MypreferenceKey: PreferenceKey { typealias Value = [MyPreferenceData] static var defaultValue: Value = [] static func reduce(value: inout [MyPreferenceData], nextValue: () -> [MyPreferenceData]) { value.append(contentsOf: nextValue()) } } 复制代码
通过这段代码,我们声明了一个MypreferenceKey,然后需要把每个view需要携带的信息通过这个key进行绑定,为了方便计算,我们把每个view的信息放到了一个数组中[MyPreferenceData]
。
DragableView
右边视图中的彩色块支持拖拽手势改变自身的frame,我们需要单独将其封装成一个view:
struct DragableView: View { let color: Color @State private var currentOffset: CGSize = CGSize.zero @State private var preOffset: CGSize = CGSize(width: 100, height: 100) var w: CGFloat { self.currentOffset.width + self.preOffset.width } var h: CGFloat { self.currentOffset.height + self.preOffset.height } var body: some View { RoundedRectangle(cornerRadius: 5) .foregroundColor(color) .frame(width: w, height: h) .anchorPreference(key: MypreferenceKey.self, value: .bounds) { anchor in [MyPreferenceData(viewType: .son(color), bounds: anchor)] } .gesture( DragGesture() .onChanged { (value: DragGesture.Value) in self.currentOffset = value.translation } .onEnded { _ in self.preOffset = CGSize(width: w, height: h) self.currentOffset = CGSize.zero } ) } } 复制代码
这段代码值得关注的有2点:
- w和h的计算
.anchorPreference
:绑定数据
MiniMap
struct MiniMap: View { let geometry: GeometryProxy let preferences: [MyPreferenceData] var body: some View { guard let parentAnchor = preferences.first(where: { $0.viewType == .parent })?.bounds else { return AnyView(EmptyView()) } guard let miniMapAreaAnchor = preferences.first(where: { $0.viewType == .miniMapArea })?.bounds else { return AnyView(EmptyView()) } let factor = geometry[parentAnchor].width / (geometry[miniMapAreaAnchor].width - 10) let miniMapAreaPosition = CGPoint(x: geometry[miniMapAreaAnchor].minX, y: geometry[miniMapAreaAnchor].minY) let parentPosition = CGPoint(x: geometry[parentAnchor].minX, y: geometry[parentAnchor].minY) return AnyView(miniMapView(factor, miniMapAreaPosition, parentPosition)) } func miniMapView(_ factor: CGFloat, _ miniMapAreaPosition: CGPoint, _ parentPosition: CGPoint) -> some View { ZStack(alignment: .topLeading) { ForEach(preferences.reversed()) { pref in if pref.show() { self.rectangleView(pref, factor, miniMapAreaPosition, parentPosition) } } } .padding(5) } func rectangleView(_ pref: MyPreferenceData, _ factor: CGFloat, _ miniMapAreaPosition: CGPoint, _ parentPosition: CGPoint) -> some View { return Rectangle() .fill(pref.getColor()) .frame(width: self.geometry[pref.bounds].width / factor, height: self.geometry[pref.bounds].height / factor) .offset(x: (self.geometry[pref.bounds].minX - parentPosition.x) / factor + miniMapAreaPosition.x, y: (self.geometry[pref.bounds].minY - parentPosition.y) / factor + miniMapAreaPosition.y) } } 复制代码
上边的这么代码,只为实现下边图片上的view:
其中,大部分代码是非常容易理解的,只有两个地方用到了一点点算法。
第一个是计算let factor = geometry[parentAnchor].width / (geometry[miniMapAreaAnchor].width - 10)
,表示右边到左边的映射因子,大家看我画的示意图就能明白了:
第二个则是计算彩色块在父view中的相对位置,我们就不做过多解释了。
overlayPreferenceValue
最后,我们把上边的代码组合起来:
struct ContentView: View { var body: some View { HStack { RoundedRectangle(cornerRadius: 5) .foregroundColor(Color.gray.opacity(0.5)) .frame(width: 250, height: 300) .anchorPreference(key: MypreferenceKey.self, value: .bounds) { anchor in [MyPreferenceData(viewType: .miniMapArea, bounds: anchor)] } ZStack(alignment: .topLeading) { VStack { HStack { DragableView(color: .green) DragableView(color: .blue) DragableView(color: .pink) } HStack { DragableView(color: .black) DragableView(color: .white) DragableView(color: .purple) } } } .frame(width: 550, height: 300) .background(Color.orange.opacity(0.5)) .transformAnchorPreference(key: MypreferenceKey.self, value: .bounds, transform: { $0.append(contentsOf: [MyPreferenceData(viewType: .parent, bounds: $1)]) }) } .overlayPreferenceValue(MypreferenceKey.self) { value in GeometryReader { proxy in MiniMap(geometry: proxy, preferences: value) } } } } 复制代码
注意: .overlayPreferenceValue
表示会把视图放到最上层,如果想放到最下层,则使用.backgroundPreferenceValue
。
transformAnchorPreference
在这里我想大概讲一个transformAnchorPreference的用法,当视图的关系只有一层的时候,如下图所示:
我们通常是不需要transformAnchorPreference的,只需要在子view上通过.anchorPreference
绑定数据即可,除非要传递的信息不只一个,比如通过.anchorPreference
传递了.bounds
,还想传递.topLeading
,那么这时就需要通过transformAnchorPreference把.topLeading
传递过去。代码类似于这样:
.anchorPreference(key: MypreferenceKey.self, value: .bounds) { anchor in [MyPreferenceData(viewType: .miniMapArea, bounds: anchor)] } .transformAnchorPreference(key: MypreferenceKey.self, value: .topLeading, transform: { ... } 复制代码
如果视图的层级很深,则必须使用transformAnchorPreference来处理,否则系统就获取不到更深层次的Preference。
系统在遍历Preference的时候,采用了类似递归的方式,也可以认为是深度优先算法,如果某个父类也写了Preference,则系统不会遍历子view的Preference,这种情况只有当某个父view写了transformAnchorPreference,系统才会往更深层次去获取Preference。
关于上边这句话的解读,大家自己去理解吧,因为这也是我的猜测,不一定正确。
总结
在SwiftUI中,Preference绝对是一柄利器,大家应该重视起来这项技术。
本文源代码:NestedViwsDemo.swfit
SwiftUI集合:FuckingSwiftUI
这篇关于SwiftUI之View Tree实战1的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2022-10-05Swift语法学习--基于协议进行网络请求
- 2022-08-17Apple开发_Swift语言地标注释
- 2022-07-24Swift 初见
- 2022-05-22SwiftUI App 支持多语种 All In One
- 2022-05-10SwiftUI 组件参数简写 All In One
- 2022-04-14SwiftUI 学习笔记
- 2022-02-23Swift 文件夹和文件操作
- 2022-02-17Swift中使用KVO
- 2022-02-08Swift 汇编 String array
- 2022-01-30SwiftUI3.0页面反向传值