SwiftUIの@AppStorageは、UserDefaultsを操作するためのproperty wrapperで、値をUIと自動的に同期します。@AppStorageで宣言されたプロパティが変更されると、新しい値は即座にUserDefaultsに保存され、UserDefaultsが外部(ウィジェットや拡張機能)から変更されると、Viewは自動的に再描画されます。Apple Developer Documentation (2025)によると、@AppStorageはString、Int、Double、Bool、Data、URLおよびそのオプショナル版をサポートし、手動の監視コードなしでユーザー設定のリアクティブな保存を提供します。
重要なポイント
@AppStorageはAppleがiOS 14で導入したproperty wrapperで、ViewのプロパティをUserDefaultsのキーにバインドします。プロパティを読み取るとき、SwiftUIは指定されたキーでUserDefaultsから値をロードします。書き込むときは、新しい値を保存し、Viewに再描画が必要であることを通知します。
@AppStorage以前は、開発者はonAppearで手動でUserDefaultsを読み取り、UserDefaults.didChangeNotificationに購読し、変更時に@Stateを更新する必要がありました。@AppStorageはサイクル全体を自動化します。1行の宣言で15~20行のボイラープレートコードを置き換えます。さらに、@AppStorageは双方向の同期を提供します。別のプロセス(App ExtensionやWidgetなど)からUserDefaultsの値が変更されても、Viewは更新を受け取ります。
アーキテクチャ的には、@AppStorageはDynamicPropertyとして実装されており、SwiftUIが依存関係を追跡し、監視対象の値が変更されたときにViewを再描画できるようにします。これにより、ユーザー設定(インターフェース言語、機能の有効/無効、最後に選択したタブ、ユーザー名)の保存に最適です。
@AppStorageは内部的にUserDefaultsを使用しますが、ストレージの操作方法は根本的に異なります。UserDefaultsは低レベルAPIであり、読み取り、書き込み、変更通知の手動管理が必要です。@AppStorageはSwiftUIの抽象化であり、すぐに使えるリアクティブな動作を提供します。
UserDefaultsは一度きりの操作に適しています:アプリ起動時の設定読み込み、分析情報の書き込み、トークンのキャッシュ。@AppStorageは、UIをリアクティブに更新する必要がある設定(テーマの切り替え、言語選択、インターフェース状態の保存)に使用します。View内でUserDefaultsを直接使用するのはアンチパターンです。追加の購読なしではViewが変更を認識できないからです。
| パラメータ | @AppStorage | UserDefaults |
|---|---|---|
| リアクティビティ | 自動 | 通知の購読が必要 |
| ボイラープレート | プロパティあたり1行 | プロパティあたり15~20行 |
| 型 | String、Int、Double、Bool、Data、URL | 全ての型+アーカイブオブジェクト |
| カスタム型 | RawRepresentable経由 | NSKeyedArchiver経由 |
| App Extension | 自動同期 | 手動購読 |
リアクティブUIを備えたシンプルな設定には、@AppStorageが推奨されます。複雑なデータ(配列、辞書、カスタムオブジェクト)には、UserDefaultsと@Stateおよび手動変更購読を組み合わせて使用するか、構造化ストレージにはSwiftData / Core Dataに切り替えてください。
@AppStorageは、UserDefaultsが直接シリアライズできる標準型をサポートします:String、Int、Double、Bool、Data、URL。各型にはオプショナル版(String?、Int?、Double?、Bool?、Data?、URL?)が存在し、「未設定」と「空の値」を区別できます。
RawRepresentableプロトコルに準拠するカスタム型の保存も、@AppStorageは自動的に機能します。enumのrawValueがString型またはInt型の場合、直接使用できます:@AppStorage("theme") var theme: AppTheme = .system。SwiftUIはrawValueを介して自動的に値をシリアライズ/デシリアライズします。
enum AppTheme: String {
case system, light, dark
}
struct SettingsView: View {
@AppStorage("username") var username: String = "Guest"
@AppStorage("launchCount") var launchCount: Int = 0
@AppStorage("isDarkMode") var isDarkMode: Bool = false
@AppStorage("appTheme") var theme: AppTheme = .system
@AppStorage("lastOpened") var lastOpened: Date? = nil
var body: some View {
Form {
TextField("Username", text: $username)
Toggle("Dark mode", isOn: $isDarkMode)
Text("\(launchCount)回起動されました")
}
}
}
この例では、さまざまな@AppStorage型を使用しています:デフォルト値「Guest」のString、起動カウンターのInt、ダークテーマのBool、String型のrawValueを持つAppTheme enum、最後の開封時刻のオプショナルDate?。各プロパティは最初の引数として指定されたUserDefaultsキーにバインドされています。初回起動時にキーがストレージに存在しない場合は、デフォルト値が使用されます。
@AppStorageの主な利点の1つは、任意のソースからのUserDefaults変更の自動監視です。App ExtensionやWidgetが値を変更すると、親アプリの@AppStorageが通知を受け取り、Viewを再描画します。これは、@AppStorageがUserDefaults.didChangeNotificationに自動的に設定するKVO(Key-Value Observing)メカニズムによって実現されます。
実際には、ユーザーがWidgetで設定を変更すると(例えばダークテーマを有効にする)、アプリは即座に変更を取得します。メインアプリとShare Extension、Watch App、Today Widgetの間でも同じ同期が機能します。開発者はプロセス間データ交換のコードを書く必要はありません — @AppStorageが自動的に行います。
struct ThemeSettingView: View {
@AppStorage("isDarkMode") var isDarkMode: Bool = false
var body: some View {
VStack {
Toggle("Dark Mode", isOn: $isDarkMode)
.onChange(of: isDarkMode) { oldValue, newValue in
print("ダークモードが\(newValue)に変更されました")
}
}
}
}
Toggleは@AppStorageを介して$isDarkModeにバインドされています。切り替えると、値は自動的に「isDarkMode」キーでUserDefaultsに保存されます。.onChange修飾子を使用すると、変更時に副作用を実行できます(例:分析情報の送信や他の画面のUI更新)。Widgetが同じキーを変更した場合も、@AppStorageはonChangeをトリガーし、状態の一貫性を確保します。
すべての設定を保存するために@AppStorageを使用する、本格的なアプリ設定画面を見てみましょう。フォームにはさまざまな種類の設定セクション(テキストフィールド、トグル、カウンター)が含まれており、すべての値は自動的にUserDefaultsに保存されます。
struct AppSettingsView: View {
@AppStorage("displayName") var displayName = ""
@AppStorage("notificationsEnabled") var notificationsEnabled = true
@AppStorage("maxResults") var maxResults = 25
@AppStorage("selectedTab") var selectedTab = "home"
var body: some View {
NavigationStack {
Form {
Section(header: Text("プロフィール")) {
TextField("Display name", text: $displayName)
}
Section(header: Text("設定")) {
Toggle("Enable notifications",
isOn: $notificationsEnabled)
Stepper("Max results: \(maxResults)",
value: $maxResults,
in: 10...100,
step: 5)
}
Section {
Button("設定をリセット") {
UserDefaults.standard.removePersistentDomain(
forName: Bundle.main.bundleIdentifier!)
}
.tint(.red)
}
}
.navigationTitle("Settings")
}
}
}
フォームには、さまざまな型の4つの@AppStorageプロパティが含まれています:名前用のString、通知用のBool、結果数用のInt、選択されたタブ用のString。すべてのコントロールはBinding($displayName、$notificationsEnabledなど)を介してプロパティにバインドされています。「Reset settings」ボタンはアプリドメインを削除してすべてのUserDefaultsをクリアし、その後@AppStorageは自動的にデフォルト値に戻ります。
struct SharedSettingsView: View {
let sharedDefaults = UserDefaults(suiteName: "group.com.example.app")
@AppStorage("widgetTheme", store: UserDefaults(suiteName: "group.com.example.app")!)
var widgetTheme: String = "システム"
@AppStorage("widgetColor", store: UserDefaults(suiteName: "group.com.example.app")!)
var widgetColor: String = "青"
var body: some View {
Form {
Picker("Widget theme", selection: $widgetTheme) {
Text("システム").tag("system")
Text("ライト").tag("ライト")
Text("ダーク").tag("ダーク")
}
Picker("Accent color", selection: $widgetColor) {
Text("青").tag("blue")
Text("緑").tag("緑")
Text("赤").tag("赤")
}
}
}
}
App Group(アプリと拡張機能間の共有ストレージ)の場合、@AppStorageはstoreパラメーターを受け入れます:UserDefaults(suiteName:)。値は共有コンテナに保存され、メインアプリ、Widget、Watch App、同じグループの他の拡張機能がアクセスできます。Widgetはこれらの設定を読み取ることができ、アプリで設定が変更されると、UserDefaults監視メカニズムを通じてWidgetが自動的に更新されます。
よくある質問
@Stateは値のみをメモリに保存し、アプリを再起動するとリセットされます。@AppStorageは値をUserDefaultsに保存し、次回起動時に復元します。一時的な画面データには@Stateを、再起動後も保持すべき設定には@AppStorageを使用してください。
はい、EnumがStringまたはInt型のrawValueを持つRawRepresentableプロトコルに準拠している場合に使用できます。例:@AppStorage("theme") var theme: AppTheme = .system。SwiftUIはrawValueを介してenumを自動的にシリアライズし、ロード時に復元します。
標準ストレージの場合はUserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)を、特定のキーの場合はremoveObject(forKey:)を呼び出します。クリア後、すべての@AppStorageプロパティは宣言時に指定されたデフォルト値に戻ります。
はい、アプリと拡張機能間の同期にはApp Groupを使用します:@AppStorage("key", store: UserDefaults(suiteName: "group.com.example.app")!)。Widget、Share Extension、Watch Appは同じUserDefaultsを読み書きでき、変更は自動的に追跡されます。
@AppStorageはUserDefaultsを使用しており、これは少量のデータ(設定、トークン、カウンター)向けに設計されています。推奨される上限はアプリあたり100 KBまでです。構造化データや大規模データ(オブジェクト配列、メディアファイル)には、SwiftData、Core Data、またはファイルシステムを使用してください。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。