美文网首页
SwiftUI 中的状态管理

SwiftUI 中的状态管理

作者: 张_何 | 来源:发表于2024-12-17 11:21 被阅读0次
@State
  • @State 用于声明视图内部的可变状态,当状态发生变化时,SwiftUI会自动重新渲染依赖该状态的视图
struct ContentView: View {
    @State private var counter = 0
    
    var body: some View {
        VStack {
            Text("Counter: \(counter)")
            Button("Increment") {
                counter += 1
            }
        }
    }
}

上面点击“Increment” 按钮时Text中的内容会跟着变化

@Binding
  • @Binding 用于在试图之间传递状态的引用,它允许父视图将某个状态传递给子视图,并且在子视图中修改这个状态时会影响父视图中的状态
struct HomepageView: View {

    var body: some View {
        ParentView()
    }
}


struct ParentView: View {
    @State private var counter = 0
    
    var body: some View {
        Text("ParentView Counter: \(counter)")
        ChildView(counter: $counter)
    }
}

struct ChildView: View {
    @Binding var counter: Int
    
    var body: some View {
        Button("Increment in Child") {
            counter += 1
        }
    }
}

当点击ChildView中的"Increment in Child" 按钮时ParentView中的counter会跟着变化.

  • 当然也可以多级传递,比如下面我们把HomepageViewcounter 传递给 ParentViewcounter1, 然后再把ParentViewcounter2传递给ChildViewcounter2, 然后在ChildView中点击"Increment in Child" 按钮的时候,HomepageViewParentView 中的Text都会跟着变化
struct HomepageView: View {
    @State private var counter = 0

    var body: some View {
        Text("HomepageView Counter: \(counter)")
        ParentView(counter1: $counter)
    }
}


struct ParentView: View {
    @Binding var counter1: Int
    
    var body: some View {
        Text("ParentView Counter: \(counter1)")
        ChildView(counter2: $counter1)
    }
}

struct ChildView: View {
    @Binding var counter2: Int
    
    var body: some View {
        Button("Increment in Child") {
            counter2 += 1
        }
    }
}
@Published
  • @Publish 用于在ObervableObject内声明一个可观察的属性,该属性的值发生变化时,所有观察该对象的视图都会更新
  • @Publish 不是直接用于视图的状态管理,而是用于声明一个ObservableObject中的属性
    ,当该属性发生变化时,所有观察该对象的视图都会被更新。@Publish使得属性变成可观察的,并且当值发生变化时,视图会自动刷新
ObservableObject
  • ObservableObject 是一个协议,用于实现数据模型的可观察性。它是 SwiftUI 状态管理的一部分,使得模型对象能够在状态发生变化时通知依赖于它的视图进行更新。换句话说,ObservableObject 用于管理和存储应用程序中的数据,并确保在数据变化时,相关视图能够自动更新。
  • ObservableObject 协议要求实现一个或多个带有 @Published 标记的属性,当这些属性的值发生变化时,任何观察该对象的视图都会重新渲染。
@ObservedObject
  • ObserveObject用于观察外部对象的变化,通常与遵循ObservableObject协议的对象一起使用,当对象的任何@Published属性发生变化时,绑定视图会重新渲染。
class CounterModel: ObservableObject {
    @Published var counter = 0
}

struct ContentView: View {
    @ObservedObject var model = CounterModel()

    var body: some View {
        VStack {
            Text("Counter: \(model.counter)")
            Button("Increment") {
                model.counter += 1
            }
        }
    }
}

每次点击“Increment”按钮时,Text中的Counter就会跟着变化

@EnvironmentObject
  • @EnvironmentObject 用于在视图层级结构中传递和共享数据,它允许你在多个视图之间传递共享的可观察对象ObservableObject而不需要显式地将数据通过视图层级逐层传递。使用时,视图需要从环境中注入对象。
// 可观察对象,用来存储应用的状态
class UserSettings: ObservableObject {
    @Published var username: String = "Guest"
}

struct ContentView: View {
    @EnvironmentObject var userSettings: UserSettings  // 从环境中获取共享的数据

    var body: some View {
        VStack {
            Text("Hello, \(userSettings.username)")  // 显示用户名
            Button("Change Username in Content") {
                userSettings.username = "Content"  // 修改用户名,视图会自动更新
            }
        }
        .padding()
    }
}

struct AnotherView: View {
    @EnvironmentObject var userSettings: UserSettings

    var body: some View {
        VStack {
            Text("AnotherView Hello: \(userSettings.username)")
            Button("Change Username in Another View") {
                userSettings.username = "Another"
            }
            
            ThreeLevelView()
        }
    }
}

struct ThreeLevelView: View {
    @EnvironmentObject var userSettings: UserSettings
    
    var body: some View {
        VStack {
            Text("ThreeLevelView Hello: \(userSettings.username)")
            Button("Change Username in ThreeLevelView") {
                userSettings.username = "Three Level"
            }
        }
    }
}

struct ParentView: View {
    @EnvironmentObject var userSettings: UserSettings

    var body: some View {
        VStack {
            ContentView()
            AnotherView()
        }
    }
}

struct HomepageView: View {
    @StateObject private var userSettings = UserSettings()

    var body: some View {
        VStack {
            Text("HomePage Hello: \(userSettings.username)")
            Button("Change Username in HomePage") {
                userSettings.username = "HomePage"
            }
        }
        ParentView().environmentObject(userSettings)
    }
}
  • 上面我们看到尽管在ParentViewContentViewAnotherViewThreeLevelView中都是使用了userSettings,但是我们只在生成ParentView的时候注入了userSettings,就可以在其子View AnotherViewContentView 以及其孙View ThreeLevelView中就可以使用了。
@StateObject
  • @StateObject是用于创建和持有一个 ObservableObject实例的属性包装器。它用于在视图中创建和拥有一个 ObservableObject 实例,并确保视图在该对象的状态发生变化时自动更新。它确保对象在视图生命周期内保持持久性和唯一行,并不会因文视图重新渲染而丢失。
  • @ObservedObject不同,@StateObjectObservableObject实例的创建和持有者;而@ObservedObject不会负责初始化或管理对象, @ObservedObject只是@ObservedObject是实例的观察者,只负责观察对象的变化
class Counter: ObservableObject {
    @Published var value = 0
    private var name: String
    
    init(name: String) {
        self.name = name
        print("======\(name)")
    }
}

struct ContentView: View {
    @ObservedObject var counter: Counter
    
    var body: some View {
        VStack {
            FirstView()
            SecondView()
        }
    }
}

struct FirstView: View {
    @ObservedObject private var counter = Counter(name: "FirstView")
    
    var body: some View {
        Text("First View")
        ThirdView(name: "First", counter: counter)
    }
}

struct SecondView: View {
    @StateObject private var counter = Counter(name: "SecondView")

    var body: some View {
        Text("Second View")
        ThirdView(name: "Second", counter: counter)
    }
}

struct ThirdView: View {
    @ObservedObject var counter: Counter
    
    init(name: String, counter: Counter) {
        print("-----\(name)")
        self.counter = counter
    }
    
    var body: some View {
        Text("Third View counter: \(counter.value)")
    }
}



struct HomepageView: View {
    @ObservedObject private var counter = Counter(name: "HomepageView")
    
    var body: some View {
        VStack {
            ContentView(counter: counter)
            Text("HomePage Hello: \(counter.value)")
            Button("HomePage") {
                counter.value += 1
            }
        }
    }
}

点击Homepage Button两次看到的结果

======HomepageView
======FirstView
-----First
======SecondView
-----Second
======FirstView
-----First
======FirstView
-----First
======FirstView
-----First

上面可以看到SecondView 只在创建的时候打印了一次,而FirstView在每次点击“Homepage” button的时候都会打印

@Environment
  • @Environment属性包装器用于从视图的环境中获取系统提供的或由父视图注入的共享数据,而不需要显示地通过属性传递数据。它能让你访问与当前环境相关的信息,并在视图中进行响应式更新。
  • 基本语法: @Environment(\.key) var value@Environment(\.key) var value
    key:系统环境值的键或自定义的环境键,用来标识要获取的环境数据.
    value:存储在环境中的实际值,可以是任何类型.
  • SwiftUI 提供了一些常用的系统级别的环境数据,比如: 当前的颜色模式,设备的横向布局类
struct ContentView: View {
    @Environment(\.colorScheme) var colorScheme1 // 获取当前的颜色模式
    @Environment(\.horizontalSizeClass) var sizeClass // 获取当前设备的横向布局类

    var body: some View {
        Text("Current color scheme is \(colorScheme1 == .dark ? "Dark" : "Light")")
            .padding()
        if sizeClass == .compact {
            Text("Compact width class")
        } else {
            Text("Regular width class")
        }
    }
}
  • 也可以使用@Environment 注入自定义数据
struct HomepageView: View {
    var body: some View {
        VStack {
            ContentView()
        }
    }
}

// 定义一个简单的主题
struct AppTheme {
    var backgroundColor: Color
    var textColor: Color
}

struct AppThemeKey: EnvironmentKey {
    // 为这个环境键提供默认值
    static let defaultValue: AppTheme = AppTheme(backgroundColor: .white, textColor: .black)
}

extension EnvironmentValues {
    var appTheme: AppTheme {
        get { self[AppThemeKey.self] }
        set { self[AppThemeKey.self] = newValue }
    }
}

struct ContentView: View {
    let lightTheme = AppTheme(backgroundColor: .white, textColor: .black)
    let darkTheme = AppTheme(backgroundColor: .black, textColor: .white)
    
    @State private var isDarkMode = false

    var body: some View {
        VStack {
            Button("Toggle Theme") {
                isDarkMode.toggle()
            }
            .padding()
            ChildView()
        }
        .environment(\.appTheme, isDarkMode ? darkTheme : lightTheme) // 注入自定义的 AppTheme
    }
}

struct ChildView: View {
    @Environment(\.appTheme) var appTheme  // 访问注入的 AppTheme
    
    var body: some View {
        VStack {
            Text("This is a child view")
                .padding()
                .background(appTheme.backgroundColor)  // 使用环境中传递的主题背景色
                .foregroundColor(appTheme.textColor)  // 使用环境中传递的主题文字色
        }
    }
}

上面每次点击“Toggle Theme” button的时候,ChildView中的Text的文字和背景色就会改变

@AppStorage
  • @AppStorage 是一种属性包装器,用于将数据存储到应用的 UserDefaults 中,并且能够使得数据在视图间保持同步。它允许你轻松地从 UserDefaults 获取和存储数据,同时自动处理视图的更新。。它非常适合在应用程序中持久化小型设置或状态,如主题、语言选择等。当 UserDefaults 中的数据发生变化时,视图会自动重新渲染。
struct HomepageView: View {
    var body: some View {
        VStack {
            ContentView()
        }
    }
}

struct ContentView: View {

    @AppStorage("isDarkMode") var isDarkMode: Bool = false
    
    var body: some View {
        VStack {
            Button("Toggle Theme") {
                isDarkMode.toggle()
            }.padding()
            ChildView()
        }
    }
}

struct ChildView: View {
    @AppStorage("isDarkMode") var isDarkMode2: Bool = false
    
    var body: some View {
        VStack {
            Text(isDarkMode2 ? "DarkModel" : "Not DarkModel")
        }
    }
}

上面每次点击Toggle Theme 的时候,会修改isDarkModel的值,同时并保存到UserDefault中去,当UserDefault中的isDarkModel变化时,会触发ChildView重新渲染,这样就会导致ChildView 的内容跟着改变。

相关文章

网友评论

      本文标题:SwiftUI 中的状态管理

      本文链接:https://www.haomeiwen.com/subject/qjqhejtx.html