UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SwiftUI 2023 : Is @AppStorage the elephant in the room ? What's the solution now if you go the MVVM way !?

Forums > SwiftUI

So SwiftUI in 2023 makes it easier than ever to build your app using the MVVM architecture, thanks to @Observation and @Environment. But at the same time, it did not address how to save the app state, which has always been the elephant in the room with SwiftUI. Previously, I could use @AppStorage in @ObservableObject classes, now we can't, but it never really worked well though. It would fail intermittently.

We can use @AppStorage in our Views and it works, but IMO we need to change these variables in the ViewModels, otherwise it breaks the concept of MVVM. Also, I may want to access some user settings it in the ViewModel because it might change the logic of some functions.

How would you guys do to save the app state in an iOS App, in order to have the same settings back even after killing and relaunching the app ? So far I made this, and it works, but it's quite a lot of boiler plate code and I don't even know if making a macro is possible :

import SwiftUI
import Observation

enum AppStorageKeys: String {
    case forceDarkMode
}

@Observable class ContentViewModel {
    var forceDarkMode: Bool = UserDefaults.standard.bool(forKey: AppStorageKeys.forceDarkMode.rawValue)
}

struct ContentView: View {
    var viewModel = ContentViewModel()

    var body: some View {
        VStack {
            Text("Force Dark Mode : " + viewModel.forceDarkMode.description)

            Button { viewModel.forceDarkMode.toggle() } label: {
                Text("Tap to toggle UI style")
            }
        }
        .onChange(of: viewModel.forceDarkMode) { _, newValue in
            UserDefaults.standard.set(newValue, forKey: AppStorageKeys.forceDarkMode.rawValue)
        }
    }
}

2      

I also noticed that this line works on @ObservableObject classes but not on @Observable : objectWillChange.send()

Is this normal behavior ?

2      

@Observable doesn't use Combine under the hood the way @ObservableObject does. That's why objectWillChange.send() doesn't work.

2      

How come doesn't this work ? I believe we could do a macro with it, but the code doesn't even compile.

Error : Cannot find 'newValue' in scope

@Observable class ContentViewModel {
    var forceDarkMode: Bool = UserDefaults.standard.bool(forKey: AppStorageKeys.forceDarkMode.rawValue) {
        didSet {
            UserDefaults.standard.set(newValue, forKey: AppStorageKeys.forceDarkMode.rawValue)
        }
    }
}

2      

Apparently, the only way to get this work is this, and since the same class encapsulates everything, it would be possible to make a macro :

@Observable
class ContentViewModel {
    var forceDarkMode: Bool {
        get {
            access(keyPath: \.forceDarkMode)
            return UserDefaults.standard.bool(forKey: AppStorageKeys.forceDarkMode.rawValue)
        }

        set {
            withMutation(keyPath: \.forceDarkMode) {
                UserDefaults.standard.setValue(newValue, forKey: AppStorageKeys.forceDarkMode.rawValue)
            }
        }
    }
}

2      

Thanks for the info, I appreciate it.

2      

TAKE YOUR SKILLS TO THE NEXT LEVEL If you like Hacking with Swift, you'll love Hacking with Swift+ – it's my premium service where you can learn advanced Swift and SwiftUI, functional programming, algorithms, and more. Plus it comes with stacks of benefits, including monthly live streams, downloadable projects, a 20% discount on all books, and free gifts!

Find out more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.