Hi All.
I have a question why a Dynamic Property wrapper is not working as I expect it to work.
If I do this with @AppStorage
it works as expected, but with @iCloudStorage
it does not work.
Demo code with @AppStorage
:
class DataClass: ObservableObject {
@AppStorage("dataSource")
var dataSource: String = "DataSource"
}
struct ContentView: View {
@ObservedObject var data = DataClass()
var body: some View {
VStack {
Text(data.dataSource) // this does update
Button("Change") {
data.dataSource = "New DataSource"
}
}
}
}
iCloudStorage property wrapper:
import Foundation
import SwiftUI
/// A property wrapper that reads and writes to iCloud.
///
/// Example:
/// ```
/// @iCloudStorage("key") var value: String = "default"
/// ```
@propertyWrapper
public struct iCloudStorage<T>: DynamicProperty {
// swiftlint:disable:previous type_name
/// The key to read and write to.
let key: String
/// The default value to use if the value is not set yet.
let defaultValue: T
@State private var value: T
/// Creates an `iCloudStorage` property.
///
/// - Parameter wrappedValue: The default value.
/// - Parameter key: The key to read and write to.
public init(wrappedValue: T, _ key: String) {
self.key = key
self.defaultValue = wrappedValue
self.value = NSUbiquitousKeyValueStore.default.object(forKey: key) as? T ?? defaultValue
}
/// The value of the key in iCloud.
public var wrappedValue: T {
get {
return value
}
nonmutating set {
NSUbiquitousKeyValueStore.default.set(newValue, forKey: key)
value = newValue
}
}
/// A binding to the value of the key in iCloud.
public var projectedValue: Binding<T> {
Binding {
return self.wrappedValue
} set: { newValue in
self.wrappedValue = newValue
}
}
}
Demo code with @iCloudStorage
property wrapper:
class DataClass: ObservableObject {
@iCloudStorage("dataSource")
var dataSource: String = "DataSource"
}
struct ContentView: View {
@ObservedObject var data = DataClass()
var body: some View {
VStack {
Text(data.dataSource) // this does not update
Button("Change") {
data.dataSource = "New DataSource"
}
}
}
}
If I pass it directly to the ContentView
it works as expected, but if I pass it to the DataClass
class it does not work as expected.
This works "slightly" better, but this takes a lot of time to update, and only updates if something else changes in the view.
@propertyWrapper
public struct iCloudStorage<T>: DynamicProperty {
// swiftlint:disable:previous type_name
/// The key to read and write to.
let key: String
/// The default value to use if the value is not set yet.
let defaultValue: T
final private class Storage: ObservableObject {
var value: T {
willSet {
print("Willset")
objectWillChange.send()
}
}
init(_ value: T) {
self.value = value
}
}
@ObservedObject private var value: Storage
/// Creates an `iCloudStorage` property.
///
/// - Parameter wrappedValue: The default value.
/// - Parameter key: The key to read and write to.
public init(wrappedValue: T, _ key: String) {
self.key = key
self.defaultValue = wrappedValue
self.value = Storage(
NSUbiquitousKeyValueStore.default.object(forKey: key) as? T ?? defaultValue
)
}
/// The value of the key in iCloud.
public var wrappedValue: T {
get {
return value.value
}
nonmutating set {
NSUbiquitousKeyValueStore.default.set(newValue, forKey: key)
value.value = newValue
}
}
/// A binding to the value of the key in iCloud.
public var projectedValue: Binding<T> {
Binding {
return self.wrappedValue
} set: { newValue in
value.value = newValue
self.wrappedValue = newValue
}
}
}