昨天我們學會了 @State
與 @Binding
:
這兩個工具對於小型 App 已經很好用了,但隨著 App 越來越複雜,我們可能會遇到以下情境:
這時候,就要用到 @ObservedObject
、@StateObject
與 @EnvironmentObject
。
ObservableObject
協定@Published
),View 會自動更新// 可被觀察的資料模型
class UserData: ObservableObject {
@Published var score = 0
}
struct ObservedExampleView: View {
@ObservedObject var userData = UserData()
var body: some View {
VStack {
Text("目前分數:\(userData.score)")
.font(.title)
Button("加一") {
userData.score += 1
}
.padding()
// 傳給子 View
ScoreDetailView(userData: userData)
}
}
}
struct ScoreDetailView: View {
@ObservedObject var userData: UserData
var body: some View {
Text("子畫面分數:\(userData.score)")
.foregroundColor(.blue)
}
}
如果物件不是由外部傳入,而是 直接在子 View 裡初始化,就會出問題:
struct BadExampleView: View {
@State var isShow = true
var body: some View {
VStack {
Text("Current User: \(isShow ? "A狀態" : "B狀態")")
Button("父畫面切換換狀態") {
isShow.toggle()
}
BadScoreDetailView()
}
}
}
struct BadScoreDetailView: View {
@ObservedObject var userData: UserData = UserData()
var body: some View {
VStack {
Text("錯誤子畫面分數:\(userData.score)")
.foregroundColor(.blue)
Button("加一") {
userData.score += 1
}
}
}
}
當父畫面刷新時,子畫面的 userData
會 被重置,導致資料消失。
點擊父畫面後資料消失。
為了解決這個問題,SwiftUI 提供了 @StateObject
。
@ObservedObject
不同,ObservedObject 通常是 別人建立好傳進來的
在剛剛的範例中加上,再看一次結果
struct GoogScoreDetailView: View {
@StateObject var userData: UserData = UserData()
var body: some View {
VStack {
Text("正確的子畫面分數:\(userData.score)")
.foregroundColor(.blue)
Button("加一") {
userData.score += 1
}
}
}
}
可以看到子畫面沒有被刷新。
當 App 的規模更大,我們可能不希望一層一層傳資料。這時候就能使用**@EnvironmentObject**,把資料「注入環境」,讓所有需要的子 View 都能直接存取。
ObservableObject
// 共享的資料模型
class AppSettings: ObservableObject {
@Published var isDarkMode = false
}
struct EnvironmentRootView: View {
@StateObject var settings = AppSettings() // 建立唯一資料來源
var body: some View {
VStack {
Toggle("深色模式", isOn: $settings.isDarkMode)
.padding()
ChildAView() // 不用手動傳,直接共享
ChildBView()
}
.environmentObject(settings) // 注入環境
}
}
struct ChildAView: View {
@EnvironmentObject var settings: AppSettings
var body: some View {
Text(settings.isDarkMode ? "深色模式 ON" : "深色模式 OFF")
.foregroundColor(.green)
}
}
struct ChildBView: View {
@EnvironmentObject var settings: AppSettings
var body: some View {
Button("切換模式") {
settings.isDarkMode.toggle()
}
}
}
父畫面注入一次 .environmentObject(settings)
,子畫面自動共享。
明天我們會回到 Layout,介紹 ScrollView
以及 Lazy
容器。