【SwiftUI】Realmの@ObservedResultsの使い方!値の観測と解凍

この記事からわかること

  • Swiftデータ永続的保存する方法
  • Realm変更観測する方法
  • @ObservedResults使い方
  • configurationの指定
  • コレクションの操作方法
  • 凍結したRealmの意味とthawメソッド

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

iOSアプリのデータを永続的に保存する目的で使用されるデータベース「Realm(レルム)」で値の変更を観測する@ObservedResultsの使い方をまとめていきます。

Realmの使い方や導入方法に関しては以下の記事を参考にしてください。

参考文献: React to Changes - SwiftUI

@ObservedResults:クエリ結果を観測

公式リファレンス:ObservedResults - SwiftUI

@ObservedResultsRealm Swiftライブラリで使用できるプロパティーラッパーの1つです。

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
@propertyWrapper  public struct ObservedResults: DynamicProperty, BoundCollection where ResultType: _ObservedResultsValue & RealmFetchable & KeypathSortable & Identifiable {
}

@ObservedResultsを使用することでRealmデータベースに格納されているオブジェクト(レコード)のコレクションを観測することができます。付与したプロパティからデータベースのオブジェクトに参照できるので中からデータを取り出すことが可能です。

objects()メソッドを使用した結果と同じ以下のようなResults<Element>形式で取得できます。

Results<User> <0x13ec05050> (
	[0] User {
		id = EEE309AB-0482-4CE7-8D70-9DF6BAC03F9C;
		name = ame;
		age = 20;
	}
}

使い方

使用するには@ObservedResultsの引数に取り出したいテーブルクラスを指定します。

struct ContentView: View {
  
  @ObservedResults(User.self) var users
  
  var body: some View {
      List(users) { user in
              Text(user.name)
      }.listStyle(GroupedListStyle())
  }
}

configurationを指定する

@ObservedResults構造体は以下のようなイニシャライザを持っているのでRealm.Configuration明示的に設定することも可能です。

public init(_ type: ResultType.Type,
    configuration: Realm.Configuration? = nil,
    where: ((Query<ResultType>) -> Query<Bool>)? = nil,
    keyPaths: [String]? = nil,
    sortDescriptor: SortDescriptor? = nil) where ResultType: Object

例えばテーブルクラスのプロパティを追加し、マイグレーションを行うときは以下のように@ObservedResultsのイニシャライザからschemaVersionを指定することができます。

struct ContentView: View {
  
  @ObservedResults(User.self,configuration: Realm.Configuration(schemaVersion: 1)) var users
  
  var body: some View {
      List(users) { user in
              Text(user.name)
      }.listStyle(GroupedListStyle())
  }
}

親ビューからrealmConfigurationを引き継ぐ

@ObservedResults子ビューで使用している場合は親ビューから設定を引き継ぐことも可能です。

ChildView().environment(\.realm, try! .init(configuration: Realm.Configuration()))

取得するデータにフィルターをかける

引数where条件を渡すことで取得するオブジェクトにフィルターをかけることも可能です。

struct ContentView: View {
  
  @ObservedResults(User.self,where: {$0.age < 10}) var users
  
  var body: some View {
      List(users) { user in
              Text(user.name)
      }.listStyle(GroupedListStyle())
  }
}

取得するデータにソートをかける

引数sortDescriptorSortDescriptor構造体型の条件を渡すことで取得するデータにソートをかけることができます。

keyPathにはプロパティ名をascendingには昇順であればtrueを降順であればfalseを渡します。

struct ContentView: View {
  
  @ObservedResults(User.self,sortDescriptor:SortDescriptor(keyPath: "age", ascending: false)) var users
  
  var body: some View {
      List(users) { user in
              Text(user.name)
      }.listStyle(GroupedListStyle())
  }
}

コレクションの操作

@ObservedResultsで参照できる変数に$を付与することでコレクションの操作も可能になります。これはRealmSwift.BoundCollectionプロトコルの持つ特徴で内部的にwriteトランザクションが実行されておりwriteの記述が省略可能になります。

例えば以下のようにUserを削除する処理(#1)Userを追加ボタン(#2)がコレクション操作を行うだけで実装可能になっています。

struct ContentView: View {
  
  @ObservedResults(User.self) var users
  
  var body: some View {
      List {
          ForEach(users) { user in
              Text(user.name)
          }.onDelete(perform: $users.remove) // (#1)
      }.listStyle(GroupedListStyle())
      // (#2)
      Button(action: {
            let user = User()
            user.name = "ame"
            user.age = 20
            $users.append(user)
            
        }, label: {
            Text("User追加")
        })
  }
}

onDeleteでコレクションを削除

コレクションを削除する時はonDeleteモディファイアを使用して以下のように記述することでList表示している1行単位でスワイプすることでデータを削除できます。

.onDelete(perform: $users.remove) // (#1)

上記の記法だとこの処理しか記述できないので引数にindexを受け取るクロージャにすることで別の処理も混ぜることができるようになります。

.onDelete(perform: { index in
    $users.remove(atOffsets: index)
    // 別の処理
})

Realmを解凍して使う

逆にwriteメソッドを自分で呼び出して使う場合は注意が必要です。デフォルトでは@ObservedResultsは凍結しており、writeメソッドを使って処理を実行しようとするとエラーになってしまいます。

エラーが発生するコード

struct ContentView: View {
    @ObservedResults(User.self) var users
    var body: some View {
        List {
            ForEach(users) { user in
                HStack{
                    Text(user.name)
                    Text("\(user.age)")
                }.onTapGesture {
                    try! user.realm!.write{
                        user.age = 80
                    }
                }
            }.onDelete(perform: $users.remove)
        }.listStyle(GroupedListStyle())
  }
}

エラー内容

Thread 1: "Can't perform transactions on a frozen Realm"
// スレッド 1: 「凍結されたレルムでトランザクションを実行できません」

thawメソッド

これを解決するには凍結されたRealmを使用できるように解凍します。解凍するにはthawメソッドを使います。これでwriteメソッドが使用可能になります。またfreezeメソッドを使用することで凍結させることも可能です。

struct ContentView: View {
    @ObservedResults(User.self) var users
    var body: some View {
        List {
            ForEach(users) { user in
                HStack{
                    Text(user.name)
                    Text("\(user.age)")
                }.onTapGesture {
                    let thawUser = user.thaw()
                    try! thawUser?.realm!.write{
                        thawUser?.age = 80
                    }
                    //  thawUser?.freeze()
                }
            }.onDelete(perform: $users.remove)
        }.listStyle(GroupedListStyle())
  }
}

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

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

私がSwift UI学習に使用した参考書

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

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

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index