SwiftUIのデータバインディングの仕組みの一つで、アプリケーション全体でデータを共有する為の@EnvironmentObjectの使い方を解説します。
その他のデータバインディングについてはこちらの記事を御覧ください。
環境
この記事の情報は次のバージョンで動作確認しています。
【Swift】5.3.1
【iOS】14.2
【macOS】Catalina バージョン 10.15.7
@EnvironmentObjectの概要
@ObserverdObjectと同様に、ObservableObjectプロトコルに準拠したデータクラス(classのインスタンス)とViewを紐付ける仕組みです。
@ObserverdObjectとの違いは、バケツリレーのように子Viewに引き渡す必要がありません。インスタンスを階層トップのViewに紐付けると、アプリケーション全体からアクセス可能になります。
アプリケーション全体で共有するようなデータを扱う用途で使用します。
インスタンスの生成と紐付け
EnvironmentObjectは、多くの場合アプリケーションのトップView生成時に、データクラスの生成と紐付けを行います。
紐付けにはenvironmentObjectモディファイアを使用ます。
アプリケーションのトップView生成箇所は、Xcodeで新規プロジェクト生成時に選択した「Life Cycle」によって変わります。
UIKit App Delegate
Xcode11で生成した新規プロジェクトはこちらのケースです。
このケースではSceneDelegate.swiftの中でトップView(ContentView)を生成しますので、ここでデータクラスを紐付けます。
ここでは例としてUserクラスのインスタンスを紐付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { let contentView = ContentView() .environmentObject(User()) // データクラス(User)のインスタンス紐付け ・ ・ ・ } } |
SwiftUI App
Xcode12以降で新規プロジェクトを生成した場合はLife Cycleが選択可能です。
SwiftUI Appを選択した場合がこちらのケースになります。
このケースでは「プロジェクト名App.swift」の中で、トップView(ContentView)を生成しますので、ここでデータクラスを紐付けます。
1 2 3 4 5 6 7 8 9 10 11 |
@main struct プロジェクト名App: App { var body: some Scene { WindowGroup { ContentView() .environmentObject(User()) // データクラス(User)のインスタンス紐付け } } } |
各Viewから共通インスタンスにアクセスする方法
1 2 3 |
@EnvironmentObject var user: User // Userクラスのインスタンスを共有する |
このように@EnvironmentObjectを付けて、対象クラス型のプロパティを宣言します。
宣言したプロパティ(user)を使って、トップビューに紐付けられた共通インスタンスにアクセスできます。
使用例
次のサンプルを実行する際には、前章に従って、アプリケーションのトップビューにUserクラスのインスタンスが紐付けられている必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
/// ユーザークラスの定義 class User: ObservableObject { @Published var name = "" @Published var level = 1 } struct ContentView: View { @EnvironmentObject var user: User // Userクラスのインスタンスを共有する var body: some View { VStack { Text("\(user.name)さんのレベルは\(user.level)です") /// 入力フォーム InputForm() } } } /// 入力フォーム struct InputForm: View { @EnvironmentObject var user: User // Userクラスのインスタンスを共有する var body: some View { VStack { TextField("名前", text: $user.name) .textFieldStyle(RoundedBorderTextFieldStyle()) .frame(width: 300) .padding() Button("レベルアップ") { user.level += 1 } } } } |
Text、TextField、Button全てが同じUserクラスのインスタンスを共有していますので、名前を変更したり、レベルアップボタンをタップすると、TextViewが自動で再描画されます。
複数のインスタンスを紐付ける場合
次のようにenvironmentObjectモディファイアを重ねて、異なるクラスのインスタンスをアプリに紐付け可能です。
同じクラスのインスタンスの複数紐付けはできません。
1 2 3 4 5 6 7 8 9 10 11 12 |
@main struct アプリ名App: App { var body: some Scene { WindowGroup { ContentView() .environmentObject(User()) // データクラス(User)のインスタンス紐付け .environmentObject(Config()) // データクラス(Config)のインスタンス紐付け } } } |
使用上の注意点
@EnvironmentObjectは、祖先ビューに紐付けられている必要があります。SwiftUIが正しいタイプのEnvironmentObjectを見つけられない場合、クラッシュが発生します。これはプレビューにも当てはまりますので、注意してください。
以下はプレビューでの記述例です。
1 2 3 4 5 6 7 8 |
struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() .environmentObject(User()) // 共有するインスタンス紐付ける } } |