I am using a property wrapper to save my User Default values. On iOS 13 devices, this solution works great. However on iOS 11 and iOS 12, the values are not being saved into User Defaults. I read that property wrappers are backwards compatible so I don't know why this would not work on older iOS versions.
This is the property wrapper:
@propertyWrapper
struct UserDefaultWrapper<T: Codable> {
private let key: String
private let defaultValue: T
init(key: String, defaultValue: T) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: T {
get {
guard let data = UserDefaults.standard.object(forKey: key) as? Data else {
// Return defaultValue when no data in UserDefaults
return defaultValue
}
// Convert data to the desire data type
let value = try? JSONDecoder().decode(T.self, from: data)
return value ?? defaultValue
}
set {
// Convert newValue to data
let data = try? JSONEncoder().encode(newValue)
UserDefaults.standard.set(data, forKey: key)
UserDefaults.standard.synchronize()
}
}
}
struct UserDefault {
@UserDefaultWrapper(key: "userIsSignedIn", defaultValue: false)
static var isSignedIn: Bool
}
I can then set the value like this:
UserDefault.isSignedIn = true
Am I using the property wrapper wrong? Is anyone else running into issues with property wrappers on older iOS versions?
Thus you create an UserDefaults wrapper to encapsulate the UserDefaults read and write logic. You will use the UserDefaults wrapper to keep track on the auto login “On” / “Off” status, as well as the user’s username. This is how your UserDefaults wrapper usually looks like:
Property type ‘Bool’ does not match that of the ‘wrappedValue’ property of its wrapper type ‘Storage’ This is because our property wrapper currently only support String data type. In order to fix both errors, we will have to make our property wrapper generic.
With a generic property wrapper, our UserDefaults wrapper can now store a boolean value without any problem. At this point, our UserDefaults wrapper is able to store any basic data types such as String, Bool, Int, Float, Array, etc.
Before we get into the details, let’s have a quick introduction on what is property wrapper. Basically, a property wrapper is a generic data structure that can intercept the property’s read / write access, thus enabling custom behaviour being added during the property’s read / write operation.
Nothing to do with property wrappers! The problem is that in iOS 12 and before, a simple value like a Bool (or String, etc.), though Codable as a property of a Codable struct (for example), cannot itself be JSON encoded. The error (which you are throwing away) is quite clear about this:
Top-level Bool encoded as number JSON fragment.
To see this, just run this code:
do {
_ = try JSONEncoder().encode(false)
print("succeeded")
} catch {
print(error)
}
On iOS 12, we get the error. On iOS 13, we get "succeeded"
.
But if we wrap our Bool (or String, etc.) in a Codable struct, all is well:
struct S : Codable { let prop : Bool }
do {
_ = try JSONEncoder().encode(S(prop:false))
print("succeeded")
} catch {
print(error)
}
That works fine on both iOS 12 and iOS 13.
And that fact suggests a solution! Redefine your property wrapper so that it wraps its value in a generic Wrapper struct:
struct UserDefaultWrapper<T: Codable> {
struct Wrapper<T> : Codable where T : Codable {
let wrapped : T
}
private let key: String
private let defaultValue: T
init(key: String, defaultValue: T) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: T {
get {
guard let data = UserDefaults.standard.object(forKey: key) as? Data
else { return defaultValue }
let value = try? JSONDecoder().decode(Wrapper<T>.self, from: data)
return value?.wrapped ?? defaultValue
}
set {
do {
let data = try JSONEncoder().encode(Wrapper(wrapped:newValue))
UserDefaults.standard.set(data, forKey: key)
} catch {
print(error)
}
}
}
}
Now it works on iOS 12 and iOS 13.
By the way, I actually think you would do better to save as a property list rather than JSON. But that makes no difference to the question generally. You can’t encode a bare Bool as a property list either. You’d still need the Wrapper approach.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With