Day10 SwiftUI 03 - SwiftUI 数据流

SwiftUI 数据流

SwiftUI 作为一个声明式的 UI 框架,帮我们处理了几乎所有关於 介面UI数据 之间的交互,通过数据流向对视图更新等操作,应用程序运行时,关於介面的修改,只能透过修改数据来间接完成,而不是直接对介面面进行修改操作,SwiftUI 又以单一数据源(single source of truth)为核心,构建了数据驱动状态更新的机制

而为了实现数据和UI 的绑定,需要透过不同的关键字属性包装器Property Wrapper 来向SwiftUI 描述它们之间的关系,用来进行状态管理

  • Property

    一般在View Strcut 里定义的varlet属性,只能用来读取

  • @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 {
      //深色模式画面
    }
    

<<:  Day11 CSS基本说明

>>:  Day10 Platform Channel - MethodChannel

【Day 2】Google Apps Script - 平台介绍

什麽是 Google Apps Script ??。 今日要点: 》Google Apps Scr...

【第十五天 - SSRF】

Q1. 什麽是 SSRF? SSRF (Server Side Request Forgery),也...

学习MLOps前暖身操:why, what, who?

接下来的30天,我们会一起看MLOps的更多层面。从为什麽产业开始谈MLOps开始,以及其包含的技术...

人脸辨识-day14 系统建置

人脸辨识建置的类型有两种: 第一种为使用云端计算,因需要使用云端计算,所以需要确保网路连线顺畅,在将...

day13_Linux Arm 的文书之旅

文书工作只能用 windows 吗? 在求学过程中,作业常常要求使用微软的 office 全家桶来进...