11

I am new to SwiftUI and currently building my first app. However, I am having an issue.

I am programming a multi-view app, in which I would like to use kind of global variables to be able to access and edit them from all my views. For example, I ask the user his "sex", "weight" and "license" at app launch. But, I would also like him to be able to change his details in the "settings" category (being a different view). At the same time, I would like to use the same variables in both the views and make them update in both views. Like basic global variables. Is there a way to do so ?

I have watched an outdated video about @State, @ObservableObject, and @EnvironmentObject. Spoiler alert: I didn't understand. I hope you'll be able to help me. If you need any detail, feel free :) Sam

Sam
  • 113
  • 1
  • 4
  • 3
    Warning: global variables are often looked at as an anti-pattern for a number of reasons, not the least of which being that they can lead to code that is challenging to test. `@State` only exists for a `View` and definitely can't be used globally. You probably want an `@EnvironmentObject` that you can pass through your view hierarchy. – jnpdx Aug 21 '21 at 19:41
  • Try the [Apple SwiftUI Tutorials](https://developer.apple.com/tutorials/swiftui). There are many ways to solve this issue but in short. No you can't have a global `@State`. To make an `ObservableObject` available throughout the app `@EnvironmentObject` is what apple recommends, the type of items you describe should be "stored" in a database/file and/or encrypted/protected since they are [considered sensitive and are required to be secured](https://developer.apple.com/app-store/review/guidelines/#legal) (Look up Guideline 5.1.1). So, it is impossible to give you a solution, maybe CoreData? – lorem ipsum Aug 21 '21 at 19:51
  • 1 - I see. Can't I then create a global class "User", create a User() from the class, and then access its details and/or edit them through all my views ? 2 - The data stored don't need to be secured to be honest. They are very secondary and not important, just needed for some calculations (plus it's a client-side app). I will look at the method you wrote me about, thank you ! :D – Sam Aug 21 '21 at 19:52

1 Answers1

19

What I would recommend: an ObservableObject called UserSettings. You can then inject this into the whole app from in your app scene or where the @main is with .environmentObject(UserSettings(...)).

For views which need access to the instance of UserSettings, you would do the following:

@EnvironmentObject private var userSettings: UserSettings

Example

Environment object:

class UserSettings: ObservableObject {
    enum Sex: String {
        case male
        case female
        case other
    }

    @Published var sex: Sex
    @Published var weight: Double
    @Published var license: Bool

    init(sex: Sex, weight: Double, license: Bool) {
        self.sex = sex
        self.weight = weight
        self.license = license
    }
}
@main
struct WhateverThisIsApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(UserSettings(sex: .male, weight: 100, license: true))
        }
    }
}

Views:

struct ContentView: View {
    @EnvironmentObject private var userSettings: UserSettings

    var body: some View {
        VStack {
            Text("Current settings:")

            Text("Sex: \(userSettings.sex.rawValue)")

            Text("Weight: \(userSettings.weight)")

            Text("License: \(userSettings.license ? "yes" : "no")")

            SomeChildView()
        }
    }
}
struct SomeChildView: View {
    @EnvironmentObject private var userSettings: UserSettings

    var body: some View {
        Picker("Sex", selection: $userSettings.sex) {
            Text("Male").tag(UserSettings.Sex.male)
            Text("Female").tag(UserSettings.Sex.female)
            Text("Other").tag(UserSettings.Sex.other)
        }
        .pickerStyle(.segmented)
    }
}

Result:

Result


For a full demo of using environment objects, see my answer here along with the associated repo.

George
  • 25,988
  • 10
  • 79
  • 133
  • It seems perfect! But what if I want to change sex, weight, or license value manually inside a view ? For example something like "$userSettings.sex = 'Male' " – Sam Aug 21 '21 at 20:07
  • @Sam Almost: use `userSettings.sex = .male`. I used an enum here (hence the `.` in front). Also you don't want the `$` in front as otherwise you are trying to directly set a `Binding`. – George Aug 21 '21 at 20:08
  • I see. And if I want to edit weight value ? – Sam Aug 21 '21 at 20:11
  • 1
    @Sam Same sort of thing, `userSettings.weight = 110`. – George Aug 21 '21 at 20:12
  • I am very sorry, but I can't find a way to apply "userSettings.weight = 110" – Sam Aug 21 '21 at 20:20
  • @Sam Works for me - what isn't working? Make sure the `@EnvironmentObject` part is in the view you are trying to do this with so you actually have `userSettings`. – George Aug 21 '21 at 20:23
  • I am using your code example to train on. Let's say I simply add "userSettings.weight = 200" in "SomeChieldView", it causes errors. – Sam Aug 21 '21 at 20:27
  • @Sam ... and what are those errors? You can't just set it in the view body. Set it in a button's action or `onAppear`, for example. – George Aug 21 '21 at 20:28
  • " Type '()' cannot conform to 'View'; only struct/enum/class types can conform to protocols " – Sam Aug 21 '21 at 20:28
  • 1
    @Sam Edited previous comment, see that. Looks like you are putting it in the view body (which doesn't really make sense anyway, because that would cause an infinite loop) – George Aug 21 '21 at 20:29
  • My bad, it's working. My XCode glitched, had to relaunch. I caused an infinite loop and made XCode glitch. I guess it's working now. Thank you ! – Sam Aug 21 '21 at 20:31
  • @Sam If everything is working now, please upvote & accept the answer to help future readers! – George Aug 21 '21 at 20:36
  • 1
    I accepted the answer, can't upvote since I am new to stack overflow, already tried before you asked ^^ Thank you for your precious help ! – Sam Aug 21 '21 at 20:37