首页 > 解决方案 > 在领域数据库中进行更改时,如何在 SwiftUI 中自动更新 UI?

问题描述

我的领域数据库结构如下例所示:

class Person: Object, Identifiable {
   @objc dynamic var id: String = NSUUID().uuidString
   @objc dynamic var name: String = "" 
   var dogs = RealmSwift.List<Dog>()
}

class Dog: Object, Identifiable {
   @objc dynamic var id: String = NSUUID().uuidString
   @objc dynamic var name: String = "" 
   let human = RealmSwift.LinkingObjects<Person>(fromType: Person.self, property: "dogs")
}

为了简化 CRUD 操作,我有一个数据库管理器

extension Realm {
public func safeWrite(_ block: (() throws -> Void)) throws {
    if isInWriteTransaction {
        try block()
    } else {
        try write(block)
    }
}
}

class DatabaseManager {

private let realm: Realm
public static let sharedInstance = DatabaseManager()

private init(){
    realm = try! Realm()
}

func save(_ obj: Object){
    do {
        try realm.safeWrite {
            realm.add(obj, update: .all)
        }
    } catch {
        NSLog("error saving object: %@", [error])
    }
}

func save(_ obj: Object,_ block: () -> Void){
    do {
        try realm.safeWrite{
            realm.add(obj, update: .all)
            block()
        }
    } catch {
        NSLog("error saving object: %@", [error])
    }
}


func save(_ objs: [Object]){
    do {
        try realm.safeWrite {
            realm.add(objs, update: .all)
        }
    } catch {
        NSLog("error saving object: %@", [error])
    }
}

func fetchData<T: Object>(type: T.Type) -> Results<T>{
    let results: Results<T> = realm.objects(type)
    return results
}

func delete(_ obj: Object){
    do {
        try realm.safeWrite {
            realm.delete(obj)
        }
    } catch {
        NSLog("error deleting object: %@", [error])
    }
}

func update(_ block: @escaping () -> Void){
    do {
        try realm.safeWrite{
            block()
        }
    } catch {
        NSLog("error updating object: %@", [error])
    }
}
}

现在我有不同的视图,我需要访问我的数据库并显示一些数据。对于这种情况,我创建了一个视图模型类

class PersonViewModel: ObservableObject {
    let realm = DatabaseManager.sharedInstance
    @Published var persons: Results<Person> = DatabaseManager.sharedInstance.fetchData(type: Person.self).sorted(byKeyPath: "name", ascending: true)

    public func fetch(){
        self.persons = DatabaseManager.sharedInstance.fetchData(type: Person.self).sorted(byKeyPath: "name", ascending: true)
    }

    public func addPerson(person: Person){
        realm.save(Person)
        self.fetch() // <---- necessary to update the UI: how to auto update?
    }
}

这个视图模型类将作为一个传递@EnvironmentObject给我需要数据的视图。如您所见,我需要在每次数据库操作后再次获取所有数据以进行“新鲜更新” Results<Person>。我知道这Results<T>是实时的,但它对 UI 没有影响。当我对数据库进行更改而不再次手动获取所有数据时,有什么方法可以自动更新所有视图?

标签: swiftuirealm

解决方案


虽然 Realm 会在删除、添加或更改新数据时自动更新集合,但需要有一个触发器来通知您的代码这些更改。

那是一个通知,也就是观察者。

在这种情况下,有一个persons使用 fetchData 函数填充的 Results 对象:

@Published var persons: Results<Person> = DatabaseManager.sharedInstance.fetchData(type: Person.self).sorted(byKeyPath: "name", ascending: true)

因此,当一个人被删除、添加或更改时,persons结果对象将自动更新。然而,要得到通知,需要添加一个观察者,并且有两个组件:一个类级别的通知令牌,然后是一个处理事件的函数。

通知令牌是这样定义的(注意需要强引用才能使其保持活动状态)

var notificationToken: NotificationToken? = nil

并确保在视图关闭时释放它

deinit {
    self.notificationToken?.invalidate()
}

然后是当人们发生变化时将执行的代码(以最简单的形式)

self.notificationToken = self.persons?.observe { changes in
    switch changes {
    case .initial:
        print("initial load")
        //the initial data is ready - reload tableviews/update ui etc

    case .update(_, _, _, _):
        print("update")
        //some change occurred, reload tableviews/update ui etc

    case .error(let error):
        fatalError("\(error)")
    }
}

这是一个深入介绍此内容的链接Collection Notifications


推荐阅读