RealmSwiftを使ってみる

## 必要最小限のアプリでRealmSwiftを試してみる

今日は、RealmSwiftを初めて使ってみます。
まずは動かしてみたいので、必要最小限の「ToDoアプリ」にRealmSwiftを追加して、変更内容が保存されたら成功とします。

### ToDoアプリ（RealmSwift追加前）
```swift:ContentView
import SwiftUI

struct RoundedButtonStyle: ButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .padding(EdgeInsets(
                top: 7, leading: 10, bottom: 7, trailing: 10
            ))
            .foregroundColor(.white)
            .background(.blue)
            .cornerRadius(7)
            .opacity(configuration.isPressed ? 0.5 : 1)
    }
}


struct ContentView: View {
    @State var inputTitle: String = ""
    @State var toDoList = ["猫に餌をやる", "雨戸を開ける", "洗濯をする"]
    
    var body: some View {
        VStack {
            TextField("やることを入力", text: $inputTitle)
                .textFieldStyle(.roundedBorder)
                .padding(EdgeInsets(
                top: 10, leading: 10, bottom: 5, trailing: 10
                ))
            
            Button(action: {
                if !inputTitle.isEmpty {
                    toDoList.append(inputTitle)
                    inputTitle = ""
                } 
            }, label: {
                Text("追加")
            })
            .buttonStyle(RoundedButtonStyle())
            
            List {
                ForEach(toDoList, id: \.self) { toDo in
                    Text(toDo)
                } // ForEach
                .onDelete(perform: deleteTitles)
            } // List
        } // VStack
        
    } // body
    
    func deleteTitles(at offsets: IndexSet) {
        toDoList.remove(atOffsets: offsets)
    }
} // ContentView
```
配列をListで表示して、やることの追加と削除だけできるようにした、シンプルなアプリです。

<img width="300" alt="ToDoスクショ.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3819878/0cc0cb77-5c7c-b748-818c-96ac4060651c.png">

ここにRealmSwiftを追加して、ToDoリストの内容を変更した後にアプリを落としても、変更内容が保存されているようにしていきます。

### テーブル定義クラスを追加

```swift:ToDo
import Foundation
import RealmSwift

class ToDo: Object, ObjectKeyIdentifiable {
    @Persisted(primaryKey: true) var id: UUID = UUID()
    @Persisted var title: String = ""
}
```

ToDo.swiftを作成して、データベースのテーブル定義を設定したクラス作ります。
カラムに当たるプロパティには、@Persistedを付与します。

### ViewModelクラスを追加

```swift:ToDoViewModel
import Foundation
import RealmSwift

// ViewModel
class ToDoViewModel: ObservableObject {
    private var realm: Realm
    @Published var toDoList: [ToDo] = []

    init() {
        do {
            realm = try Realm()
            loadToDos()
        } catch {
            print("Failed to open Realm. Error: \(error.localizedDescription)")
            realm = try! Realm(configuration: .init(inMemoryIdentifier: "TemporaryRealm"))
        }
    }

    func loadToDos() {
        let todos = realm.objects(ToDo.self)
        toDoList = Array(todos)
    }

    func addToDo(title: String) {
        let newToDo = ToDo()
        newToDo.title = title

        do {
            try realm.write {
                realm.add(newToDo)
            }
            loadToDos()
        } catch {
            print("Failed to add ToDo. Error: \(error.localizedDescription)")
        }
    }

    func deleteToDos(at offsets: IndexSet) {
        offsets.map { toDoList[$0] }.forEach { toDo in
            do {
                try realm.write {
                    realm.delete(toDo)
                }
            } catch {
                print("Failed to delete ToDo. Error: \(error.localizedDescription)")
            }
        }
        loadToDos()
    }
}
```
Realmデータベースを使用して、アプリケーションのToDoリストの管理を行うためのToDoViewModelクラスを作成します。

### ContentViewを変更

```diff_swift: ContentView
import SwiftUI

struct RoundedButtonStyle: ButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .padding(EdgeInsets(
                top: 7, leading: 10, bottom: 7, trailing: 10
            ))
            .foregroundColor(.white)
            .background(.blue)
            .cornerRadius(7)
            .opacity(configuration.isPressed ? 0.5 : 1)
    }
}

struct ContentView: View {
    @State var inputTitle: String = ""
-    @State var toDoList = ["猫に餌をやる", "雨戸を開ける", "洗濯をする"]
+    @StateObject private var viewModel = ToDoViewModel()
    
    var body: some View {
        VStack {
            TextField("やることを入力", text: $inputTitle)
                .textFieldStyle(.roundedBorder)
                .padding(EdgeInsets(
                top: 10, leading: 10, bottom: 5, trailing: 10
                ))
            
            Button(action: {
                if !inputTitle.isEmpty {
-                    toDoList.append(inputTitle)
+                    viewModel.addToDo(title: inputTitle)
                    inputTitle = ""
                } 
            }, label: {
                Text("追加")
            })
            .buttonStyle(RoundedButtonStyle())
            
-            List {
-               ForEach(toDoList, id: \.self) { toDo in
-                    Text(toDo)
-                } // ForEach
-                .onDelete(perform: deleteTitles)
-            } // List
            
+             List {
+                ForEach(viewModel.toDoList) { toDo in
+                    Text(toDo.title)
+                } // ForEach
+                .onDelete(perform: viewModel.deleteToDos)
+            } // List

         } // VStack
         
    } // body
    
    func deleteTitles(at offsets: IndexSet) {
        toDoList.remove(atOffsets: offsets)
    }
} // ContentView
```

最後に、ContentView.swiftの中でToDoViewModelクラスをインスタンス化して、ToDoViewModelに定義したメソッドでリストの追加・削除を行うように書き換えます。


<img width="300" alt="ToDoスクショ２.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3819878/5f4116d1-3f91-c54e-0ea6-c22a56cf32d5.png">

アプリの起動中に追加・削除を行なったリストの内容が、アプリ停止後に再起動しても保存されています!

ご覧いただき、ありがとうございました。