Search code examples
local-storageobservableswiftuiwrapperuserdefaults

UserDefaults not being saved and update after first time in SwiftUI


I created a simple system to navigate on-screen according to the UserDefaults value, I have a key ”rootStatus” that key I placed inside the enum. I used @PropertyWrapper for storing the value of user default.

Place UserDefaults Key inside the enum.

  enum UserDefaultKey: String {
        case rootStatus           = "rootStatus"
    }

Use Property Wrapper to Save Data in the User Default.

//MARK: UserDefault:- storage template to occupies the memory in the local Database.
@propertyWrapper
struct UserDefault<T> {

    //Key: to storing the value in the userDefault in with name of specific key.
    let key: String
    //defaultValue:- to  storing the data in  any format in the UserDefault.
    let defaultValue: T

    //Update value when use a UserDefaultStorage inside the Project.
    init(_ key: String, defaultValue: T) {
        self.key = key
        self.defaultValue = defaultValue
    }

    // To Wrapping the Data inside the Project
    var wrappedValue: T {

        get{
             // Read value from UserDefaults
            return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
        }

        set{
            // Set value to UserDefaults.
            UserDefaults.standard.set(newValue, forKey: key)
        }
    }
}

Value of enum to store in UserDefaults.

enum ScreenSwitch: String {
    case lauched
    case logIn
    case dashboard
    case withoutlogin
    case none
}

To get and set the UserDefaults value using the ObservableObject property Wrapper.

final class ScreenSwitchSettings: ObservableObject {

    let objectWillChange = PassthroughSubject<Void, Never>()

    @UserDefault(UserDefaultKey.rootStatus.rawValue, defaultValue: ScreenSwitch.lauched.rawValue)
    var screenSwitch: String {
        willSet {
            objectWillChange.send()
        }
    }
}

TO used inside the SwiftView

 struct SwitchRootView: View {

            @ObservedObject var screenSwitchSettings = ScreenSwitchSettings()

        var body: some View {

 containedView()

        }

        func containedView() -> AnyView?{

            switch screenSwitchSettings.screenSwitch {

//// Introduction is a SwiftUIView for Navigation.
            case ScreenSwitch.lauched.rawValue : return AnyView(IntroductionView()) 

// LoginView is a SwiftUIView for Navigation.
            case ScreenSwitch.logIn.rawValue: return AnyView(LoginView())

// DashboardTabView is a SwiftUIView for Navigation. 
            case ScreenSwitch.withoutlogin.rawValue: return AnyView(DashboardTabView())

// DashboardTabView is a SwiftUIView for Navigation.
            case ScreenSwitch.dashboard.rawValue: return AnyView(DashboardTabView()) 

// EmptyView is a SwiftUIView for Navigation.
            case ScreenSwitch.none.rawValue: return AnyView(EmptyView()) 
            default : return AnyView(EmptyView())
            }
        }
    }

// when application launch first time so i set user default value in initially.

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    @ObservedObject var screenSwitchSettings = ScreenSwitchSettings()

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

        let contentView = SwitchRootView()
        screenSwitchSettings.screenSwitch = ScreenSwitch.lauched.rawValue
        // Use a UIHostingController as window root view controller.
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: contentView)
            self.window = window
            window.makeKeyAndVisible()
        }
    }
}

Change the value in the SwiftUIView

struct ContentView: View {

    //MARK: Properties
    @ObservedObject var screenSwitchSettings = ScreenSwitchSettings()

    var body: some View {

        NavigationView{

            Button(action: {
                // here i change Userdefault Value.
                self.screenSwitchSettings.screenSwitch = ScreenSwitch.logIn.rawValue

            }) {

                NavigationLink(destination: SwitchRootView()) {

                    Text("Get Started")
                }
            }
        }
    }
}

In the ContentView, I changed the User default Value on the Button Selection but it's not updating.

What might be the problem?


Solution

  • Try the following instead of your

    final class ScreenSwitchSettings: ObservableObject {
    
      @UserDefault(UserDefaultKey.rootStatus.rawValue, defaultValue: ScreenSwitch.lauched.rawValue)
        var screenSwitch: String {
            willSet { // or even maybe on didSet
                self.objectWillChange.send()
            }
        }
    }
    

    and... button does not work when you place navigation link in it, because latter consumes tap gesture, so test with something like following

    @State private var isActive = false
    
    ...
    NavigationView{
    
        Button(action: {
            // here i change Userdefault Value.
            self.screenSwitchSettings.screenSwitch = ScreenSwitch.logIn.rawValue
            self.isActive.toggle()
    
        }) {
                Text("Get Started")
        }
        NavigationLink(destination: SwitchRootView(), isActive: $isActive) {
            EmptyView()
        }
    }