Error Using property Wrapper in class swift 5.1 - swift

I am using UserDefaults and Combine in SwiftUI.
My UserDefault.swift file:
import SwiftUI
struct UserDefault<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)
}
}
}
When using this struct in the following class as follows:
DataStore.swift file:
import SwiftUI
import Combine
final class DataStore : ObservableObject { //(1)
let didChange = PassthroughSubject<DataStore, Never>()
#UserDefault(key: "firstLaunch", defaultValue: true) //(2)
var firstLaunch:Bool{
didSet{
didChange.send(self)
}
}
}
In the above code, I am getting 2 errors:
(1):Class 'DataStore' has no initializers
(2):Generic struct 'UserDefault' cannot be used as an attribute
I think there is a change or depreciation in swift 5.1, but I am unable to find it.

Use something like this:
#propertyWrapper
struct UserDefault<T> {
let key: String
let defaultValue: T
init(key: String, defaultValue: T) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: T {
get {
return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
}
https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md

You need to add the #propertyWrapper annotation to your UserDefault struct.
#propertyWrapper
struct UserDefault<T> {
...

Related

SwiftUI: Encode a struct to be saved in AppStorage

Currently trying to build my first app in swiftUI. The part I thought would be the easiest as become a nightmare... save a struct in AppStorage to be available upon restart of the app
I got two struct to save. The first is for player and I have implemented the RawRepresentable
struct Player: Codable, Identifiable {
let id: Int
let name: String
let gamePlayed: Int
let bestScore: Int
let nbrGameWon: Int
let nbrGameLost: Int
let totalScore: Int?
}
typealias PlayerList = [Player]
extension PlayerList: RawRepresentable {
public init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8),
let result = try? JSONDecoder().decode(PlayerList.self, from: data)
else {
return nil
}
self = result
}
public var rawValue: String {
guard let data = try? JSONEncoder().encode(self),
let result = String(data: data, encoding: .utf8)
else {
return "[]"
}
return result
}
}
Calling in my view this way:
struct AddPlayerView: View {
#State var name: String = ""
#State var isDisabled: Bool = false
#State var modified: Bool = false
#AppStorage("players") var players: PlayerList = PlayerList()
...
}
The above works, now I also want to save the current game data, I have the following struct:
struct Game: Codable, Identifiable {
var id: Int
var currentPlayerIndexes: Int
var currentRoundIndex: Int?
var dealerIndex: Int?
var maxRounds: Int?
var dealResults: [Int: Array<PlayerRoundSelection>]?
var currentLeaderIds: Array<Int>?
var isGameInProgress: Bool?
}
extension Game: RawRepresentable {
public init?(rawValue: String) {
if rawValue == "" {
// did to fix issue when calling AppStorage, but it is probably a bad idea
self = Game(id:1, currentPlayerIndexes:1)
}
else {
guard let data = rawValue.data(using: .utf8),
let result = try? JSONDecoder().decode(Game.self, from: data)
else {
return nil
}
self = result
}
}
public var rawValue: String {
guard let data = try? JSONEncoder().encode(self),
let result = String(data: data, encoding: .utf8)
else {
return ""
}
return result
}
}
As soon as I try to modify the struct, it calls rawValue and the encoding fails with the following:
error: warning: couldn't get required object pointer (substituting NULL): Couldn't load 'self' because its value couldn't be evaluated
error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=2, address=0x7ffee49bbff8).
Here part of the code that access the struct:
struct SelectPlayersView: View {
#AppStorage("currentGame") var currentGame: Game = Game(rawValue: "")!
....
NavigationLink(
destination: SelectModeTypeView(), tag: 2, selection: self.$selection) {
ActionButtonView(text:"Next", disabled: self.$isDisabled, buttonAction: {
var currentPlayers = Array<Int>()
self.players.forEach({ player in
if selectedPlayers.contains(player.id) {
currentPlayers.insert(player.id, at: currentPlayers.count)
}
})
// This used to be a list of indexes, but for testing only using a single index
self.currentGame.currentPlayerIndexes = 6
self.selection = 2
})
...
I found the code to encode here: https://lostmoa.com/blog/SaveCustomCodableTypesInAppStorageOrSceneStorage/
My understanding is that with the self in the encode, it generate an infinite loop hence the bad access.
I have really no knowledge how to properly encode this, any help, links would be appreciated
I had the same problem and I wanted to share my experience here.
I eventually found that apparently you cannot rely on the default Codable protocol implementation when used in combination with RawRepresentable.
So when I did my own Codable implementation, with CodingKeys and all, it worked!
I think your Codable implementation for Game would be something like:
enum CodingKeys: CodingKey {
case currentPlayerIndexes
case currentRoundIndex
// <all the other elements too>
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.currentPlayerIndexes = try container.decode(Int.self, forKey: .currentPlayerIndexes)
self.currentRoundIndex = try container.decode(Int.self, forKey: .currentRoundIndex)
// <and so on>
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(currentPlayerIndexes, forKey: .currentPlayerIndexes)
try container.encode(currentRoundIndex, forKey: .currentRoundIndex)
// <and so on>
}
I then wondered why your Player coding/decoding did work and found that the default coding and decoding of an Array (i.e. the PlayerList, which is [Player]), works fine.

How can I implement a generic struct that manages key-value pairs for UserDefaults in Swift?

How would one implement a struct that manages UserDefaults mappings in Swift?
Right now I have some computed properties a, b, c, d of different types and corresponding keys that look like this:
enum UserDefaultsKeys {
a_key
b_key
...
}
var a: String {
get { UserDefaults.standard.string(forKey: UserDefaultsKeys.a_key.rawValue) }
set { UserDefaults.standard.set(newValue, forKey: UserDefaultsKeys.a_key.rawValue) }
}
var b: Int {
get { UserDefaults.standard.integer(forKey: UserDefaultsKeys.b_key.rawValue) }
set { UserDefaults.standard.set(newValue, forKey: UserDefaultsKeys.b_key.rawValue) }
}
...
What I would like to achieve instead is to implement a struct that has a key of type String and a generic type value.
The value get function should - depending on its type - chose wether to use UserDefaults.standard.string or UserDefaults.standard.integer so that I can just create a DefaultsVar with some key and everything else is managed automatically for me.
What I have so far is this:
struct DefaultsVar<T> {
let key: String
var value: T {
get {
switch self {
case is String: return UserDefaults.standard.string(forKey: key) as! T
case is Int: return UserDefaults.standard.integer(forKey: key) as! T
default: return UserDefaults.standard.float(forKey: key) as! T
}
}
set { UserDefaults.standard.set(newValue, forKey: key) }
}
}
I get the following error: "Cast from DefaultsVar to unrelated Type 'String' always fails.
I am completely new to swift (and relatively new to programming) and don't really understand how to implement this the right way - or if this is even an approach that would be considered a good practice. Could someone please shine some light on this?
Thank you in advance!
You can use a custom
Property wrapper:
#propertyWrapper
struct UserDefaultStorage<T: Codable> {
private let key: String
private let defaultValue: T
private let userDefaults: UserDefaults
init(key: String, default: T, store: UserDefaults = .standard) {
self.key = key
self.defaultValue = `default`
self.userDefaults = store
}
var wrappedValue: T {
get {
guard let data = userDefaults.data(forKey: key) else {
return defaultValue
}
let value = try? JSONDecoder().decode(T.self, from: data)
return value ?? defaultValue
}
set {
let data = try? JSONEncoder().encode(newValue)
userDefaults.set(data, forKey: key)
}
}
}
This wrapper can store/restore any kind of codable into/from the user defaults.
Usage
#UserDefaultStorage(key: "myCustomKey", default: 0)
var myValue: Int
iOS 14
SwiftUI has a similar wrapper (only for iOS 14) called #AppStorage and it can be used as a state. The advantage of using this is that it can be used directly as a State. But it requires SwiftUI and it only works from the iOS 14.

Make ObservableObject subscribe to custom PropertyWrapper

I have written a custom PropertyWrapper, that tries to wrap UserDefaults while also giving them the same behaviour as a #Published variable. It almost works, except that the ObservableObject does not propagate the changes without observing the UserDefaults themselves.
I cannot pass a objectWillChange ref to the #Setting init, as self is not available during Settings.init...
I wonder how #Published does that..
import Combine
import Foundation
class Settings: ObservableObject {
// Trying to avoid this:
/////////////////////////////////////////////
let objectWillChange = PassthroughSubject<Void, Never>()
private var didChangeCancellable: AnyCancellable?
private init(){
didChangeCancellable = NotificationCenter.default
.publisher(for: UserDefaults.didChangeNotification)
.map { _ in () }
.receive(on: DispatchQueue.main)
.subscribe(objectWillChange)
}
/////////////////////////////////////
static var shared = Settings()
#Setting(key: "isBla") var isBla = true
}
#propertyWrapper
public struct Setting<T> {
let key: String
let defaultValue: T
init(wrappedValue value: T, key: String) {
self.key = key
self.defaultValue = value
}
public var wrappedValue: T {
get {
let val = UserDefaults.standard.object(forKey: key) as? T
return val ?? defaultValue
}
set {
objectWillChange?.send()
publisher?.subject.value = newValue
UserDefaults.standard.set(newValue, forKey: key)
}
}
public struct Publisher: Combine.Publisher {
public typealias Output = T
public typealias Failure = Never
public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Downstream.Input == T, Downstream.Failure == Never {
subject.subscribe(subscriber)
}
fileprivate let subject: Combine.CurrentValueSubject<T, Never>
fileprivate init(_ output: Output) {
subject = .init(output)
}
}
private var publisher: Publisher?
internal var objectWillChange: ObservableObjectPublisher?
public var projectedValue: Publisher {
mutating get {
if let publisher = publisher {
return publisher
}
let publisher = Publisher(wrappedValue)
self.publisher = publisher
return publisher
}
}
}

swift - UserDefaults wrapper

I want to make userDefaults easier to use. I write this code.
But I don't want to create a allKeys enum(cases voipToken, userId, userName, displayName,...) as the key path for my property.
If I want add a var accessToken to struct LoginInfo, I must add a case accessToken to enum AllKeys. I use CoreStore.key(.accessToken) get a keyPath for CoreStore.LoginInfo.accessToken.
import Foundation
struct CoreStore {
// static let sharedInstance = CoreStore()
// private init(){}
private static let keyPrefix = "_shared_store_"
enum allKeys: String {
case accessToken
}
static func key(_ key: allKeys) -> String {
return keyPrefix + key.rawValue
}
struct LoginInfo: CoreStoreSettable,CoreStoreGettable { }
}
extension CoreStore.LoginInfo {
static var accessToken: String? {
set {
set(value: newValue, forKey: CoreStore.key(.accessToken))
}
get {
return getString(forKey: CoreStore.key(.accessToken))
}
}
// ...
}
protocol CoreStoreSettable {}
extension CoreStoreSettable {
static func set(value: String?, forKey key: String) {
UserDefaults.standard.set(value, forKey: key)
}
}
protocol CoreStoreGettable {}
extension CoreStoreGettable {
static func getString(forKey key: String) -> String? {
return UserDefaults.standard.string(forKey: key)
}
}
Questions:
Is there a way to remove "enum allKeys"?
Should I use sharedInstance? Why?
I tried :
static var accessToken: String? {
set {
let keyPath = \CoreStore.Loginfo.accessToken
let keyPathString = keyPath.string
set(value: newValue, forKey: keyPathString)
}
get {
return getString(forKey: CoreStore.key(.accessToken))
}
}
I get error "Type of expression is ambiguous without more context"

How can I store a Swift enum value in NSUserDefaults

I have an enum like this:
enum Environment {
case Production
case Staging
case Dev
}
And I'd like to save an instance in NSUserDefaults like this:
func saveEnvironment(environment : Environment){
NSUserDefaults.standardUserDefaults().setObject(environment, forKey: kSavedEnvironmentDefaultsKey)
}
I understand that a Swift enum isn't an NSObject, and that makes it difficult to save, but I'm unsure what the best way is to convert it to something storable.
Using rawValue for the enum is one way of using types that can be stored in NSUserDefaults, define your enum to use a rawValue. Raw values can be strings, characters, or any of the integer or floating-point number types :
enum Environment: String {
case Production = "Prod"
case Staging = "Stg"
case Dev = "Dev"
}
You can also create an enum instance directly using the rawValue (which could come from NSUserDefaults) like:
let env = Environment(rawValue: "Dev")
You can extract the rawValue (String) from the enum object like this and then store it in NSUserDefaults if needed:
if let myEnv = env {
println(myEnv.rawValue)
}
func saveEnvironment(environment : Environment){
NSUserDefaults.standardUserDefaults().setObject(environment.rawValue, forKey: kSavedEnvironmentDefaultsKey)
}
If you would like to save/read data from UserDefaults and separate some logic, you can do it in following way (Swift 3):
enum Environment: String {
case Production
case Staging
case Dev
}
class UserDefaultsManager {
static let shared = UserDefaultsManager()
var environment: Environment? {
get {
guard let environment = UserDefaults.standard.value(forKey: kSavedEnvironmentDefaultsKey) as? String else {
return nil
}
return Environment(rawValue: environment)
}
set(environment) {
UserDefaults.standard.set(environment?.rawValue, forKey: kSavedEnvironmentDefaultsKey)
}
}
}
So saving data in UserDefaults will look this way:
UserDefaultsManager.shared.environment = Environment.Production
And reading data, saved in UserDefaults in this way:
if let environment = UserDefaultsManager.shared.environment {
//you can do some magic with this variable
} else {
print("environment data not saved in UserDefaults")
}
Using Codable protocol
Extent Environment enum that conforms to Codable protocol to encode and decode values as Data.
enum Environment: String, Codable {
case Production
case Staging
case Dev
}
A wrapper for UserDefaults:
struct UserDefaultsManager {
static var userDefaults: UserDefaults = .standard
static func set<T>(_ value: T, forKey: String) where T: Encodable {
if let encoded = try? JSONEncoder().encode(value) {
userDefaults.set(encoded, forKey: forKey)
}
}
static func get<T>(forKey: String) -> T? where T: Decodable {
guard let data = userDefaults.value(forKey: forKey) as? Data,
let decodedData = try? JSONDecoder().decode(T.self, from: data)
else { return nil }
return decodedData
}
}
Usage
// Set
let environment: Environment = .Production
UserDefaultsManager.set(environment, forKey: "environment")
// Get
let environment: Environment? = UserDefaultsManager.get(forKey: "environment")
Here is another alternative that can be be easily used with enums based on types (like String, Int etc) that can be stored by NSUserDefaults.
#propertyWrapper
struct StoredProperty<T: RawRepresentable> {
let key: String
let defaultValue: T
init(_ key: String, defaultValue: T) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: T {
get {
guard let rawValue = UserDefaults.standard.object(forKey: key) as? T.RawValue, let value = T(rawValue: rawValue) else {
return defaultValue
}
return value
}
set {
UserDefaults.standard.set(newValue.rawValue, forKey: key)
}
}
}
Example usage:
enum Environment: String {
case Production
case Staging
case Dev
}
#StoredProperty("Environment", defaultValue: .Dev)
var storedProperty: Environment
Swift 5.1 You can create a generic property wrapper, using Codable to transform values in and out the UserDefaults
extension UserDefaults {
// let value: Value already set somewhere
// UserDefaults.standard.set(newValue, forKey: "foo")
//
func set<T>(_ value: T, forKey: String) where T: Encodable {
if let encoded = try? JSONEncoder().encode(value) {
setValue(encoded, forKey: forKey)
}
}
// let value: Value? = UserDefaults.standard.get(forKey: "foo")
//
func get<T>(forKey: String) -> T? where T: Decodable {
guard let data = value(forKey: forKey) as? Data,
let decodedData = try? JSONDecoder().decode(T.self, from: data)
else { return nil }
return decodedData
}
}
#propertyWrapper
public struct UserDefaultsBacked<Value>: Equatable where Value: Equatable, Value: Codable {
let key: String
let defaultValue: Value
var storage: UserDefaults = .standard
public init(key: String, defaultValue: Value) {
self.key = key
self.defaultValue = defaultValue
}
// if the value is nil return defaultValue
// if the value empty return defaultValue
// otherwise return the value
//
public var wrappedValue: Value {
get {
let value: Value? = storage.get(forKey: key)
if let stringValue = value as? String, stringValue.isEmpty {
// for string values we want to equate nil with empty string as well
return defaultValue
}
return value ?? defaultValue
}
set {
storage.set(newValue, forKey: key)
storage.synchronize()
}
}
}
// use it
struct AppState: Equatable {
enum TabItem: String, Codable {
case home
case book
case trips
case account
}
var isAppReady = false
#UserDefaultsBacked(key: "selectedTab", defaultValue: TabItem.home)
var selectedTab
// default value will be TabItem.home
#UserDefaultsBacked(key: "selectedIndex", defaultValue: 33)
var selectedIndex
// default value will be 33
}
I am using like this type staging. Can you please try this it will help you.
enum Environment: String {
case Production = "Production URL"
case Testing = "Testing URl"
case Development = "Development URL"
}
//your button actions
// MARK: set Development api
#IBAction func didTapDevelopmentAction(_ sender: Any) {
let env = Environment.Development.rawValue
print(env)
UserDefaults.standard.set(env, forKey:Key.UserDefaults.stagingURL)
}
// MARK: set Production api
#IBAction func didTapProductionAction(_ sender: Any) {
let env = Environment.Production.rawValue
print(env)
UserDefaults.standard.set(env, forKey:Key.UserDefaults.stagingURL)
}
// MARK: set Testing api
#IBAction func didTapTestingAction(_ sender: Any) {
let env = Environment.Testing.rawValue
print(env)
UserDefaults.standard.set(env, forKey:Key.UserDefaults.stagingURL)
}
//Based on selection act
print("\(UserDefaults.standard.object(forKey: "stagingURL") ?? "")")
Swift 5.1
You can create property wrapper for this
#propertyWrapper final class UserDefaultsLanguageValue {
var defaultValue: LanguageType
var key: UserDefaultsKey
init(key: UserDefaultsKey, defaultValue: LanguageType) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: LanguageType {
get { LanguageType(rawValue: UserDefaults.standard.object(forKey: key.rawValue) as? String ?? defaultValue.rawValue) ?? .en }
set { UserDefaults.standard.set(newValue.rawValue, forKey: key.rawValue) }
}
}
enum UserDefaultsKey: String {
case language
}
enum LanguageType: String {
case en
case ar
}
And use it just like that
#UserDefaultsLanguageValue(key: .language, defaultValue: LanguageType.en) var language