[SwiftUI] iOS16以降の@EnvironmentObjectとiOS17以降の@Environment
2024.11.01
こんにちは。きんくまです。
iOS16までの@EnvironmentObject
つくったもの
構造と挙動について
ContentView(親View。EnvironmentObjectを定義)
|- ChildView(子供View。だけどEnvironmentObjectは定義していない)
|- GrandchildView(孫View。EnvironmentObjectを定義)
通常SwiftUIでは、プロパティを子供に渡して、子供からさらに孫に渡して。
といった感じにバケツリレーでデータを渡します。
@EnvironmentObjectだと、階層にかかわらずデータを渡すことが可能です。
親と孫のデータが共有されているので、以下が行われます
- 親でのデータ更新が孫のViewも合わせて更新
- 逆に孫でのデータ更新が親のViewも合わせて更新
ソースコード
class AppSettings: ObservableObject {
@Published var mainBackgroundColor: Color = .blue
@Published var isToggleOn: Bool = true
}
struct GrandchildView: View {
@EnvironmentObject var appSettings: AppSettings
var body: some View {
VStack {
Text("GrandchildView")
Color(appSettings.mainBackgroundColor)
.frame(width: 50, height:50)
Toggle(isOn: $appSettings.isToggleOn) {
Text("isToggleOn")
}
Button {
appSettings.mainBackgroundColor = .red
} label: {
Text("set backgroundColor to red")
}
}
.padding(10)
.border(Color.gray)
.frame(width: 300)
}
}
struct ChildView: View {
var body: some View {
VStack {
Text("ChildView")
GrandchildView()
}
.padding(10)
.border(Color.gray)
}
}
struct ContentView: View {
@StateObject private var appSettings = AppSettings()
var body: some View {
VStack {
Text("ContentView")
Color(appSettings.mainBackgroundColor)
.frame(width: 50, height:50)
Toggle(isOn: $appSettings.isToggleOn) {
Text("isToggleOn")
}
Button {
appSettings.mainBackgroundColor = .green
} label: {
Text("set backgroundColor to green")
}
ChildView()
}
.padding(10)
.border(Color.gray)
.frame(width: 300)
.environmentObject(appSettings)
}
}
iOS17以降で同じものを作る
iOS17以降はObservationフレームワークが追加されたので、同じものを書き直します。
@Observable class AppSettings {
var mainBackgroundColor: Color = .blue
var isToggleOn: Bool = true
}
struct GrandchildView: View {
// @EnvironmentObjectから変更
@Environment(AppSettings.self) private var appSettings: AppSettings
var body: some View {
// Toggleで$appSettingsを使いたいので定義
@Bindable var appSettings = appSettings
VStack {
Text("GrandchildView")
Color(appSettings.mainBackgroundColor)
.frame(width: 50, height:50)
Toggle(isOn: $appSettings.isToggleOn) {
Text("isToggleOn")
}
Button {
appSettings.mainBackgroundColor = .red
} label: {
Text("set backgroundColor to red")
}
}
.padding(10)
.border(Color.gray)
.frame(width: 300)
}
}
struct ChildView: View {
var body: some View {
VStack {
Text("ChildView")
GrandchildView()
}
.padding(10)
.border(Color.gray)
}
}
struct ContentView: View {
// @StateObjectから変更
@State private var appSettings = AppSettings()
var body: some View {
VStack {
Text("ContentView")
Color(appSettings.mainBackgroundColor)
.frame(width: 50, height:50)
Toggle(isOn: $appSettings.isToggleOn) {
Text("isToggleOn")
}
Button {
appSettings.mainBackgroundColor = .green
} label: {
Text("set backgroundColor to green")
}
ChildView()
}
.padding(10)
.border(Color.gray)
.frame(width: 300)
//.environmentObject(appSettings) から変更
.environment(appSettings)
}
}
ポイントは以下です
- GrandchildViewで@EnvironmentObjectから@Environment(AppSettings.self)に変更
- ContentViewで@StateObjectから@Stateに変更
- ContentViewのbodyで、.environmentObject(appSettings) から.environment(appSettings)に変更
@Environmentは今回の使い方だけでなく、キーを指定してデータを共有する使い方もあります。それについては別記事にします。
[SwiftUI] iOS 17以降の@Environmentまとめ
参考
Migrating from the Observable Object protocol to the Observable macro