LoginSignup
24
20

More than 3 years have passed since last update.

SwiftUIでRealmを使ってみた

Last updated at Posted at 2020-10-31

はじめに

SwiftUIでRealmを使ったサンプルアプリを作成しました。
理解するのに時間がかかったので、自分のアウトプットを兼ねて書いています。
↓こんな動きをするアプリです。右の数字は0~100からランダムで取得します。
ezgif.com-video-to-gif.gif

環境

Swift 5.3
Xcode 12.0.1
Cocoapods 1.9.3
RealmSwift 5.4.7

経緯

最近、SwiftでRealmに初めて触れ、SwiftUIではどのように実装できるのか気になったので調べ始めました。

実装した動作

・追加ボタン
 Listに「First Title」と0〜100の乱数の行を追加
・ゴミ箱マーク
 対応する行を削除
・全削除ボタン
 行を全て削除
・行のタイトルを長押し
 「First Title」を「Changed Title」にリネーム、乱数再取得
・Realmで保存
 アプリを落としてもデータ保存

ソースコード

まずはデータの元となるコードを記述します。

ItemDB.swift
import RealmSwift
// Realm用のClass
class ItemDB: Object {
  @objc dynamic var id = 0
  @objc dynamic var title = ""
  @objc dynamic var number = 0

  // 主キーを使うと、データの更新や削除に便利
  override static func primaryKey() -> String? {
    "id"
  }
}
Item.swift
import Foundation

struct Item: Identifiable {
  let id: Int
  let title: String
  let number: Int
}

extension Item {
  init(itemDB: ItemDB) {
    id = itemDB.id
    title = itemDB.title
    number = itemDB.number
  }
}
ItemStore.swift
import RealmSwift

final class ItemStore: ObservableObject {

  private var itemResults: Results<ItemDB>

  // itemResultsにDBのデータをセット
  init(realm: Realm) {
    itemResults = realm.objects(ItemDB.self)
  }

  var items: [Item] {
    itemResults.map(Item.init)
  }
}

ItemStore.swiftには、DBを操作する以下のメソッドも記述します。
上記の実装した動作を行うためのものです。

  • create (データを追加)
  • update (データを更新)
  • delete (データを削除)
  • deleteAll (データを全削除)
ItemStore.swift
extension ItemStore {
  // データを追加
  func create() {
    // これを書かないとDBの変更をViewに伝えることができません。
    objectWillChange.send()

    do {
      let realm = try Realm()
      let itemDB = ItemDB()
      itemDB.id = UUID().hashValue
      itemDB.title = "First Title"
      itemDB.number = Int.random(in: 0...100)
      try realm.write {
        realm.add(itemDB)
      }
    } catch let error {
      print(error.localizedDescription)
    }
  }

  // データを更新
  func update(itemID: Int) {
    objectWillChange.send()

    do {
      let realm = try Realm()
      try realm.write {
        realm.create(ItemDB.self,
                     value: ["id": itemID, 
                             "title": "Changed Title", 
                             "number": Int.random(in: 0...100)],
                     update: .modified)
      }
    } catch let error {
      print(error.localizedDescription)
    }
  }

  // データを削除
  func delete(itemID: Int) {
    objectWillChange.send()

    guard let itemDB = itemResults.first(where: { $0.id == itemID})
    else {
      return
    }

    do {
      let realm = try Realm()
      try realm.write {
        realm.delete(itemDB)
      }
    } catch let error {
      print(error.localizedDescription)
    }
  }

  // データを全削除
  func deleteAll() {
    objectWillChange.send()

    do {
      let realm = try Realm()
      try realm.write {
        realm.deleteAll()
      }
    } catch let error {
      print(error.localizedDescription)
    }
  }
}

続いてViewとなるコードを書いていきます。

ItemRowView.swift
import SwiftUI

struct ItemRowView: View {
  @EnvironmentObject var store: ItemStore
  let item: Item

  var body: some View {
    HStack{
      Text(item.title)
        // タイトルの長押しでデータの更新
        .onLongPressGesture {
          store.update(itemID: item.id)
        }
      Spacer()
      Text(String(item.number))
      // ゴミ箱マークを設置して、削除を実装
      Image(systemName: "trash.circle.fill")
        .resizable()
        .frame(width: 24, height: 24)
        .foregroundColor(.red)
        .onTapGesture {
          store.delete(itemID: item.id)
        }
    }
  }
}
ItemListView.swift
import SwiftUI

struct ItemListView: View {
  @EnvironmentObject var store: ItemStore
  let items: [Item]

  var body: some View {
    List {
      Section(header: sectionHeaderView) {
        ForEach(items) { item in
          HStack{
            ItemRowView(item: item)
          }
        }
      }
    }
    .navigationTitle("RealmDBリスト")
  }

  // ヘッダーに追加ボタンと全削除ボタンを設置
  var sectionHeaderView: some View {
    HStack {
      Button("追加", action: store.create)
      Spacer()
      Button("全削除", action: store.deleteAll)
    }
  }
}
ContentView.swift
import SwiftUI

struct ContentView: View {
  @EnvironmentObject var store: ItemStore

  var body: some View {
    NavigationView {
      ItemListView(items: store.items)
    } 
  }
}

最後に、「SceneDelegate.swift」の一部を書き換えて完了です。

SceneDelegate.swift
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 
  if let windowScene = scene as? UIWindowScene {
    do {
      let realm = try Realm()
      let window = UIWindow(windowScene: windowScene)
      // Realmを最初に読み込むところ
      let contentView = ContentView()
        .environmentObject(ItemStore(realm: realm))
      window.rootViewController = UIHostingController(rootView: contentView)
      self.window = window
      window.makeKeyAndVisible()
    } catch let error {
      fatalError("Failed to open Realm. Error: \(error.localizedDescription)")
    }      
  }
}

ソースコード全体はこちら
https://github.com/takuma-2531/SwiftUI_Realm_1To1

参考にしたサイト

https://www.raywenderlich.com/12235561-realm-with-swiftui-tutorial-getting-started
SwiftUIでのRealmの使い方を調べた中で、このサイトが一番わかりやすかったです。
英語ですが、Google翻訳を駆使して読みました。
このサイトのコードをより単純にしたものが上記サンプルです。

おわりに

最後まで読んでくださりありがとうございます。
SwiftUIやRealmを使う人の役に立てれば幸いです。

24
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
20