Realm SwiftとSwiftUIを使ったテーブル実装を試してみました。
注) 2020/04/26時点でRealmはSwiftUIを公式サポートしていません。
対応状況は下Issueを参考にしてみて下さい。
表示
プロジェクトを作ったらCocoaPodsでRealmSwiftを追加します。
platform :ios, '13.0' target 'MyApp' do use_frameworks! pod 'RealmSwift' end
Realmの追加が終わったらEntityを作成します。
定義は下の通りです。
import UIKit import RealmSwift class MyModel: Object { @objc dynamic var id = UUID().uuidString @objc dynamic var title = "" }
次はこのデータをテーブル表示します。
後々のことを考えて、データ一覧はViewModelに入れています。
import SwiftUI import RealmSwift struct ContentView: View { @ObservedObject var model = ContentViewModel() var body: some View { List { ForEach(model.myModels, id: \.id) { myModel in Button(action: { }) { Text("\(myModel.title)") } } } } } class ContentViewModel: ObservableObject { @Published var myModels: [MyModel] = (try? Realm().objects(MyModel.self).map { $0 }) ?? [] }
テストデータを3件作ってから実行すると下のようになります。
日付をそのままtitleに入れたので下のような表示になっています。
編集
次はデータの更新をします。
Cellをタップしたら現在時刻をtitleにセットするような実装にしたいと思います。
まずはViewModeを以下のように修正します。
ここではRealmのobserveを使って更新を受け取るように変更しています。
class ContentViewModel: ObservableObject { private var token: NotificationToken? private var myModelResults = try? Realm().objects(MyModel.self) @Published var myModels: [MyModel] = [] init() { token = myModelResults?.observe { [weak self] _ in self?.myModels = self?.myModelResults?.map { $0 } ?? [] } } deinit { token?.invalidate() } }
続けてView側も修正します。
struct ContentView: View { @ObservedObject var model = ContentViewModel() var body: some View { List { ForEach(model.myModels, id: \.id) { myModel in Button(action: { try? Realm().write { myModel.title = "\(Date())" } }) { Text("\(myModel.title)") } } } } }
これで、ボタンをタップする度にtitleが更新されるようになりました。
追加
次はデータの追加を実装します。
ナビゲーションバーの追加ボタンを押したらデータが増えるような仕様にしたいと思います。
Viewを下のように変更します。
追加したのはnavigationBarItems以下の処理です。
struct ContentView: View { @ObservedObject var model = ContentViewModel() var body: some View { NavigationView { List { ForEach(model.myModels, id: \.id) { myModel in Button(action: { try? Realm().write { myModel.title = "\(Date())" } }) { Text("\(myModel.title)") } } }.navigationBarItems(trailing: Button(action: { let myModel = MyModel() myModel.title = "\(Date())" let realm = try? Realm() try? realm?.write { realm?.add(myModel) } }) { Text("Add") }) } } }
これでデータの追加ができるようになりました。
削除
最後に削除処理を実装します。
削除処理は下のようにonDeleteを付ける事で実現できます。
struct ContentView: View { @ObservedObject var model = ContentViewModel() var body: some View { NavigationView { List { ForEach(model.myModels, id: \.id) { myModel in Button(action: { try? Realm().write { myModel.title = "\(Date())" } }) { Text("\(myModel.title)") } }.onDelete { indexSet in if let index = indexSet.first { let realm = try? Realm() try? realm?.write { realm?.delete(self.model.myModels[index]) } } } }.navigationBarItems(trailing: Button(action: { let myModel = MyModel() myModel.title = "\(Date())" let realm = try? Realm() try? realm?.write { realm?.add(myModel) } }) { Text("Add") }) } } }
実行すると削除ボタンが出るようになります。
ただ、削除しようとすると以下のようなエラーが出てしまいます。
Terminating app due to uncaught exception 'RLMException', reason: 'Object has been deleted or invalidated.'
これはデータを削除した後もSwiftUIが削除済みデータにアクセスするからです。
今回はcellの対になるViewModelを作る事で対応しました。
ViewModelの定義は下の通りです。
struct ContentViewCellModel { let id: String let title: String }
View全体のViewModelもContentViewCellModelの配列を持つようにします。
class ContentViewModel: ObservableObject { private var token: NotificationToken? private var myModelResults = try? Realm().objects(MyModel.self) @Published var cellModels: [ContentViewCellModel] = [] init() { token = myModelResults?.observe { [weak self] _ in self?.cellModels = self?.myModelResults?.map { ContentViewCellModel(id: $0.id, title: $0.title) } ?? [] } } deinit { token?.invalidate() } }
Viewもこれに合わせて変更します。
struct ContentView: View { @ObservedObject var model = ContentViewModel() var body: some View { NavigationView { List { ForEach(model.cellModels, id: \.id) { cellModel in Button(action: { let myModel = try? Realm().objects(MyModel.self).filter("id = %@", cellModel.id).first try? Realm().write { myModel?.title = "\(Date())" } }) { Text("\(cellModel.title)") } }.onDelete { indexSet in let realm = try? Realm() if let index = indexSet.first, let myModel = realm?.objects(MyModel.self).filter("id = %@", self.model.cellModels[index].id).first { try? realm?.write { realm?.delete(myModel) } } } }.navigationBarItems(trailing: Button(action: { let myModel = MyModel() myModel.title = "\(Date())" let realm = try? Realm() try? realm?.write { realm?.add(myModel) } }) { Text("Add") }) } } }
これで削除しても落ちなくなりました。