【SwiftUI】Codableを使ってUserDefaultsにカスタムデータを保存する方法

## はじめに
SwiftUIのUserDefaultsにおいて、ユーザーの情報や複雑なデータ（構造体やカスタムオブジェクト）を保存する方法を学習したので、備忘録としてまとめます。

ここではUserDefaultsに **Codable** を使ってカスタムデータを保存・読み込み・削除する方法を、簡単なアプリを例に説明します。

## 環境
【Xcode】16.2
【iOS】18.2
【macOS】Sequoia 15.3.1


## UserDefaultsとCodableについて

UserDefaultsは基本的に単純なデータ型（Int, String, Bool, Arrayなど）しか保存できません。

**Codable** を使えば、構造体（struct）やカスタムオブジェクトをJSON形式に変換して、UserDefaultsに保存できます。

※UserDefaultsでデータ型を保存する方法は下記にまとめています。

https://qiita.com/wano1101/items/c54ba4adc433de605864


### Codableとは？

Codableは、Swiftに標準搭載されているプロトコルで、以下の2つを組み合わせたものです。

- **Encodable**：オブジェクトをデータ（JSON）に変換
- **Decodable**：データ（JSON）をオブジェクトに変換

## UserDefaults + Codableを使った簡単なアプリを作成

ここでは、ユーザー情報（名前・年齢）を保存・読み込み・リセットできるシンプルなアプリを作成します。

### アプリの仕様

- ユーザー情報を入力し、「保存」ボタンでUserDefaultsに保存
- アプリ起動時に保存されたデータを読み込み、画面に表示
- 「リセット」ボタンでデータを削除

```swift
import SwiftUI

// UserDefaultsに保存するカスタムデータ用の構造体
struct User: Codable {
    let name: String
    let age: Int
}

struct ContentView: View {
    @State private var name: String = ""
    @State private var age: String = ""
    @State private var savedUser: User? = nil

    private let userKey = "savedUser"

    var body: some View {
        VStack {
            TextField("名前を入力", text: $name)
                .textFieldStyle(.roundedBorder)
                .padding()

            TextField("年齢を入力", text: $age)
                .textFieldStyle(.roundedBorder)
                .padding()
                .keyboardType(.numberPad)

            HStack {
                Button("保存") {
                    saveUser()
                }
                .padding()
                .background(Color.blue)
                .foregroundStyle(.white)
                .cornerRadius(10)

                Button("リセット") {
                    resetUser()
                }
                .padding()
                .background(Color.red)
                .foregroundStyle(.white)
                .cornerRadius(10)
            }
            //保存したデータを画面に表示
            if let user = savedUser {
                Text("保存されたユーザー: \(user.name), \(user.age)歳")
                    .padding()
            }
        }
        //アプリ起動時にデータを読み込む
        .onAppear(perform: loadUser)
    }

    // ユーザー情報をUserDefaultsに保存
    private func saveUser() {
        guard let ageInt = Int(age) else { return }
        let user = User(name: name, age: ageInt)

        if let encoded = try? JSONEncoder().encode(user) {
            UserDefaults.standard.set(encoded, forKey: userKey)
            savedUser = user
        }
    }

    // UserDefaultsからユーザー情報を読み込み
    private func loadUser() {
        if let savedData = UserDefaults.standard.data(forKey: userKey),
           let user = try? JSONDecoder().decode(User.self, from: savedData) {
            savedUser = user
        }
    }

    // UserDefaultsからデータを削除
    private func resetUser() {
        UserDefaults.standard.removeObject(forKey: userKey)
        savedUser = nil
    }
}
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4004493/12a3d935-1533-4587-a536-de3d5de8d067.png" width=30%>

### 1. `Codable`な構造体の定義

```swift
struct User: Codable {
    let name: String
    let age: Int
}
```

- **Codable** を適用した構造体を作成。
- `name`（文字列）と `age`（整数）を持つ簡単なユーザー情報を表現。

### 2. データの保存

```swift
if let encoded = try? JSONEncoder().encode(user) {
    UserDefaults.standard.set(encoded, forKey: userKey)
}
```

- **JSONEncoder()** でオブジェクトをData型に変換。
- 変換したData型をUserDefaultsに保存。
- `try?` で、エラーが発生した場合にnilを返すようにする。


### 3. データの読み込み

```swift
if let savedData = UserDefaults.standard.data(forKey: userKey),
   let user = try? JSONDecoder().decode(User.self, from: savedData) {
    savedUser = user
}
```

- `data(forKey:)` でUserDefaultsからデータを取得。
- **JSONDecoder()** でDataを構造体に変換。

### 4. データの削除

```swift
UserDefaults.standard.removeObject(forKey: userKey)
savedUser = nil
```

- `removeObject(forKey:)` で指定したキーに紐づくデータを削除。

### 5. アプリ起動時にデータを読み込む

```swift
.onAppear(perform: loadUser)
```

- onAppear はビューが表示されたときに一度だけ実行される。

- ここではアプリ起動時に loadUser() を実行し、保存されたデータを画面に表示。

- アプリを再起動してもデータを復元するために重要。

## まとめ

- **UserDefaults** では単純な型だけでなく、**Codable** を使えばカスタムデータ（構造体）も保存可能。
- **JSONEncoder** でデータをエンコードして保存、**JSONDecoder** でデコードして読み込む。
- 小規模なデータ保存には便利だが、大規模データには **Core Data** や **FileManager** の使用が推奨。