【Swift UI】@StateObjectとは?違いや使い方!
この記事からわかること
- Swift UIの@StateObjectの使い方
- @ObservedObjectとの違い
- プロパティラッパとは?
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
環境
- Xcode:15.0.1
- iOS:17.1
- Swift:5.9
- macOS:Sonoma 14.1
Swift UIでクラスのプロパティの更新を監視するために使用する「@StateObject
」の使い方やメリットをまとめていきたいと思います。
@StateObjectとは?
@StateObject
はSwift5.1から導入された機能「Property Wrapper(プロパティラッパ)」の1つです。プロパティラッパの特徴はプロパティに対する操作や処理をカプセル化して定義することでキーワードを追加するだけで任意の動作や挙動を行わせることができるデータ構造です。@XXXX
形式で自分で定義することも可能です。定義する際は「@propertywrapper
」を定義前に記述します。
@StateObject
は@ObservedObject
と同じく@Published
と組み合わせて使用することで、クラスのプロパティの変更を監視し、更新された時にビューに表示されている値も更新するためのプロパティラッパです。つまりクラスのプロパティの更新がリアルタイムにビューに反映されるようになります。
おすすめ記事:【Swift UI】@ObservedObjectの意味と使い方!クラスとプロトコルとの関係
@Published
もプロパティラッパの1種でcombine
フレームワークから提供されている機能の1つです。@Published
が付与されたプロパティは監視状態になります。これで値が変更された時にその変化を感知することができるようになります。
「Property Wrapper(プロパティラッパ)」には他にも子の構造体から親の構造体のプロパティを変更できるようにする@Binding
やクラス(オブジェクト)を観測できる@EnvironmentObject
などが用意されています。
使い方
クラスで定義しているプロパティの値が変化する際にビュー(表示)も同時に更新したい場合に@StateObject
を使います。使う流れは以下の通りです。
- クラスはObservableObjectプロトコルに準拠して定義
- 監視したいプロパティに@Publishedを付与
- インスタンス化した変数に@StateObjectを付与
クラスはObservableObjectプロトコルに準拠して定義
まずは適当なSwiftファイルにクラスを定義します。変更を監視したいプロパティに@Published
を付与しておきます。
class MainViewModel: ObservableObject {
@Published var title: String = ""
}
インスタンス化した変数に@StateObjectを付与
ObservedObject
プロトコルに準拠させたクラスをインスタンス化し変数に格納します。その際に@StateObject
を付与します。これでクラスのプロパティの値が変化した時にビューも連動して更新されるようになります。
struct InputView: View {
@StateObject private var viewModel = MainViewModel()
var body: some View {
VStack {
TextField("タイトル", text: $viewModel.title)
Text("\(viewModel.title)")
}
}
}
@StateObjectと@ObservedObjectとの違いと使い分け
先ほどのコードを@ObservedObject
に変更しても一見なんの変化も起きていないように期待通りの動作をします。しかし両者にはライフサイクルに明確な違いがあります。
@ObservedObject
はそのオブジェクトを含むView構造体が再描画された際にインスタンスが破棄され、再度新規でインスタンス化されます。
一方@StateObject
はそのオブジェクトを含むView構造体が再描画されてもインスタンスは破棄されず、同じインスタンスを保持してくれます。
これを検証するために先ほどのInputView
を別のビューでラップして使用してみます。@StateObject
と@ObservedObject
用の2つを用意しておいてください。ラップした親のViewでは再描画が走るように@State
を使用してビューに反映されるようにしておきます。
struct ContentView: View {
@State private var count: Int = 0
var body: some View {
VStack {
Text("\(count)")
Button {
count += 1
} label: {
Text("Add Count")
}
// 先ほどのInputViewのプロパティラップをObservedObjectに変更したもの
ObservedObjectInputView()
// 先ほどのInputViewの名称のみをStateObjectInputViewに変更
StateObjectInputView()
}
}
}
この場合にカウントを増やすボタンを押下した際の挙動が両者では異なります。ObservedObjectInputView
側で入力していた値はリセットされ、StateObjectInputView
側で入力していた値は保持されていることを確認することができます。
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。