🎦

SwiftUIでRealmを使ってカウンターアプリを作ってみた

2023/11/25に公開

Overview

Realmとは、MongoDBの会社が作ったローカルDBです。今回は、こちらを使ってカウンターの値を端末に保存するアプリを作ってみましょう!

https://www.mongodb.com/ja-jp

こちらを参考にRealmeを追加してください
https://zenn.dev/joo_hashi/articles/0ce2f6c4fb8615

summary

プロジェクトを作成して、Realmを追加したら、UIを配置するApplicationディレクトリと、Entity(モデルの方がわかりやすいか?)とロジックを書いたコードを配置するdomainディレクトリを作成します。

.
├── Application
│   └── ContentView.swift
├── Assets.xcassets
│   ├── AccentColor.colorset
│   │   └── Contents.json
│   ├── AppIcon.appiconset
│   │   └── Contents.json
│   └── Contents.json
├── CounterRealmeApp.swift
├── Domain
│   ├── Counter.swift
│   └── RealmManager.swift
├── Preview Content
│   └── Preview Assets.xcassets
│       └── Contents.json

こちらが作成した3つのファイルです。

Realmを使ってモデルクラスを作成します。難しい設計の専門用語だと、エンティティって呼んでます。

モデルとなるコード
import Foundation
import RealmSwift

// カウンターのモデルを定義する
class Counter: Object, ObjectKeyIdentifiable {
    @Persisted(primaryKey: true) var id: ObjectId
    @Persisted var count: Int = 0
}

ライフサイクルの違いを説明すると...

ロジックを書いたコードはViewModelなんでしょうけど、ロジックを持ってるので違う気がする???
状態を扱う変数の@Stateがないのは、ObservableObjectがあるからです。このプロトコルを使っているから、なくても良い。

@Stateは通常、ビュー自体に固有の単純なデータを管理するために使用されます。このデータはビューの内部状態を表し、ビューが再描画されても保持されます。

@ObservedObjectは、外部の参照型のオブジェクト(この場合はRealmManager)を監視するために使用されます。このオブジェクトはビューの外部に存在し、ビューはオブジェクトの変更を監視して対応します。

ロジック
import Foundation
import RealmSwift

// 状態が変更されたときに、Viewに更新を通知するクラス
class RealmManager: ObservableObject {
    private(set) var localRealm: Realm?
    @Published var counters: [Counter] = []
    // initはRealmManagerが生成されたときに呼ばれる
    init() {
        // Realmを開くメソッドを実行
        openRealm()
        // 保存されてるカウンターの情報を取得
        getCounters()
    }
    // Realmとの接続を開くメソッド
    func openRealm() {
        do {
            let config = Realm.Configuration(schemaVersion: 1)
            Realm.Configuration.defaultConfiguration = config
            localRealm = try Realm()
        } catch {
            print("Error opening Realm", error)
        }
    }
    // カウンターを追加するメソッド
    func addCounter(count: Int) {
        // if letでlocalRealmがnilでないことを確認
        if let localRealm = localRealm {
            // do-catchでエラー処理
            do {
                // Realmに書き込み
                try localRealm.write {
                    let newCounter = Counter()
                    newCounter.count = count
                    localRealm.add(newCounter)
                    getCounters()
                }
            } catch {
                print("Error adding counter to Realm: \(error)")
            }
        }
    }
    // カウンターの情報を取得するメソッド
    func getCounters() {
        // if letでlocalRealmがnilでないことを確認
        if let localRealm = localRealm {
            // Realmから全てのカウンターを取得
            let allCounters = localRealm.objects(Counter.self)
            // countersに全てのカウンターを代入
            counters = []
            // forEachでallCountersの要素をcounterに代入。配列なので、appendでcountersに追加
            allCounters.forEach { counter in
                counters.append(counter)
            }
        }
    }
    // カウンターをリセットするメソッド
    func resetCounter(id: ObjectId) {
        // if letでlocalRealmがnilでないことを確認
        if let localRealm = localRealm {
            // do-catchでエラー処理
            do {
                // idが一致するカウンターを取得
                let counterToReset = localRealm.objects(Counter.self).filter(NSPredicate(format: "id == %@", id))
                // guardでcounterToResetが空でないことを確認
                guard !counterToReset.isEmpty else { return }
                // Realmに書き込み
                try localRealm.write {
                    // countを0にする
                    counterToReset[0].count = 0
                    getCounters()
                }
            } catch {
                print("Error resetting counter \(id) to Realm: \(error)")
            }
        }
    }
}
View側のコード
Application/ContentView.swift
import SwiftUI

struct ContentView: View {
    // @ObservedObject var realmManagerは、RealmManagerクラスのインスタンスを生成
    @ObservedObject var realmManager = RealmManager()

    var body: some View {
        VStack {
            // 現在のカウントを表示
            Text("Count: \(realmManager.counters.last?.count ?? 0)")
                .font(.largeTitle)

            // カウントアップボタン
            Button(action: {
                // カウントを1増やす
                let newCount = (realmManager.counters.last?.count ?? 0) + 1
                // カウントを追加
                realmManager.addCounter(count: newCount)
            }) {
                // 追加ボタンのデザイン
                Text("カウントアップ")
                    .foregroundColor(.white)
                    .padding()
                    .background(Color.blue)
                    .cornerRadius(10)
            }

            // リセットボタンのデザイン
            Button(action: {
                // if letでlastCounterIdがnilでないことを確認
                if let lastCounterId = realmManager.counters.last?.id {
                    // カウンターをリセット
                    realmManager.resetCounter(id: lastCounterId)
                }
            }) {
                // リセットボタンのデザイン
                Text("リセット")
                    .foregroundColor(.white)
                    .padding()
                    .background(Color.red)
                    .cornerRadius(10)
            }
        }
        .padding()
    }
}

📱ビルドして実験してみる

ボタンを押してカウンターを増やして、アプリを停止して、またビルドしてみましょう。ボタンを押してカウントして、アプリを停止して、ビルドすると先ほどの数値のままになっていれば端末に情報を保存できています。

https://youtube.com/shorts/HqmgS72vfsc?feature=share

thoughts

今回は、簡単にと言いたいですが、設定で躓いたので簡単ではなかったです😅
SwiftUIでRealmを使ったアプリを作ってみました。

こちらが完成品です
https://github.com/sakurakotubaki/CounterRealm

Jboy王国メディア

Discussion