Sreeni
Sreeni

Reputation: 215

SwiftUI: How to persist @Published variable using UserDefaults?

I want a @Published variable to be persisted, so that it's the same every time when I relaunch my app.

I want to use both the @UserDefault and @Published property wrappers on one variable. For example I need a '@PublishedUserDefault var isLogedIn'.

I have the following propertyWrapper

import Foundation

@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)
        }
    }
}

This is my Settings class

import SwiftUI
import Combine

 class Settings: ObservableObject {

   @Published var isLogedIn : Bool = false

 func doLogin(params:[String:String]) {

        Webservice().login(params: params) { response in

            if let myresponse = response {                    
                    self.login = myresponse.login
                    }
               }
         }

}

My View class

struct HomeView : View {
    @EnvironmentObject var settings: Settings
    var body: some View {
        VStack {
            if settings.isLogedIn {
            Text("Loged in")
            } else{
            Text("Not Loged in")
            }
        }
    }
}

Is there a way to make a single property wrapper that covers both the persisting and the publishing?

Upvotes: 19

Views: 7466

Answers (5)

Victor Kushnerov
Victor Kushnerov

Reputation: 3974

import SwiftUI
import Combine

fileprivate var cancellables = [String : AnyCancellable] ()

public extension Published {
    init(wrappedValue defaultValue: Value, key: String) {
        let value = UserDefaults.standard.object(forKey: key) as? Value ?? defaultValue
        self.init(initialValue: value)
        cancellables[key] = projectedValue.sink { val in
            UserDefaults.standard.set(val, forKey: key)
        }
    }
}

class Settings: ObservableObject {
    @Published(key: "isLogedIn") var isLogedIn = false
    ...
}

Sample: https://youtu.be/TXdAg_YvBNE

Version for all Codable types check out here

Upvotes: 38

Pim
Pim

Reputation: 2128

To persist your data you could use the @AppStorage property wrapper. However, without using @Published your ObservableObject will no longer put out the news about the changed data. To fix this, simply call objectWillChange.send() from the property's willSet observer.

import SwiftUI

class Settings: ObservableObject {
    @AppStorage("Example") var example: Bool = false {
        willSet {
            // Call objectWillChange manually since @AppStorage is not published
            objectWillChange.send()
        }
    }
}

Upvotes: 10

malhal
malhal

Reputation: 30627

struct HomeView : View {
    @StateObject var auth = Auth()
    @AppStorage("username") var username: String = "Anonymous"

    var body: some View {
        VStack {
            if username != "Anonymous" {
                Text("Logged in")
            } else{
                Text("Not Logged in")
            }
        }
        .onAppear(){
             auth.login()
        }
    }
}


import SwiftUI
import Combine

class Auth: ObservableObject {

 func login(params:[String:String]) {

        Webservice().login(params: params) { response in

            if let myresponse = response {          
                    UserDefaults.standard.set(myresponse.login, forKey: "username")`
                    }
               }
         }

}

Upvotes: 0

Mycroft Canner
Mycroft Canner

Reputation: 1997

It should be possible to compose a new property wrapper:

Composition was left out of the first revision of this proposal, because one can manually compose property wrapper types. For example, the composition @A @B could be implemented as an AB wrapper:

@propertyWrapper
struct AB<Value> {
  private var storage: A<B<Value>>

  var wrappedValue: Value {
    get { storage.wrappedValue.wrappedValue }
    set { storage.wrappedValue.wrappedValue = newValue }
  }
}

The main benefit of this approach is its predictability: the author of AB decides how to best achieve the composition of A and B, names it appropriately, and provides the right API and documentation of its semantics. On the other hand, having to manually write out each of the compositions is a lot of boilerplate, particularly for a feature whose main selling point is the elimination of boilerplate. It is also unfortunate to have to invent names for each composition---when I try the compose A and B via @A @B, how do I know to go look for the manually-composed property wrapper type AB? Or maybe that should be BA?

Ref: Property WrappersProposal: SE-0258

Upvotes: 1

Fabian
Fabian

Reputation: 5348

You currently can't wrap @UserDefault around @Published since that is not currently allowed.

The way to implement @PublishedUserDefault is to pass an objectWillChange into the wrapper and call it before setting the variable.

Upvotes: 0

Related Questions