0
0

More than 1 year has passed since last update.

[Firestore][iOS] Ballcap-iOS 使用時に子 View が更新されない場合がある

Last updated at Posted at 2021-12-18

Firestore のラッパーライブラリ Ballcap-iOS を使っていた時に、思っていたのと違う挙動が起きたので、解消法についてメモします。

問題は何?

以下の View で recreate item をタップすると vm.item が更新され、 ChildView の表示( item の生成時刻 = item.dateDebugDescription )が変わるはずですが、変わらないという問題です。

import SwiftUI

struct MyView: View {

    @ObservedObject var vm = MyViewModel()

    var body: some View {
        VStack {
            ChildView(item: vm.item)
            Button("recreate item") { vm.recreateItem() }
        }
    }

    struct ChildView: View {
        @ObservedObject var item:Item
        var body: some View {
            Text(item.dateDebugDescription)
        }
    }

}

class MyViewModel: ObservableObject {

    @Published var item:Item

    init() {
        let item = Item(id: "a")
        item.dateDebugDescription = Date().debugDescription
        self.item = item
    }

    func recreateItem() {
        let item = Item(id: "a")
        item.dateDebugDescription = Date().debugDescription
        self.item = item
    }

}


// GithubレポジトリのREADMEの「using-ballcap-with-swiftui」
// (https://github.com/1amageek/Ballcap-iOS#using-ballcap-with-swiftui)
// のクラス定義をコピーして
// クラス名を Item に変えて
// 「var dateDebugDescription = ""」を追加した
import Ballcap
import FirebaseFirestore

final class Item: Object, DataRepresentable, DataListenable, ObservableObject, Identifiable {

    typealias ID = String

    override class var name: String { "items" }

    struct Model: Codable, Modelable {
        var name: String = ""
    }

    @Published var data: Item.Model?

    var listener: ListenerRegistration?

    // MARK: -

    var dateDebugDescription = ""
}

原因は何?

Ballcap の Object.swift に次のコードがありました。

public extension DataRepresentable where Self: Equatable, Self: Object {

    static func == (lhs: Self, rhs: Self) -> Bool {
        lhs.path == rhs.path && lhs.data == rhs.data
    }
}

pathdataが一致していれば、インスタンスが異なっても==が成立するみたいです。

ちなみに、

  • ItemクラスはDataRepresentableプロトコルに準拠している。
  • DataRepresentableプロトコルはHashableプロトコルに準拠している。
  • HashableプロトコルはEquatableプロトコルに準拠している。

というわけで、 ItemクラスはEquatableプロトコルに準拠しているみたいです。

MyViewの再描画時にChildView(item: vm.item)の引数の新旧で==が成り立っていることが原因かと思い色々調べると、そうでした。

解消法

少なくとも、Object.swiftDataRepresentable.swiftDataListenable.swiftでは==を使っていなかったので、==の実装を上書きすることにしました。

インスタンス識別子を比較するようにします。
どこかにこのコードを書けばOKです。

public extension DataRepresentable where Self: Equatable, Self: Object {

    static func == (lhs: Self, rhs: Self) -> Bool {
        // インスタンス識別子で比較
        ObjectIdentifier(lhs).hashValue == ObjectIdentifier(rhs).hashValue
    }
}
0
0
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
0
0