使用Swift 5.1 Property Wrapper似乎破坏了SwiftUI更新。已知问题?

时间:2019-11-28 16:20:35

标签: ios swift swiftui

这是一个难题:在ObservableObject属性上实现一个简单的包装将否定SwiftUI更新。

属性包装器的目的是为UserDefaults提供一个getter / setter方法。属性包装器工作正常。但是它扼杀了SwiftUI的更新。

我尝试过的事情:

-除了我的自定义包装器之外,还添加了@Published包装器。但是,Swift目前不支持可组合包装器(例如,一次不只一个属性包装器)。

-手动添加PassthoughSubject发布者,以复制@Published功能。

后者也不能解决有关属性值更改的视图更新。

我发现的唯一解决方法是放弃属性包装器。我最终不得不在两个地方设置状态,1)@Published属性上的状态,以及2)UserDefaults中的状态。这似乎很笨拙,而且是复制状态的反模式。

这是已知问题吗?

完整的编译代码位于https://github.com/taskcruncher/propertyWrapperSwiftUIBug.git。 “工作”分支是我必须复制状态的地方。下面是“残破”分支,其中使用自定义属性包装器将中断SwiftUI更新。

ContentView.swift

import Combine
//https://www.avanderlee.com/swift/property-wrappers/

@propertyWrapper
struct PersistInUserDefaults<T> {
    let key: String
    let defaultValue: T

    var wrappedValue: T {
        get {
            return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
        }
        set {
            UserDefaults.standard.set(newValue, forKey: key)
        }
    }
}


class AppleUser: ObservableObject {

    static var shared = AppleUser()

    let subject = PassthroughSubject<String, Never>()

    @PersistInUserDefaults(key: "appleID", defaultValue: "") var appleID: String {willSet {

        subject.send(newValue) // problem here: does not trigger UI updates 
        }}
}


struct ContentView: View {

    @ObservedObject var appleUser: AppleUser

    var body: some View {

        return VStack {
            Text(appleUser.appleID)

        }.onAppear{

            DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
                         self.appleUser.appleID = "Sven" 
                     }


            DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) {
                self.appleUser.appleID = "Olaf"
            }//update not reflected in UI

            DispatchQueue.main.asyncAfter(deadline: .now() + 3.5) {
                          self.appleUser.appleID = "Anna"
                      }//update not reflected in UI

        }
    }

}

SceneDelegate.swift

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

        let contentView = ContentView(appleUser: AppleUser.shared)

        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: contentView)
            self.window = window
            window.makeKeyAndVisible()
        }

    }

1 个答案:

答案 0 :(得分:1)

您可能需要在两个不同的区域中分隔@publish和@persist属性。 尽管现在不支持可组合属性包装器,但是它们的链在大多数时间仍然有效。

                    @propertyWrapper
            struct PersistInUserDefaults<T> {
                let key: String
                let defaultValue: T

                var wrappedValue: T {
                    get {
                        return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
                    }
                    set {
                        UserDefaults.standard.set(newValue, forKey: key)
                    }
                }
            }

            struct PersistInUserDefaults_Wrapper {
              @PersistInUserDefaults(key: "appleID", defaultValue: "") var value
            }

            class AppleUser: ObservableObject {
                static var shared = AppleUser()
                @Published var appleID = PersistInUserDefaults_Wrapper()
            }

            struct ContentMMMMSSSView: View {

                @ObservedObject var appleUser: AppleUser

                var body: some View {

                    return VStack {
                        Text(appleUser.appleID.value)

                    }.onAppear{

                        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
                            self.appleUser.appleID.value = "Leonard"
                                 }


                        DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) {
                            self.appleUser.appleID.value = "Bill"
                        }//

                        DispatchQueue.main.asyncAfter(deadline: .now() + 3.5) {
                            self.appleUser.appleID.value  = ""
                                  }

                    }
                }

            }