这是一个难题:在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()
}
}
答案 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 = ""
}
}
}
}