当前位置: 首页 > 科技观察

详细讲解SwiftUI数据流是如何在View之间传递的

时间:2023-03-21 21:15:04 科技观察

作为一个声明式的UI框架,SwiftUI帮助我们处理了几乎所有的UI和数据的交互,这让我们不再需要关注数据变化时刷新UI的逻辑在与用户交互后更新数据。为了实现数据和UI的绑定,我们需要用到Swift的一些相关的属性包装器,将它们之间的关系描述给SwiftUI,那么我们开始吧。状态属性@State在上一篇文章中,当我们向数组中添加或删除元素时,列表会自动响应变化,正是因为@State用于标记View中的模型。structContentView:View{@Stateprivatevartitle:String=""varbody:someView{VStack{Text("\(title)")TextField("PleaseEnterTitle",text:$title)}}}用@State包裹的变量可以被SwiftUI使用读取值,通常是一些常量值,例如字符串或数字。当State包裹的属性发生变化时,SwiftUI会重新计算并绘制使用该属性的视图所在的整个视图层次结构。通常,变量所在的View的Body会被重绘。在此示例中,它指的是ContentView的主体。@State包裹的变量必须用private修饰,这个变量只能在当前view及其子View的body中使用。就像TextField中的$title一样,我们可以通过*$*前缀将变量绑定到另一个视图,这样就可以在另一个视图中修改变量。以下代码使用Toggle来控制Wi-Fi开关:structContentView:View{@StateprivatevarisOn=falsevarbody:someView{VStack{Text("Wi-FiState:\(isOn?"On":"Off")")Image(systemName:"\(isOn?"wifi":"wifi.slash")")Toggle("Wi-FiState",isOn:$isOn)}}}如上代码所示,我们在isOn属性和Togglecontrol它们之间建立了绑定。Toggle可以修改isOn的值,当isOn改变时,Text和Image的内容也会改变。StateBinding@state包裹的属性只在它所属的视图内部使用,所以当它的子视图要访问这个属性时,就要用到@binding。就像上面例子中使用的Toggle一样,我们将Text和Image放到一个自定义的View中。structWiFiView:View{@BindingvarisOn:Boolvarbody:someView{Text("Wi-FiState:\(isOn?"On":"Off")")Image(systemName:"\(isOn?"wifi":"wifi.slash")")}}这里我们使用@Binding来创建数据和接口之间的依赖关系。与@State不同的是,绑定的属性不被当前视图持有,绑定的值可以通过state属性值导出。这里如果把@Binding换成@State的话,WiFiView和它的父view都会有自己的isOn属性,一个修改不会影响另一个,这显然不是我们想要的结果。使用Combine框架的Publisher使用@State包装属性,只能在当前View或其子视图内部使用,并且state属性是临时的——因为state包装属性属于它所在的视图,当视图被销毁时相应的状态属性也会消失,这显然是不够的。另外,我们在开发过程中还要处理一些非接口的信息,比如Timer,Notification等,它们携带的信息通常需要更新接口。在这种情况下,将使用Combine中的Publisher。Combine是iOS13引入的,主要目的是处理App中的各种事件消息。如果你之前接触过RxSwift或者ReactiveCocoa,你应该很容易理解这个概念。它的原理是发布者和订阅者模型。classContact:ObservableObject{@Publishedvarname:String@Publishedvarage:Intinit(name:String,age:Int){self.name=nameself.age=age}}structContentView:View{@ObservedObjectvarxiaowang=Contact(name:"xiaowang",age:21)varbody:someView{VStack{Text("Xiaowang:\(xiaowang.name)")//这只是一个例子,一般不会在这里修改PublisherButton("ModifyContact"){xiaowang.name="小王》}}}}我们先创建一个遵守ObservableObject协议的联系人类,然后在SwiftUI视图中添加一个被ObservedObject包裹的变量,在body中使用这个变量,当@Published包裹的变量发生变化时,body将重新加载新值。如果您在观看WWDC2019的Combil介绍视频时发现BindableObject/didChange.send()/onReceive,这些内容现已被删除。只有类才能遵守ObservableObject协议,否则会报错Non-classtype'Contact'cannotconformtoclassprotocol'ObservableObject'在iOS14中,引入了一个新的@StateObject来丰富这个使用场景。它与ObservedObject的区别在于,当视图刷新时,ObservedObject包裹的属性会重置为初始值,而StateObject使用的属性则不会。大多数情况下都适合使用StateObject,除了一些必要的情况下需要使用ObservedObject。环境变量EnvironmentObjects除了上面列出的场景,假设我们的应用程序需要从一个页面跳转到另一个页面。这是一个很常见的场景,后面的页面会用到前面的一些页面。属性。通常可以这样做:NavigationLink(destination:nextView(aModel:aModel)){Text("Detail")}使用NavigationLink进行导航,destination是要弹出的页面,用当前页面的一个属性初始化.这种方式没有什么大问题,但是如果层数多了,后面的层会出现很多新的层,如果有反向传值,就会很复杂,容易出错——就像使用UIKit一样。为了解决这个问题,SwiftUI引入了EnvironmentObjects。//DataSource.swiftclassDataSource:ObservableObject{@Publishedvarcounter=0}//ContentView.swiftstructContentView:View{letdataSource=DataSource()varbody:someView{NavigationView{VStack{Button("Click"){dataSource.counter+=1}NavigationLink(目的地:ContactView()){Text("EnterNextPage")}}}.environmentObject(dataSource)}}//ContactView.swiftstructContactView:View{@EnvironmentObjectvardataSource:DataSourcevarbody:someView{Text("\(dataSource.counter)")}}EnvironmentObject和ObservedObject/StateObject的用法非常相似。首先,DataSource遵守ObservableObject协议,将要观察的属性counter封装在Publisher中。@EnvironmentObject包裹的属性会随着Published属性的变化而变化,视图会重新加载。.environmentObject是一个将属性注入环境变量的修饰符。如果在使用@EnvironmentObject之前没有将属性注入到环境变量中,就会出现本文总结的错误{%labeldanger@MissingEnvironmentObjectError:MissingEnvironmentObject%}中,我们了解了数据流向SwiftUI框架,主要涉及几个常用的属性包装器:@State@Binding@ObservedObject@StateObject@EnvironmentObject用法和适用场景,希望对大家有所帮助。