5

Is there a better way to do this? Is there a way to access the UserDefaults in the environment?? I did the following:

struct ContentView: View {
    @AppStorage("darkMode") var darkMode = false

    var body: some View {
            SubView(darkMode: $darkMode)
        }
    }
}

struct SubView: View {
    @Binding var darkMode: Bool
    var body: some View {
        Text("Dark Mode is \(darkMode == true ? "on" : "off")")
    }
}
Super Noob
  • 750
  • 8
  • 17

1 Answers1

10

By using @AppStorage in different views you still access the same UserDefaults.standard storage (unless you explicitly specify the suiteName).

Which means you can just use the @AppStorage directly in the subview.

struct ContentView: View {
    @AppStorage("darkMode") var darkMode = DefaultSettings.darkMode

    var body: some View {
        VStack {
            Button("Toggle dark mode") {
                self.darkMode.toggle()
            }
            SubView()
        }
        .colorScheme(darkMode ? .dark : .light)
        .preferredColorScheme(darkMode ? .dark : .light)
    }
}

struct SubView: View {
    @AppStorage("darkMode") var darkMode = DefaultSettings.darkMode

    var body: some View {
        Text("Dark Mode is \(darkMode == true ? "on" : "off")")
    }
}

enum DefaultSettings {
    static let darkMode = false
}

Note: the default darkMode value is extracted (to the DefaultSettings enum) so you don't repeat false in each view.


Alternatively you can inject @AppStorage directly to the environment. See:

pawello2222
  • 46,897
  • 22
  • 145
  • 209
  • So for that example, how would I use darkMode var in the contentview? Say, if I wanted to check what the setting was so that I could make the entire subview in darkmode? or the entire app in dark mode? like, if I had a settings page that controlled dark mode, how would I use that AppStorage there and then read it in the App struct so that I could change the preferred color scheme for the entire app? – Super Noob Sep 29 '20 at 12:28
  • 1) I believe you only asked *How do you access an @AppStorage var from a subview* - so I answered this question above. 2) *how would I use darkMode var in the contentview?* - just add `@AppStorage`. 3) *change the preferred color scheme for the entire app* - this is not directly related with this question but in your root view you may pass the `@AppStorage` darkMode var to the `.environment(\.colorScheme, darkMode)` and apply it to the root view. – pawello2222 Sep 29 '20 at 12:33
  • Ok sorry let me edit the question to make it more clear. Going from your comment though: would I keep the AppStorage in the settings view as well as the root view? Because I tried that and it doesn't let you write the AppStorage var without declaring a value for the var, in the settings page (which would not link up with the var in the root view). I'm talking in terms of the single-source of truth here, we would need to establish a single source of truth for the AppStorage var, right? – Super Noob Sep 29 '20 at 12:46
  • If you access `AppStorage` from different views you still access *the same* UserDefaults.standard storage (unless you specify the suiteName). Which means you can easily use the same `AppStorage` in any view you want. – pawello2222 Sep 29 '20 at 13:28
  • Really? I try to access AppStorage with the 'darkMode' key in another view but it gives an error: Ambiguous use of 'init(_:store:)'. Ofcourse if I set the AppStorage var = false, with a Bool value then it has no problems, but I want to access the root AppStorage var that was set in the root view, so that I can read it's value.. – Super Noob Sep 29 '20 at 13:47
  • `AppStorage` is not a `State`, it's not view-specific. This is the *global* storage. What you change in one place is then saved in the UserDefaults and available across the whole app. I recommend you read more about AppStorage and UserDefaults, eg. here: [What is @AppStorage property wrapper](https://stackoverflow.com/q/62562534/8697793) – pawello2222 Sep 29 '20 at 14:14
  • I understand that, but I thought you didn’t need to use UserDefaults.standard to access it anymore, you would just use the @AppStorage property wrapper to both set and get/access the data stored. But it looks like you can only use the wrapper for initially setting it only in one root view. And then you can’t use the wrapper anymore to access it in other views? – Super Noob Sep 29 '20 at 14:19
  • `AppStorage` **is** `UserDefaults` in this sense - it's just a wrapper on top of it. By calling AppStorage you save the data to UserDefaults, there is no other storage involved. You can use `AppStorage` in as many views you want - just please *try* my solution first. – pawello2222 Sep 29 '20 at 14:35
  • Ok so in your edited answer, how do you extract the default value into another view to not repeat false in each view, and will you be able to edit it to true? – Super Noob Sep 29 '20 at 14:54
  • You can create some generic enum for your constants/defautls. Something like `enum DefaultSettings { static let darkMode = true }` – pawello2222 Sep 29 '20 at 14:56
  • So you still keep the AppStorage(“darkMode”) var darkMode = false, in the view struct, but in the struct view’s body you use that enum to change the value? And it will change it even though the AppStorage line sets it to false in two views. I’m gonna come back to you tomorrow as it’s 1am where I am, will edit my post to make myself more clear. – Super Noob Sep 29 '20 at 15:04
  • I updated my answer with an example more suited to your needs (although you didn't directly specify this in your question). Please see if that's what you're looking for. – pawello2222 Sep 29 '20 at 15:15
  • Ok thanks, didn't know you could extract it out like that, even to other views or class object properties – Super Noob Sep 30 '20 at 03:13
  • By the way I don't know why it works, but if you don't use the enum and just set both AppStorage lines in each view to false it still works. Any idea why? – Super Noob Sep 30 '20 at 06:13
  • It's just a substitution so you don't repeat the default value in all views. – pawello2222 Sep 30 '20 at 08:16
  • Yeah but if you repeat the default values in all views it works too. – Super Noob Sep 30 '20 at 09:00