SwiftUI中的@State、@StateObject、@Binding、@ObservedObject、@Environment、@EnvironmentObject等属性包装器的作用和用法

先用一个图来整体说明各个状态的作用与使用范围:

SwiftUI中的@State、@StateObject、@Binding、@ObservedObject、@Environment、@EnvironmentObject等属性包装器的作用和用法_第1张图片

SwiftUI 为我们的应用程序提供了 17 个属性包装器,每个属性包装器都提供不同的功能。知道使用哪个以及何时使用对正确处理至关重要,因此在本文中,我将向您介绍其中的每一个,并为您提供明确的使用指南。

参考链接:https://www.cocoaz.com/swiftui/example-appendix-a-demo2

  • @AppStorage 从 UserDefaults 读取和写入值。这拥有其数据。更多信息。
  • @Binding 引用另一个视图拥有的值类型数据。在本地更改绑定也会更改远程数据。这不拥有其数据。更多信息。
  • @Environment 允许我们从系统读取数据,例如配色方案,可访问性选项和特征集,但是如果需要,您可以在此处添加自己的密钥。这不拥有其数据。更多信息。
  • @EnvironmentObject 读取我们放入环境中的共享对象。这不拥有其数据。更多信息。
  • @FetchRequest 为特定实体启动核心数据获取请求。这拥有其数据。更多信息。
  • @FocusedBinding 旨在监视键窗口中的值,例如当前选定的文本字段。这不拥有其数据。
  • @FocusedValue 是 @FocusedBinding 的简单版本,不会为您解开绑定值。这不拥有其数据。
  • @GestureState 存储与当前正在进行的手势关联的值,例如您滑动了多远,除非在手势停止时它将重置为其默认值。这拥有其数据。更多信息。
  • @Namespace 创建一个动画名称空间,以允许匹配的几何效果,该效果可以由其他视图共享。这拥有其数据。
  • @NSApplicationDelegateAdaptor 用于创建类并将其注册为 macOS 应用程序的应用程序委托。这拥有其数据。
  • @ObservedObject 引用符合 ObservableObject 协议的外部类的实例。这不拥有其数据。更多信息。
  • @Published 附加到 ObservableObject 内的属性,并告诉 SwiftUI 它应该在更改此属性时刷新使用此属性的所有视图。这拥有其数据。更多信息。
  • @ScaledMetric 会读取用户的动态类型设置,并根据您提供的原始值向上或向下缩放数字。这拥有其数据。更多信息。
  • @SceneStorage 使我们可以保存和还原少量数据以进行状态还原。这拥有其数据。更多信息。
  • @State 允许我们在视图本地操作少量的值类型数据。这拥有其数据。更多信息。
  • @StateObject 用于存储符合 ObservableObject 协议的引用类型数据的新实例。这拥有其数据。更多信息。
  • @UIApplicationDelegateAdaptor 用于创建一个类并将其注册为iOS应用程序的应用程序委托。这拥有其数据。更多信息。

1.@Binding包装器的作用和用法

作用:@Binding用于在视图之间传递和共享可读写的值。它创建了一个对属性的引用,以便多个视图可以共享同一份数据,并且对数据的更改会在所有引用的地方生效。它做的事情是将值语义的属性“转换”为引用语义。对被声明为 @Binding 的属性进行赋值,改变的将不是属性本身,而是它的引用,这个改变将被向外传递,使用@state可以实现在当前view视图内的状态管理,但是如果需要将状态传递到子视图,并且实现双向绑定就需要使用@Binding来实现

示例:在下面的示例中,父视图通过 @Binding 将布尔值传递给子视图,以控制子视图的可见性。

struct ParentView: View {
    @State private var isChildViewVisible = false

    var body: some View {
        VStack {
            Toggle(isOn: $isChildViewVisible) {
                Text("Show Child View")
            }
            if isChildViewVisible {
                ChildView(isVisible: $isChildViewVisible)
            }
        }
    }
}

struct ChildView: View {
    @Binding var isVisible: Bool

    var body: some View {
        Text("Child View")
        Button(action: {
            isVisible = false
        }) {
            Text("Hide")
        }
    }
}

2.@Environment包装器的作用和用法

作用:使用 @Environment 属性包装器,你可以声明一个属性来获取特定环境变量的值,而无需手动传递它们。当环境变量的值发生变化时,相关的视图会自动更新。

示例:下面是一个使用 @Environment 属性包装器的示例代码:我们使用 @Environment(.colorScheme) 将 colorScheme属性声明为从环境中获取当前的颜色方案。根据颜色方案的值,我们设置不同的文本颜色和背景色。你可以尝试切换模拟器的外观(Light 或 Dark),以查看文本颜色和背景色是如何自动根据环境变量的值而更新的。

import SwiftUI

struct ContentView: View {
    @Environment(\.colorScheme) var colorScheme

    var body: some View {
        if colorScheme == .dark {
            Text("Dark Mode")
                .foregroundColor(.white)
                .background(Color.black)
        } else {
            Text("Light Mode")
                .foregroundColor(.black)
                .background(Color.white)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .previewLayout(.sizeThatFits)
    }
}

3.@State包装器的作用和用法

作用:@State 用于在视图中声明可变的状态属性,并自动更新视图。当使用 @State 标记属性时,在 SwiftUI 内部会被自动转换为一对 setter 和 getter,对这个属性进行赋值的操作将会触发 View 的刷新,它的 body 会被再次调用,底层渲染引擎会找出界面上被改变的部分,根据新的属性值计算出新的 View,并进行刷新。

示例:在下面的示例中,我们使用 @State 来声明一个可变的计数属性,点击按钮时,计数会增加并更新视图。

struct ContentView: View {
    @State private var count: Int = 0

    var body: some View {
        Button(action: {
            count += 1
        }) {
            Text("Count: \(count)")
        }
    }
}

4.@GestureState包装器的作用和用法

作用:@GestureState 用于跟踪手势的状态和进度。它用于创建自定义手势,并跟踪手势过程中的状态变化。

示例:在下面的示例中,我们使用 @GestureState 跟踪长按手势的状态,并根据手势的状态来改变视图的颜色。

struct ContentView: View {
    @GestureState private var isLongPressed = false

    var body: some View {
        Circle()
            .fill(isLongPressed ? Color.red : Color.blue)
            .frame(width: 100, height: 100)
            .gesture(
                LongPressGesture()
                    .updating($isLongPressed) { currentState, gestureState, _ in
                        gestureState = currentState
                    }
            )
    }
}

5.@ObservedObject包装器的作用和用法

作用:@ObservedObject用于观察引用类型对象的变化,并在对象更改时更新视图。它用于管理外部对象的状态,并在该对象发生变化时自动刷新视图。如果说 @State 是全自动驾驶的话,ObservableObject 就是半自动,它需要一些额外的声明。ObservableObject 协议要求实现类型是 class,它只有一个需要实现的属性:objectWillChange。在数据将要发生改变时,这个属性用来向外进行“广播”,它的订阅者 (一般是 View 相关的逻辑) 在收到通知后,对 View 进行刷新。
创建 ObservableObject 后,实际在 View 里使用时,我们需要将它声明为 @ObservedObject。这也是一个属性包装,它负责通过订阅 objectWillChange 这个“广播”,将具体管理数据的 ObservableObject 和当前的 View 关联起来。如果属性定义时加了@Published包装器,就可以省略objectWillChange,系统已经自动帮你实现了objectWillChange,@ObservedObject修饰的必须是遵守ObservableObject 协议的class对象,class对象的属性只有被@Published修饰时,属性的值修改时,才能被监听到

示例:在下面的示例中,我们创建一个 UserData 类,使用 @ObservedObject标记属性,在视图中观察和使用该对象的属性。

class UserData: ObservableObject {
    @Published var name: String = ""
}

struct ContentView: View {
    @ObservedObject var user = UserData()

    var body: some View {
        TextField("Enter your name", text: $user.name)
            .textFieldStyle(.roundedBorder)
        Text("Hello, \(user.name)!")
    }
}

6.@StateObject包装器的作用和用法

作用:@StateObject 用于在视图中创建一个持久化的可观察对象,并在视图的生命周期内保持持久性。它类似于@ObservedObject,但在对象生命周期中只会创建一次。

示例:在下面的示例中,我们创建一个 DataModel 类,并使用 @StateObject 在视图中创建和使用该对象。

class DataModel: ObservableObject {
    @Published var data: [String] = []
}

struct ContentView: View {
    @StateObject var dataModel = DataModel()

    var body: some View {
        VStack {
            Button(action: {
                dataModel.data.append("New Item")
            }) {
                Text("Add Item")
            }
            ForEach(dataModel.data, id: \.self) { item in
                Text(item)
            }
        }
    }
}

7.@EnvironmentObject包装器的作用和用法

作用:@EnvironmentObject用于在整个应用程序中共享全局环境对象。它允许在应用程序中的任何地方访问和使用该对象,而无需手动传递数据。在 SwiftUI 中,View 提供了 environmentObject(自定义的Object) 方法,来把某个 ObservableObject 的值注入到当前 View 层级及其子层级中去。在这个 View 的子层级中,可以使用 @EnvironmentObject 来直接获取这个绑定的环境值。@EnvironmentObject 修饰器是针对全局环境的。通过它,我们可以避免在初始 View 时创建 ObservableObject, 而是从环境中获取 ObservableObject

示例:在下面的示例中,我们创建一个 Person类,并使用 @EnvironmentObject 在视图中共享该对象。可以看出我们获取 p这个 ObservableObject 是通过 @EnvironmentObject 修饰器,但是在入口需要传入 .environmentObject(p) 。@EnvironmentObject 的工作方式是在 Environment 查找 Person 实例。

final class Person:ObservableObject{
  @Published var name = "我是个人"
   
}
struct ContentView: View {
    var body: some View {
        VStack{
            let p = Person()
            MapView().environmentObject(p)
        }
    }
}
​struct MapView: View {
    @EnvironmentObject var p:Person
    var body: some View {
        VStack{
            Text(p.name)
            Button("点我") { //添加一个按钮,指定标题文字为 First button
                p.name = "1234567890"
            }
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
     
        ContentView()
    }
}

​

@Environment与@EnvironmentObject区别:

@Environment 属性包装器只能获取已经存在于环境中的环境变量的值

@EnvironmentObject 属性包装器结合 ObservableObject 协议来创建和传递自定义的环境对象

@StateObject和@ObservedObject区别:

@SateObject针对引用类型设计,被View持有,当View更新时,实例不会被销毁,与State类似,使得View本身拥有数据,这使得 @StateObject 适用于那些需要持久状态且与视图密切相关的数据对象,比如页面导航、用户输入等。@StateObject告诉SwiftUI,当这个视图更新时,你希望保留这个对象的一个示例

@ObservedObject只是作为View的数据依赖,不被View持有,View更新时ObservedObject对象可能会被销毁,适合数据在SwiftUI外部存储,把@ObservedObject包裹的数据作为视图的依赖,比如数据库中存储的数据,当SwiftUI视图“更新”时,实际发生的是创建并显示视图的新示例。
这意味着当您通过@ObservableObject声明视图模型时,您将获得数据对象Model的一个新示例

你可能感兴趣的:(swiftui,ios,swift,SwitUI属性包装器)