SwiftUI 中的狀態(tài)管理

@State
  • @State 用于聲明視圖內(nèi)部的可變狀態(tài),當(dāng)狀態(tài)發(fā)生變化時(shí),SwiftUI會(huì)自動(dòng)重新渲染依賴該狀態(tài)的視圖
struct ContentView: View {
    @State private var counter = 0
    
    var body: some View {
        VStack {
            Text("Counter: \(counter)")
            Button("Increment") {
                counter += 1
            }
        }
    }
}

上面點(diǎn)擊“Increment” 按鈕時(shí)Text中的內(nèi)容會(huì)跟著變化

@Binding
  • @Binding 用于在試圖之間傳遞狀態(tài)的引用,它允許父視圖將某個(gè)狀態(tài)傳遞給子視圖,并且在子視圖中修改這個(gè)狀態(tài)時(shí)會(huì)影響父視圖中的狀態(tài)
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
        }
    }
}

當(dāng)點(diǎn)擊ChildView中的"Increment in Child" 按鈕時(shí)ParentView中的counter會(huì)跟著變化.

  • 當(dāng)然也可以多級(jí)傳遞,比如下面我們把HomepageViewcounter 傳遞給 ParentViewcounter1, 然后再把ParentViewcounter2傳遞給ChildViewcounter2, 然后在ChildView中點(diǎn)擊"Increment in Child" 按鈕的時(shí)候,HomepageViewParentView 中的Text都會(huì)跟著變化
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內(nèi)聲明一個(gè)可觀察的屬性,該屬性的值發(fā)生變化時(shí),所有觀察該對(duì)象的視圖都會(huì)更新
  • @Publish 不是直接用于視圖的狀態(tài)管理,而是用于聲明一個(gè)ObservableObject中的屬性
    ,當(dāng)該屬性發(fā)生變化時(shí),所有觀察該對(duì)象的視圖都會(huì)被更新。@Publish使得屬性變成可觀察的,并且當(dāng)值發(fā)生變化時(shí),視圖會(huì)自動(dòng)刷新
ObservableObject
  • ObservableObject 是一個(gè)協(xié)議,用于實(shí)現(xiàn)數(shù)據(jù)模型的可觀察性。它是 SwiftUI 狀態(tài)管理的一部分,使得模型對(duì)象能夠在狀態(tài)發(fā)生變化時(shí)通知依賴于它的視圖進(jìn)行更新。換句話說(shuō),ObservableObject 用于管理和存儲(chǔ)應(yīng)用程序中的數(shù)據(jù),并確保在數(shù)據(jù)變化時(shí),相關(guān)視圖能夠自動(dòng)更新。
  • ObservableObject 協(xié)議要求實(shí)現(xiàn)一個(gè)或多個(gè)帶有 @Published 標(biāo)記的屬性,當(dāng)這些屬性的值發(fā)生變化時(shí),任何觀察該對(duì)象的視圖都會(huì)重新渲染。
@ObservedObject
  • ObserveObject用于觀察外部對(duì)象的變化,通常與遵循ObservableObject協(xié)議的對(duì)象一起使用,當(dāng)對(duì)象的任何@Published屬性發(fā)生變化時(shí),綁定視圖會(huì)重新渲染。
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
            }
        }
    }
}

每次點(diǎn)擊“Increment”按鈕時(shí),Text中的Counter就會(huì)跟著變化

@EnvironmentObject
  • @EnvironmentObject 用于在視圖層級(jí)結(jié)構(gòu)中傳遞和共享數(shù)據(jù),它允許你在多個(gè)視圖之間傳遞共享的可觀察對(duì)象ObservableObject而不需要顯式地將數(shù)據(jù)通過(guò)視圖層級(jí)逐層傳遞。使用時(shí),視圖需要從環(huán)境中注入對(duì)象。
// 可觀察對(duì)象,用來(lái)存儲(chǔ)應(yīng)用的狀態(tài)
class UserSettings: ObservableObject {
    @Published var username: String = "Guest"
}

struct ContentView: View {
    @EnvironmentObject var userSettings: UserSettings  // 從環(huán)境中獲取共享的數(shù)據(jù)

    var body: some View {
        VStack {
            Text("Hello, \(userSettings.username)")  // 顯示用戶名
            Button("Change Username in Content") {
                userSettings.username = "Content"  // 修改用戶名,視圖會(huì)自動(dòng)更新
            }
        }
        .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)
    }
}
  • 上面我們看到盡管在ParentView、ContentView、AnotherViewThreeLevelView中都是使用了userSettings,但是我們只在生成ParentView的時(shí)候注入了userSettings,就可以在其子View AnotherViewContentView 以及其孫View ThreeLevelView中就可以使用了。
@StateObject
  • @StateObject是用于創(chuàng)建和持有一個(gè) ObservableObject實(shí)例的屬性包裝器。它用于在視圖中創(chuàng)建和擁有一個(gè) ObservableObject 實(shí)例,并確保視圖在該對(duì)象的狀態(tài)發(fā)生變化時(shí)自動(dòng)更新。它確保對(duì)象在視圖生命周期內(nèi)保持持久性和唯一行,并不會(huì)因文視圖重新渲染而丟失。
  • @ObservedObject不同,@StateObjectObservableObject實(shí)例的創(chuàng)建和持有者;而@ObservedObject不會(huì)負(fù)責(zé)初始化或管理對(duì)象, @ObservedObject只是@ObservedObject是實(shí)例的觀察者,只負(fù)責(zé)觀察對(duì)象的變化
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
            }
        }
    }
}

點(diǎn)擊Homepage Button兩次看到的結(jié)果

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

上面可以看到SecondView 只在創(chuàng)建的時(shí)候打印了一次,而FirstView在每次點(diǎn)擊“Homepage” button的時(shí)候都會(huì)打印

@Environment
  • @Environment屬性包裝器用于從視圖的環(huán)境中獲取系統(tǒng)提供的或由父視圖注入的共享數(shù)據(jù),而不需要顯示地通過(guò)屬性傳遞數(shù)據(jù)。它能讓你訪問(wèn)與當(dāng)前環(huán)境相關(guān)的信息,并在視圖中進(jìn)行響應(yīng)式更新。
  • 基本語(yǔ)法: @Environment(\.key) var value@Environment(\.key) var value
    key:系統(tǒng)環(huán)境值的鍵或自定義的環(huán)境鍵,用來(lái)標(biāo)識(shí)要獲取的環(huán)境數(shù)據(jù).
    value:存儲(chǔ)在環(huán)境中的實(shí)際值,可以是任何類型.
  • SwiftUI 提供了一些常用的系統(tǒng)級(jí)別的環(huán)境數(shù)據(jù),比如: 當(dāng)前的顏色模式,設(shè)備的橫向布局類
struct ContentView: View {
    @Environment(\.colorScheme) var colorScheme1 // 獲取當(dāng)前的顏色模式
    @Environment(\.horizontalSizeClass) var sizeClass // 獲取當(dāng)前設(shè)備的橫向布局類

    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 注入自定義數(shù)據(jù)
struct HomepageView: View {
    var body: some View {
        VStack {
            ContentView()
        }
    }
}

// 定義一個(gè)簡(jiǎn)單的主題
struct AppTheme {
    var backgroundColor: Color
    var textColor: Color
}

struct AppThemeKey: EnvironmentKey {
    // 為這個(gè)環(huán)境鍵提供默認(rèn)值
    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  // 訪問(wèn)注入的 AppTheme
    
    var body: some View {
        VStack {
            Text("This is a child view")
                .padding()
                .background(appTheme.backgroundColor)  // 使用環(huán)境中傳遞的主題背景色
                .foregroundColor(appTheme.textColor)  // 使用環(huán)境中傳遞的主題文字色
        }
    }
}

上面每次點(diǎn)擊“Toggle Theme” button的時(shí)候,ChildView中的Text的文字和背景色就會(huì)改變

@AppStorage
  • @AppStorage 是一種屬性包裝器,用于將數(shù)據(jù)存儲(chǔ)到應(yīng)用的 UserDefaults 中,并且能夠使得數(shù)據(jù)在視圖間保持同步。它允許你輕松地從 UserDefaults 獲取和存儲(chǔ)數(shù)據(jù),同時(shí)自動(dòng)處理視圖的更新。。它非常適合在應(yīng)用程序中持久化小型設(shè)置或狀態(tài),如主題、語(yǔ)言選擇等。當(dāng) UserDefaults 中的數(shù)據(jù)發(fā)生變化時(shí),視圖會(huì)自動(dòng)重新渲染。
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")
        }
    }
}

上面每次點(diǎn)擊Toggle Theme 的時(shí)候,會(huì)修改isDarkModel的值,同時(shí)并保存到UserDefault中去,當(dāng)UserDefault中的isDarkModel變化時(shí),會(huì)觸發(fā)ChildView重新渲染,這樣就會(huì)導(dǎo)致ChildView 的內(nèi)容跟著改變。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容