SwiftUI 作为一个声明式的 UI 框架,帮我们处理了几乎所有关於 介面UI 和 数据 之间的交互,通过数据流向对视图更新等操作,应用程序运行时,关於介面的修改,只能透过修改数据来间接完成,而不是直接对介面面进行修改操作,SwiftUI 又以单一数据源(single source of truth)为核心,构建了数据驱动状态更新的机制
而为了实现数据和UI 的绑定,需要透过不同的关键字属性包装器Property Wrapper 来向SwiftUI 描述它们之间的关系,用来进行状态管理
Property
一般在View Strcut 里定义的var
、let
属性,只能用来读取
@State 可变属性(可观察属性)
Struct 结构里的属性不能直接修改,需把**@State关键字放置到属性前修饰,该属性实际上会被放到Struct 的外部储存**起来,这意味着SwiftUI 能够随时销毁和重建Struct 而不会丢失属性的值
当被@State
包装的属性改变时,SwiftUI 会重新渲染使用到该属性的视图,系统会自动的重新计算View 的body 部分,构建出View Tree,由於View 都是Struct,SwiftUI 每次构建这个View Tree 都极快,这使得性能有很强的保障
视图经常会被系统重建,所以需要给属性赋一个预设值,若属性被标记为@State
,系统之後会使用储存的变量的值,而不是每次都使用初始化的预设值
被@State
包装的属性一定要用private
修饰,并且这个变量只能在当前View 的body 内修改,所以它的使用场景是只影响当前View 内部的变化的操作
当一个属性使用@State
关键词时,Swift 会为这个属性添加一些额外的方法,比如Boolean 类型会有toggle()
方法,用於在true/false 之间切换
范例:
struct ContentView: View {
@State private var isOn = false
var body: some View {
VStack {
Text("Switch State: \(isOn ? "On" : "Off")")
Button("change") {
isOn.toggle()
}
.padding()
}
}
}
@ObservedObject
一般情况下数据来自本地Local 端或者远端Remote API,这些数据预设与SwiftUI 没有任何关系,而我们需要建立数据与介面的依赖关系就要使用到@ObservedObject
和@Published
,@ObservedObject
允许外部进行使用和修改
@ObservedObject
告诉SwiftUI,这个物件是可以被观察的,里面含有被@Published
包装了的属性,而@ObservedObject
包装的物件必须是Class的物件,且必须实作ObservableObject
,不能是Struct
@ObservedObject
搭配着@Published
一起使用,允许我们创建出能够被自动观察的物件属性,SwiftUI 会自动监视观察这个属性,一旦发生改变,会自动修改与该属性绑定的介面
范例:
建立一个ObservedObject 的类别
class Contact: ObservableObject {
@Published var name: String
@Published var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
当被@Published
包装的变量改变时,body 会使用新值重新加载刷新页面
struct ContentView: View {
@ObservedObject var person = Contact(name: "Ryder", age: 27)
var body: some View {
VStack {
Text("name:\(person.name)")
Button("change") {
person.name = "Ryder2"
}
}
}
}
@StateObject
与@ObservedObject
功能类似,区别在於当view 刷新时被@ObservedObject
包装的属性会重置到初始值,而被StateObject使用的不会,就是说随着View 的创建被多次创建(复用)@ObservedObject
包装的属性将会多次创建(浪费资源),而@StateObject
包装的属性只会被创建一次(节约资源),@StateObject
基本上来说就是一个针对Class 的@State
升级版,因此除非在某些必要的情况下需要使用ObservedObject 之外,大多数情况都适用於StateObject
@Binding 属性绑定
声明一个属性是从外部获取的,并且与外部是共享的,作用是在保存状态的属性和更改数据的视图之间创建双向连接,将当前属性连接到储存在别处的单一数据源(single source of truth),而不是直接储存数据。将储存在别处的值的属性转换为引用类型,在使用时需要在变量名前加$符号
通常使用场景是把当前View 中的@State
值传递给其子View,要在子View 里修改上层数据,需透过修改@Binding
属性触发父视图@State
改变并重新计算body 後更新视图,可以实现反向数据流的功能(双向绑定),如果在子View 使用的是@State
值,子视图中的@State
属性会成为新的单一数据源,那麽子View 中对值的某个属性进行修改,父View 就不会得到变化,所以需要在子视图中使用@Binding
范例:
struct ContentView: View {
@State private var isWifiOn = false
var body: some View {
VStack {
Text("Wifi state: \(isWifiOn ? "On" : "Off")")
// 向下传递子视图的binding参数 isOn,透过$符号传递引用 isWifiOn
WifiView(isOn: $isWifiOn)
.padding()
}
}
}
struct WifiView:View {
// 引用外部传递的参数
@Binding var isOn:Bool
var body: some View{
VStack{
Button("wifi change") {
// 切换时,由於Binding 机制的作用,会修改引用的外部单一数据源
isOn.toggle()
}
}
}
}
@EnvironmentObject
主要是为了在整个应用程序中共享物件,在View 物件里通过environmentObject()
方法向下传递全局数据,底下所有的视图皆共享同一个环境物件并且可以监听变更
范例:
// 设定要共享的数据类别
class DataSource: ObservableObject {
@Published var counter = 0
}
struct ContentView: View {
let dataSource = DataSource()
var body: some View {
VStack {
Button("Click") {
// 改变共享的数据,其他有用此数据的视图也将重载
dataSource.counter += 1
}
DisplayView()
}
// 通过在View 使用`environmentObject()`方法向下传递全局数据
.environmentObject(dataSource)
}
}
struct DisplayView: View {
@EnvironmentObject var dataSource: DataSource
var body: some View {
Text("Click times:\(dataSource.counter)")
}
}
@Environment 系统环境数据
与@EnvironmentObject
作用不同,@Environment
是从不同硬体设置及软件环境中取出预设定义的值(系统内建属性),比如获得当前是深色模式还是正常模式,萤幕的大小等等
范例:
@Environment(\.colorScheme) var colorScheme: ColorScheme
if colorScheme == .light {
//正常模式画面
} else {
//深色模式画面
}
>>: Day10 Platform Channel - MethodChannel
什麽是 Google Apps Script ??。 今日要点: 》Google Apps Scr...
Q1. 什麽是 SSRF? SSRF (Server Side Request Forgery),也...
接下来的30天,我们会一起看MLOps的更多层面。从为什麽产业开始谈MLOps开始,以及其包含的技术...
人脸辨识建置的类型有两种: 第一种为使用云端计算,因需要使用云端计算,所以需要确保网路连线顺畅,在将...
文书工作只能用 windows 吗? 在求学过程中,作业常常要求使用微软的 office 全家桶来进...