@State, @ObservedObject, @EnvironmentObject の使い分け – SwiftUI

Swift

SwiftUIにおいて、struct内でプロパティを宣言するときに使用する@State, @ObservedObject, @EnvironmentObject の使い分けについて、今回は説明していきます。

@State

@Stateをつけたプロパティはメモリの管理がSwiftUIフレームワークに委譲されるため、値の変更が可能になります。また、このプロパティの値とUIの状態は同期されます。

つまり、TextUIに表示する文字のプロパティを宣言したいときなどに使用します。

struct ContentView: View {
    @State private var text: String = "Hello"
 
    var body: some View {
        VStack {
            Text(text)
        }
        .font(.largeTitle)
    }
}

ただし、@Stateで宣言されたプロパティは、そのViewとプロパティを参照する配下のViewからしかアクセスできません。なのでAppleは@Stateと一緒にprivate修飾子をつけることを推奨しています。また、外部から値の設定ができないので初期値の設定が必要になります。

イニシャライザで初期値を設定する場合は以下のように、

  • プロパティ名の先頭に_をつける
  • State(initialValue: 値) を使って設定する

とする必要があります。こんな感じに。

init() {
    _text = State(initialValue: "Hello")
}

@ObservedObject

@ObservedObjectを使用すると複数の@Stateプロパティをひとまとまりにして管理することができます。一つのViewに監視したいプロパティがたくさんあるときに非常に便利です。

監視したいプロパティには@Publishedをつけます。

class UserInfo: ObservableObject {
    @Published var firstName: String = ""
    @Published var lastName: String = ""
}

こんな感じで監視したいプロパティをもつクラスを作成します。

そして、View側でこのデータクラスを宣言してあげます。

struct ContentView: View {
    @ObservedObject var userInfo = UserInfo()

    var body: some View {
        VStack {
            Text("フルネーム:\(userInfo.lastName) \(userInfo.firstName)")
            
            TextField("姓", text: $userInfo.lastName)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .frame(width: 300)
                .padding()
            TextField("名", text: $userInfo.firstName)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .frame(width: 300)
                .padding()
        }
    }
}

@ObservableObjectも@Stateと同様にそのViewとプロパティを参照する配下のViewからしかアクセスできません。ただし、クラスを定義するため使い回しができるので非常に便利です。

@EnvironmentObject

@EnvironmentObjectを付与したプロパティは、@Stateや@ObservedObjectとは異なり複数のViewで共通のインスタンスを参照できるようになります。つまり、アプリ全体からアクセス可能なプロパティになります。

今回はさっき作ったObservableObjectのクラスを画面間で共有してみましょう。

class UserInfo: ObservableObject {
    @Published var firstName: String = ""
    @Published var lastName: String = ""
}

使用したいViewで@EnvironmentObjectを付けてクラスのプロパティを宣言します。今回はSubView1とSubView2で使用します。

struct SubView1: View {
    @EnvironmentObject var userInfo: UserInfo

    var body: some View {
        VStack {
            TextField("姓", text: $userInfo.lastName)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .frame(width: 300)
                .padding()
            TextField("名", text: $userInfo.firstName)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .frame(width: 300)
                .padding()
        }
    }
}
struct SubView2: View {
    @EnvironmentObject var userInfo: UserInfo

    var body: some View {
        VStack {
            Text("フルネーム:\(userInfo.lastName) \(userInfo.firstName)")
        }
    }
}

そして、アプリのトップビュー(今回はContentView)内でUserクラスのインスタンスを紐付けます。こうすることでSubView1とSubView2でインスタンスを共有することができます。

struct ContentView: View {
    var body: some View {
        VStack {
            SubView1()
            SubView2()
        }
        .environmentObject(UserInfo())
    }
}

もしくはトップビューが生成されるときにインスタンスの紐付けをすることも可能です。

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(User())
        }
    }
}

@EnvironmentObjectは、インスタンスを使用したいビューより祖先のビューに紐付けられている必要があります。

まとめ

プロパティの数が少なく、1画面で使用する場合は @State

プロパティの数が多かったり、複数の画面で独立して使用したりする場合は @ObservableObject

複数の画面で値を共有する場合は @EnvironmentObject

コメント

  1. […] @Stateや@Environmentについてわからない場合は僕の過去の記事で説明しているので、読んでみてください。@State, @ObservedObject, @EnvironmentObject の使い分け – SwiftUI@EnvironmentでViewの環境値を読み取ろう-SwiftUI […]