@State, @Binding, @StateObject, @ObservedObject, ObservableObject, @Published, @EnvironmentObject
SwiftUI で開発を行っている人であれば、
これらのキーワードを一度は目にしたことがあるのではないでしょうか。
(「@」で始まるものは「プロパティラッパー」、「ObservableObject」はプロトコルです。)
画面内で値を保持したり、
画面間で値を共有したり、
値の変更に応じて画面を再描画したりする中で、
何となく使ってきた方も多いのではないでしょうか。
一方で、「どのキーワードを、どの画面で、なぜ使うのか」
を説明しようとすると、意外と迷うことがあります。
これらは、
「値がどこに存在し、画面(View)がそれをどう扱うか」
を明示するための仕組みです。
本記事では、SwiftUI における値の所在と画面との関係を整理しながら、
各キーワードの役割を確認していきます。
※ 本記事では、View と ViewModel を分離する構成(MVVMなど)を前提に説明します。
※「@EnvironmentObject」については View や ViewModel だけで完結する話ではないので本記事では扱わず、別記事で紹介しようと思います。
全体像
まずはキーワードがどこで記述されて、何に付与されるかを、階層構造で整理します。
Viewファイルに記述
├─ Viewファイルが宣言した値に付与
│ ├─ @State(親Viewで使用)
│ └─ @Binding(子Viewで使用)
│
└─ Viewファイルが宣言したインスタンス(ObservableObject に準拠)に付与
├─ @StateObject(親Viewで使用)
└─ @ObservedObject(子Viewで使用)
ViewModelファイルに記述
└─ ObservableObject に準拠したViewModelファイルが宣言した値に付与
└─ @Published
そして、図で整理します。

@State / @Binding / @StateObject / @ObservedObject はすべて View 内の監視対象に使用しますが、「その View が親なのか子なのか」「監視対象が値なのかインスタンスなのか」でどれを使用するかが変わります。
ObservableObject は View が監視したいクラス(ViewModel)に準拠させるプロトコルです。
@Published は 上記ObservableObjectに準拠したクラスの監視対象にしたい値に使用します。(@Publishedのついた値の変更 → そのクラスの変更通知 となります。)
@State と @Binding(View内で完結するやつ)
@State と @Binding は、View(画面)内で完結する「値」を扱うための仕組みです。
@State
@State は、その View 自身が所有する値を定義するために使用します。
ボタンの ON / OFF、アラート表示フラグ、入力中のテキストなど、
「この画面の中だけで意味を持つ状態」を表すのに適しています。
@State の値が変更されると、SwiftUI はその変更を検知し、画面(View)を再描画します。
struct ParentView: View {
@State var status: Bool = false // ← 変更を検知したい値に @State 付与。
var body: some View {
Toggle("ON / OFF", isOn: $status)
}
}コード内で使用されている「Toggle」 は ON / OFF を切り替えるための SwiftUI の標準コンポーネントで、
第二引数の isOn に渡した値が切り替え操作に応じて更新されます。
以降のコード例でも「Toggle」を多用します。
@Binding
@Binding は 親 View が所有している @State の値を、子 View から参照・更新するための仕組みです。
子 View は親の所有している値を、借りて使うという位置づけです。
もちろん @Binding の値が更新されると、親 View の @State が更新されたことになるので、画面が再描画されます。
struct ChildView: View {
@Binding var status: Bool // ← 親から借り受ける「@Stateの値」を格納する変数に @Binding を付与
var body: some View {
Toggle("ON / OFF", isOn: $status)
}
}要点をまとめると
• 親 View:@State で値を所有する
• 子 View:@Binding で親の値を借り受ける
@State と @Binding を合わせた例を以下に示します。
親 View 内で子 View を呼び出す際に、引数で@State の値を渡しています。
// 親 View
struct ParentView: View {
@State var status: Bool = false // ← 変更を検知したい値に @State 付与。
var body: some View {
VStack {
ChildView(childStatus: $status) // ← 子 View の呼び出し時に @State の値を渡す
}
}
}// 子 View
struct ChildView: View {
@Binding var childStatus: Bool // ← 親から借り受ける「@Stateの値」を格納する変数に @Binding を付与
var body: some View {
Toggle("ON / OFF", isOn: $childStatus)
}
}@StateObject と @ObservedObject(View外も関係するやつ)
@StateObject と @ObservedObject は、View 内で View の外にある「オブジェクト」を扱うための仕組みです。
@StateObject ( @ObservedObject ) を付与するオブジェクトは後で解説する
ObservableObjectに準拠している必要があります。
@StateObject
@StateObject は、その View 自身が所有するオブジェクト(主に ViewModel)に使用します。
また、@StateObject が付与されたオブジェクト内の監視対象の値が変更されると、SwiftUI はその変更を検知し、画面(View)を再描画します。
// 親 View
struct ParentView: View {
@StateObject var viewModel = ToggleViewModel() // ← 変更を検知したいオブジェクト(ViewModel)に @StateObject を付与
var body: some View {
Toggle("ON / OFF", isOn: $viewModel.outerStatus)
}
}@ObservedObject
@ObservedObject は 親 View が所有している @StateObject のオブジェクト(主に ViewModel)を、子 View から参照・更新するための仕組みです。
子 View は親の所有しているオブジェクトを、借りて使うという位置づけです。
もちろん @ObservedObject が付与されたオブジェクト内の監視対象の値が更新されると、親Viewの @StateObject が更新されたことになるので、画面が再描画されます。
// 子 View
struct ChildView: View {
@ObservedObject var viewModel: ToggleViewModel // ← 親から借り受ける「@StateObject のオブジェクト(ViewModel)」を格納する変数に @ObservedObject を付与
var body: some View {
Toggle("ON / OFF", isOn: $viewModel.outerStatus)
}
}要点をまとめると
• 親 View:@StateObject でオブジェクトを所有する
• 子 View:@ObservedObject で親のオブジェクトを借り受ける
@StateObject と @ObservedObject を合わせた例を以下に示します。
親 View 内で子 View を呼び出す際に、引数で @StateObject のオブジェクトを渡しています。
struct ParentView: View {
@StateObject var parentViewModel = ToggleViewModel() // ← ViewModel を所有する
var body: some View {
VStack {
Toggle("ON / OFF", isOn: $parentViewModel.outerStatus)
ChildView(childViewModel: parentViewModel) // ← 子 View の呼び出し時に ViewModel を渡す
}
}
}struct ChildView: View {
@ObservedObject var childViewModel: ToggleViewModel // ← 親から借り受ける「ViewModel」を格納する変数に @ObservedObject を付与
var body: some View {
Toggle("ON / OFF", isOn: $childViewModel.outerStatus)
}
}ちなみに、上記の例で使用しているオブジェクト(ObservableObjectに準拠)は以下です。
ObservableObject と @Published については次章で詳しく解説しています。
class ToggleViewModel: ObservableObject {
@Published var outerStatus: Bool = false
}ObservableObject と @Published(ViewModel)
ObservableObject と @Publishedは、オブジェクト(主に ViewModel)を View から監視できるようにする仕組みです。
ObservableObject
ObservableObject は、「このクラスは View から監視される対象である」ということを SwiftUI に伝えるためのプロトコルです。通常は ViewModel に準拠させて使用します。
class ToggleViewModel: ObservableObject {
}ただしこの時点では、「このクラスが監視対象になり得る」ことを宣言しただけで、まだ画面更新は発生しません。
次で説明する@Publishedが必要です。
@Published
@Published は、ObservableObject に準拠したクラスの中で「変更を検知したい値」 に付与します。
@Published は View で記述するものではなく、
ObservableObject に準拠したオブジェクトの内部で記述します。
class ToggleViewModel: ObservableObject {
@Published var status: Bool = false
}実際の使用例(朝の実)
ここでは、私が開発している朝専用SNSアプリ「朝の実(Asanomi)」で実際に使用しているソースコードを元に使用例を示します。
本アプリでは、画面表示と状態・処理を分離するために、MVVM(Model / View / ViewModel)構成を採用しています。
※投稿処理の詳細は省略します。
ViewModel(PostViewModel.swift)
ViewModel は ObservableObject に準拠し、変更を検知したい値に @Published を付与します。
これにより、@Published を付与した値が更新されたタイミングで SwiftUI が画面を再描画します。
// PostViewModel.swift
import Foundation
@MainActor
final class PostViewModel: ObservableObject {
private let repository: PostRepository
// 変更を検知して画面(View)を更新したい値
@Published var text: String = ""
@Published var isPosting: Bool = false
@Published var errorMessage: String?
init(repository: PostRepository = MockPostRepository()) {
self.repository = repository
}
func post() async {
// ※朝の実では、入力チェック・投稿実行・エラー処理などをここに実装
isPosting = true
defer { isPosting = false }
do {
try await repository.createPost(text: text)
text = ""
errorMessage = nil
} catch {
errorMessage = "投稿に失敗しました"
}
}
}
// ※朝の実では以下は別ファイルに分割しています(記事内で完結させるため最小限を記載)
protocol PostRepository {
func createPost(text: String) async throws
}
struct MockPostRepository: PostRepository {
func createPost(text: String) async throws {
// ダミー実装(成功させるだけ)
}
}
親View(PostView.swift)
親Viewでは、ViewModel(オブジェクト)を @StateObject で「所有」します。
また、画面内だけで完結する UI 制御(アラート表示など)は @State で「所有」します。
// PostView.swift
import SwiftUI
struct PostView: View {
// 親Viewが ViewModel(オブジェクト)を所有する
@StateObject private var viewModel = PostViewModel()
// 親View内だけで完結する状態(値)
@State private var isShowingErrorAlert: Bool = false
var body: some View {
PostComposerView(
viewModel: viewModel, // 子View の @ObservedObject にオブジェクトを渡す
isShowingErrorAlert: $isShowingErrorAlert // 子View の @Binding に値を渡す
)
.padding()
.alert("エラー", isPresented: $isShowingErrorAlert) {
Button("OK") { viewModel.errorMessage = nil }
} message: {
Text(viewModel.errorMessage ?? "")
}
}
}
子View(PostComposerView.swift)
子Viewでは、親が所有している ViewModel を @ObservedObject で借り受けます。
また、アラート表示などの “画面側の状態” は @Binding で借り受けます。
// PostComposerView.swift
import SwiftUI
struct PostComposerView: View {
// 親が所有する ViewModel を監視するだけ(子Viewは所有しない)
@ObservedObject var viewModel: PostViewModel
// 親Viewが持つ「画面用の状態」を Binding で受け取る
@Binding var isShowingErrorAlert: Bool
var body: some View {
VStack(spacing: 12) {
Text("朝の実 - 投稿")
.font(.headline)
TextEditor(text: $viewModel.text)
.frame(minHeight: 120)
Button {
Task { await viewModel.post() }
} label: {
if viewModel.isPosting {
ProgressView()
} else {
Text("投稿")
}
}
.disabled(viewModel.isPosting)
}
// 子View側で「エラーが出たらアラートを出す」というUI判断だけ行う
.onChange(of: viewModel.errorMessage) { _, newValue in
isShowingErrorAlert = (newValue != nil)
}
}
}
まとめ
@State / @Binding は、View 内で完結する「値」を扱うための仕組みであり、
@StateObject / @ObservedObject は、View の外にある ViewModel などの「オブジェクト」を扱うための仕組みです。
また、ViewModel 側では ObservableObject に準拠し、@Published を付与した値を変更すると、View 側がその変更を検知し画面が再描画されます。
重要なのは、それぞれのプロパティラッパーを個別に暗記することではなく、
「この状態(値)は View と ViewModel のどこに置くべきか」「親 View が所有するのか、子 View が借り受けるのか」という観点で使い分けることです。
本記事の内容が、SwiftUI における状態管理の全体像を整理する一助になれば幸いです。
