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 重绘)参考链接。