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