SwiftUI 的状态管理

@State

被@State包装的值发生改变时,UI将被同时改变

struct ContentView: View {
    @State var title = "HelloWorld"

    var body: some View {
        VStack {
            Text(title)
            Button {
                title = "Good afternoon"
            } label: {
                Text("Change Value")
            }
        }

    }
}

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

@Binding

与 State 结合使用,双向绑定

struct ContentView: View {
    @State var number = 1

    var body: some View {
        VStack {
            VStack {
                BindingText(value: $number)
                Spacer().frame(height: 30)
                CommonText(value: number)
            }.border(.secondary)
            Button {
                number += 1
            } label: {
                Image(systemName: "plus")
            }
        }
    }
}

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

struct BindingText: View {
    // 对包装的值采用传址而不是传值,双向绑定很有用。
    @Binding var value: Int
    var body: some View {
        VStack {
            Text("\(value)")
                .bold()
                .foregroundColor(.gray)
            Button {
                value = 0
            } label: {
                Image(systemName: "eraser")
            }
        }
    }
}

struct CommonText: View {
    // 对包装的值采用传值
    var value: Int
    var body: some View {
        Text("\(value)")
            .bold()
            .foregroundColor(.gray)
    }
}

@ObservedObject

引用对象的属性值发生改变时,更新UI

struct ContentView: View {
    @ObservedObject var user = User(name: "John", age: 18)

    var body: some View {
        VStack {
            Text(user.name + String(user.welcome ?? "!"))
            Button {
                user.happlyBirthday()
            } label: {
                Text(String(user.age))
            }

        }
    }
}

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

// 可观察对象(继承自 ObservableObject)
class User: ObservableObject {
    var name: String
    var age: Int
    //仅当 welcome 属性改变时,才会触发UI更新操作
    @Published var welcome: String?
    
    init(name: String, age:Int) {
        self.name = name
        self.age = age
    }
    
    func happlyBirthday() {
        self.welcome = ", Happly Birthday"
    }
}

@EnvironmentObject

跨 view 层级传递数据

struct ContentView: View {
    @ObservedObject var user = User(name: "John", age: 18)

    var body: some View {
        VStack {
            UserInfo()
            Button {
                user.happlyBirthday()
            } label: {
                Text(String(user.age))
            }
        }
        .padding()
        // environmentObject 的作用是可以跨 view 传递数据
        .environmentObject(user)
    }
}

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

struct UserInfo: View {
    @EnvironmentObject var user: User

    var body: some View {
        Text(user.name + String(user.welcome ?? "!"))
    }
}

// 可观察对象(继承自 ObservableObject)
class User: ObservableObject {
    var name: String
    var age: Int
    // 仅当 welcome 属性改变时,才会触发UI更新操作
    @Published var welcome: String?

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    func happlyBirthday() {
        self.welcome = ", Happly Birthday"
    }
}

Binding 的使用

在 @State 或 @Binding 包装属性包装的变量前使用 $ 符号,返回该变量的 projectedValue(呈现值),该值的类型即为 Binding。

Binding 类型的值有两个作用:

① 传递给子 View,其属性用 @Binding 包装。

② 获取其包装值 wrappedValue,即变量本身的值

struct DetailView: View {
    @Binding var recipeId: Recipe.ID?
    @EnvironmentObject private var recipeBox: RecipeBox
    @State private var showDeleteConfirmation = false
    
    private var recipe: Binding<Recipe> {
        Binding {
            if let id = recipeId {
                return recipeBox.recipe(with: id) ?? Recipe.emptyRecipe()
            } else {
                return Recipe.emptyRecipe()
            }
        } set: { updatedRecipe in
            recipeBox.update(updatedRecipe)
        }
    }

    var body: some View {
        ZStack {
            if recipeBox.contains(recipeId) {
                // 传递给子View
                RecipeDetailView(recipe: recipe)
                    // 直接使用,获取变量本身的值 wrappedValue
                    .navigationTitle(recipe.wrappedValue.title)
                    #if os(iOS)
                    .navigationBarTitleDisplayMode(.inline)
                    #endif
                    .toolbar {
                        RecipeDetailToolbar(
                            recipe: recipe,
                            showDeleteConfirmation: $showDeleteConfirmation,
                            deleteRecipe: deleteRecipe)
                    }
            } else {
                RecipeNotSelectedView()
            }
        }
    }
    
    private func deleteRecipe() {
        recipeBox.delete(recipe.id)
    }
}

@StateObject

相比于 @ObservedObject,前者在UI重绘后不会重置(如父组件中 @State 修饰的变量更新,且该变量在UI中被使用,即会导致 UI 重绘)参考链接

参考文章

https://onevcat.com/2020/06/stateobject/

posted on 2022-12-05 23:18  Lemo_wd  阅读(147)  评论(0编辑  收藏  举报

导航