SwiftUI 属性包装器

285 阅读3分钟

@State

@State 应该仅用于简单的值类型,如字符串、整数、布尔值或结构体。


@State priate var isShow: Bool = false

@StateObject

@StateObject 用于在视图中声明和管理一个 ObservableObject。这是一个引用类型,可以包含复杂的数据和业务逻辑。@StateObject 确保对象的生命周期由 SwiftUI 管理,只有在视图创建时初始化一次。它适用于需要观察和响应状态变化的复杂对象。

  1. 定义一个遵循 ObservableObject 协议的类:
class UserSettings: ObservableObject {
    @Published var username: String = "Guest"
}
  1. 在视图中使用 @StateObject 管理 UserSettings 对象:
struct ContentView: View {
    @StateObject private var settings = UserSettings()

    var body: some View {
        VStack {
            Text("Username: \(settings.username)")
            Button("Change Username") {
                settings.username = "John Doe"
            }
        }
    }
}

使用 @State 来管理视图的简单本地状态。

使用 @StateObject 来管理需要观察的复杂对象的状态,确保它们在视图的生命周期内只初始化一次。

@Bind

可以将值类型的属性改变为引用类型传递值。在子视图中修改值, 可以触发父视图中 @State 修饰的对应属性的值, 从而可以触发父视图的body属性, 重新渲染UI。

在 Swift 5.1 中,对一个由 @ 符号修饰的属性,在它前面使用 $ 所取得的值,被称为投影属性 (projection property)。

struct SubView: View {
    @Binding var isEnable: Bool
    var body: some View {
        Text("View1")
    }
}

struct ParentView: View {
    @State var isEnable: Bool
    
    var body: some View {
        SubView(isEnable: $isEnable)
    }
}

ObservableObject协议

用来修饰类类型, 需要结合 @Published 和 @EnvironmentObject 使用, 见下面

@Published 和 @ObservedObject

可以被 SwiftUI 观察的属性。或者叫绑定的属性  

class Model: ObservableObject {
    @Published var title: String = "hello"
}

struct CustomView: View {
    @ObservedObject var model: Model

    var body: some View {
        Text(model.title)
    }
}

@ObservedObject 和 @StateObject 的区别

  1. 生命周期管理

• @StateObject: 视图创建和管理对象的生命周期。确保对象在视图的生命周期内只初始化一次。

• @ObservedObject: 观察一个外部管理的对象的变化,不管理对象的生命周期。

  1. 使用场景

• @StateObject: 用于视图本身创建和拥有的对象。

• @ObservedObject: 用于引用和观察视图外部创建和传入的对象。

@EnvironmentObject

如同单例对象, 进行页面之间的数据传递, 使用环境对象, 你可以在整个程序的任意页面设置和读取该对象。

class Model: ObservableObject {
    var title: String = "hello"
}

struct CustomView: View {
    @EnvironmentObject var model: Model
    
    var body: some View {
        Text(model.title)
    }
}

@Environment

  • @EnvironmentObject 和 @Environment 都可以自在环境中存储和读取数据 但是 @Environment 需要根据预定义的键, 获取对应的值。

  • 并且 @Environment 可以获取系统定义的固定属性。

要使用 @Environment, 必须要先定义一个遵循EnvironmentKey 协议的结构体。

private struct MyEnvironmentKey: EnvironmentKey {
    static let defaultValue: String = "Default value"
}

extension EnvironmentValues {
    var myCustomValue: String {
        get { self[MyEnvironmentKey.self] }
        set { self[MyEnvironmentKey.self] = newValue }
    }
}

struct ContentView: View {
    @Environment(\.myCustomValue) var customValue: String
    var body: some View {
        VStack {
            Text(customValue)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .environment(\.myCustomValue, "My custom value")
    }
}

@AppStorage

定义一个属性, 它拥有一个默认值, 并且使用了@AppStorage 属性包装器, 这样对该属性的存储和读取操作, 都将映射到键名为 nickname 的UserDefaults 中。

@AppStorage("nickname") var nickName: String = "Fate"
struct ContentView: View {

    @AppStorage("nickname") var nickName: String = "Fate"

    var body: some View {
        VStack(spacing: 20) {
            Text(nickName)
            Button {
                nickName = "Cris Paul"
                print(UserDefaults.standard.value(forKey: "nickname") ?? "")
            } label: {
                Text("Change nickName")
            }
        }
        .padding()
    }
}

@SceneStorage

该属性的值被保存在不同的场景中, 应用于 iPad多个场景中使用不同的值

struct ContentView: View {
    @SceneStorage("nickname") var nickname: String = ""
    var body: some View {
        VStack(spacing: 20) {
            Text(nickname)
            Button {
                self.nickname = "Hobe"
            } label: {
                Text("change nickname Hobe")
            }

            Button {
                self.nickname = "Jerry"
            } label: {
                Text("change nickname Jerry")
            }
        }
        .padding()
    }
}