ios @发布的领域对象未触发SwiftUI中的视图重画

kx5bkwkv  于 2023-01-22  发布在  iOS
关注(0)|答案(1)|浏览(107)

在我们的应用中,我们使用UserService,它是一个ObservableObject,并作为环境传递。打开一个同步领域,并使用灵活的同步获取应用用户(RealmObject)。
当更新用户属性时,比如用户名,视图不会被重绘。这与我的预期不符,因为UserService包含一个@Published属性,用户(正在编辑)存储在其中。在数据库上,它清楚地显示正在编辑的属性,但是视图不会被重绘,只有当重新启动应用程序时,新属性才会显示。
UserService对象负责所有与用户相关的逻辑(存储用户对象(对它的引用),包含要更新的函数,......)并使用它在整个视图中显示该用户的活动数据的最佳方法是什么?
下面是一个MRE(省略登录逻辑以降低复杂性):

import SwiftUI
import RealmSwift

class UserService2: ObservableObject {
    
    var realm: Realm
    @Published var ownUser: User

    var userNotificationToken: NotificationToken?
    
    init(realm: Realm, ownUser: User) {
        self.realm = realm
        self.ownUser = ownUser

        userNotificationToken = ownUser.observe { change in
            print(change) // just to see that the user object is actually live and being updated...
        }
    }
    
    func changeName(newName: String) {
        do {
            try self.realm.write {
                self.ownUser.userName = newName
            }
        } catch {
            print("error")
        }
    }
}
struct TestView: View {
    
    @EnvironmentObject var userService: UserService2
    
    var body: some View {
        VStack {
            Text(userService.ownUser.userName ?? "no name")
            Button {
                userService.changeName(newName: Date().description)
            } label: {
                Text("change name")
            }
        }
    }
}

struct ContentView: View {
    
    var realm: Realm? = nil
    
    init() {
        let flexSyncConfig = app.currentUser!.flexibleSyncConfiguration(initialSubscriptions: { subs in
            subs.append(
                QuerySubscription<User>(name: "ownUserQuery") {
                    $0._id == "123"
                })
        })
        do {
            let realm = try Realm(configuration: flexSyncConfig)
            self.realm = realm
        } catch {
            print("sth went wrong")
        }
    }
    
    var body: some View {
        if let realm = realm, let ownUser = realm.objects(User.self).where( { $0._id == "123" } ).first {
            TestView()
                .environmentObject(UserService2(realm: realm, ownUser: ownUser))
        } else {
            ProgressView()
        }
    }
}

User对象如下所示

import Foundation
import RealmSwift

class User: Object, ObjectKeyIdentifiable {
    @Persisted(primaryKey: true) var _id = UUID().uuidString
    @Persisted var userName: String?
    
    convenience init(_id: String? = nil, userName: String? = nil) {
        self.init()
        if let _id = _id {
            self._id = _id
        }
        self.userName = userName
    }
}

我想我可以使用realm来观察对象的变化,并以某种方式强制视图刷新,但我会发现使用现有的方法来观察变化并在需要时使用@Published来重绘视图要干净得多...
P. P. S.这个用户对象是在服务器上创建的,当有人进行身份验证时使用触发器。不过,我假设这与这个问题并不真正相关。

rta7y2nd

rta7y2nd1#

这里的问题是引用类型作为“事实来源”的用法。
ObservableObject和SwiftUI视图使用合并发布程序来了解何时刷新。
@Published值仅在其 Package 值“更改”时发送ObservableObject.objectWillChange发布者。在此上下文中,“更改”意味着它被替换。因此,此处首选值类型,因为如果更改其中一个属性,则整个对象将被替换。引用类型不会发生这种情况。
此处有多种可能的解决方案:

  • User类更改为struct(此处可能不需要,因为此对象实现了Realm)
  • 在更改用户之前自己使用.objectWillChange.send()方法
  • 不要改变ownUser变量,而要用包含新信息的新变量替换它。
func changeName(newName: String) {
    do {
        self.objectWillChange.send() //add this
        try self.realm.write {
            self.ownUser.userName = newName
        }
    } catch {
        print("error")
    }
}

相关问题