SwiftUIの@AppStorage — 概要、UserDefaultsと設定の保存

著者: IT Sectr 公開日: 2026-06-25 読了時間: 7 分

SwiftUIの@AppStorageは、UserDefaultsを操作するためのproperty wrapperで、値をUIと自動的に同期します。@AppStorageで宣言されたプロパティが変更されると、新しい値は即座にUserDefaultsに保存され、UserDefaultsが外部(ウィジェットや拡張機能)から変更されると、Viewは自動的に再描画されます。Apple Developer Documentation (2025)によると、@AppStorageはString、Int、Double、Bool、Data、URLおよびそのオプショナル版をサポートし、手動の監視コードなしでユーザー設定のリアクティブな保存を提供します。

重要なポイント

  • @AppStorage — SwiftUIでUserDefaultsとリアクティブに連携するproperty wrapper
  • 自動保存 — 変更のたびに値がUserDefaultsに書き込まれる
  • 自動更新 UI — 任意のソースからUserDefaultsが変更されるとViewが再描画される
  • サポートされる型: String、Int、Double、Bool、Data、URLおよびオプショナル版
  • デフォルト値は宣言時に設定され、初回起動時に使用される

SwiftUIの@AppStorageとは?

@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 vs UserDefaults:比較

@AppStorageは内部的にUserDefaultsを使用しますが、ストレージの操作方法は根本的に異なります。UserDefaultsは低レベルAPIであり、読み取り、書き込み、変更通知の手動管理が必要です。@AppStorageはSwiftUIの抽象化であり、すぐに使えるリアクティブな動作を提供します。

UserDefaultsは一度きりの操作に適しています:アプリ起動時の設定読み込み、分析情報の書き込み、トークンのキャッシュ。@AppStorageは、UIをリアクティブに更新する必要がある設定(テーマの切り替え、言語選択、インターフェース状態の保存)に使用します。View内でUserDefaultsを直接使用するのはアンチパターンです。追加の購読なしではViewが変更を認識できないからです。

パラメータ@AppStorageUserDefaults
リアクティビティ自動通知の購読が必要
ボイラープレートプロパティあたり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を介して自動的に値をシリアライズ/デシリアライズします。

swift
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が自動的に行います。

swift
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のコード例

すべての設定を保存するために@AppStorageを使用する、本格的なアプリ設定画面を見てみましょう。フォームにはさまざまな種類の設定セクション(テキストフィールド、トグル、カウンター)が含まれており、すべての値は自動的にUserDefaultsに保存されます。

swift
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は自動的にデフォルト値に戻ります。

App Groupとの@AppStorage同期

swift
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が自動的に更新されます。

よくある質問

@AppStorageと@Stateの違いは何ですか?

@Stateは値のみをメモリに保存し、アプリを再起動するとリセットされます。@AppStorageは値をUserDefaultsに保存し、次回起動時に復元します。一時的な画面データには@Stateを、再起動後も保持すべき設定には@AppStorageを使用してください。

Enumで@AppStorageを使用できますか?

はい、EnumがStringまたはInt型のrawValueを持つRawRepresentableプロトコルに準拠している場合に使用できます。例:@AppStorage("theme") var theme: AppTheme = .system。SwiftUIはrawValueを介してenumを自動的にシリアライズし、ロード時に復元します。

すべての@AppStorage値をクリアするには?

標準ストレージの場合はUserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)を、特定のキーの場合はremoveObject(forKey:)を呼び出します。クリア後、すべての@AppStorageプロパティは宣言時に指定されたデフォルト値に戻ります。

@AppStorageはApp Extensionsで動作しますか?

はい、アプリと拡張機能間の同期にはApp Groupを使用します:@AppStorage("key", store: UserDefaults(suiteName: "group.com.example.app")!)。Widget、Share Extension、Watch Appは同じUserDefaultsを読み書きでき、変更は自動的に追跡されます。

@AppStorageにはどのくらいのデータを保存できますか?

@AppStorageはUserDefaultsを使用しており、これは少量のデータ(設定、トークン、カウンター)向けに設計されています。推奨される上限はアプリあたり100 KBまでです。構造化データや大規模データ(オブジェクト配列、メディアファイル)には、SwiftData、Core Data、またはファイルシステムを使用してください。

まとめ

  • @AppStorage — UserDefaultsで設定をリアクティブに保存するためのproperty wrapper
  • 自動保存と任意のソースからの値変更時のUI自動更新
  • サポート:String、Int、Double、Bool、Data、URLおよびRawRepresentable enum
  • デフォルト値は宣言時に設定され、初回起動時に復元される
  • App Groupによりアプリと拡張機能間で@AppStorageを同期可能
  • UserDefaultsは少量のデータ(最大100 KB)にのみ適している
  • @AppStorageをユーザー設定に、@Stateを一時的な画面状態に使用

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください