0

I’ve created a small sample project in Swift Playgrounds to debug an issue I’ve encountered. This sample project contains the a primary ContentView with a single Text field and a button that opens Settings in a modal view.

When I open Settings and change the a setting via a picker, I would like to see the corresponding Text label change in my ContentView. In the current project, I’m using the @ObservableObject Type Alias to track the change, and I see that the setting changes correctly, but the view is not updated. If I restart the preview in Playgrounds, the view is updated with the changed setting. I would expect the Text label to change in real-time.

The code is as follows:

ContentView.swift

import SwiftUI

struct ContentView: View {
    
    @ObservedObject var userSettings = UserSettings()
    
    @State var isModal: Bool = false
    
    var body: some View {
        
        VStack {
            Text("Setting: " + userSettings.pickerSetting)
                .fontWeight(.semibold)
                .font(.title)
            Button(action: {
                    self.isModal = true
                }) {
                    Image(systemName: "gear")
                        .font(.title)
                }
                .padding()
                .foregroundColor(.white)
                .background(Color.gray)
                .cornerRadius(40)
                .sheet(isPresented: $isModal, content: {
                    UserSettingsView()
                })
                .environmentObject(userSettings)
        }
    }
}

UserSettings.swift

import Foundation

class UserSettings: ObservableObject {
    
    @Published var pickerSetting: String {
        didSet {
            UserDefaults.standard.set(pickerSetting, forKey: "pickerSetting")
        }
    }
    
    public var pickerSettings = ["Setting 1", "Setting 2", "Setting 3"]
    
    init() {
        self.pickerSetting = UserDefaults.standard.object(forKey: "pickerSetting") as? String ?? "Setting 1"
    }
}

UserSettingsView.swift

import SwiftUI

struct UserSettingsView: View {
    @ObservedObject var userSettings = UserSettings()
    
    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("")) {
                    Picker(selection: $userSettings.pickerSetting, label: Text("Picker Setting")) {
                        ForEach(userSettings.pickerSettings, id: \.self) { setting in
                            Text(setting)
                        }
                    }
                }
            }
            .navigationBarTitle("Settings")
        }
    }
}
Ricky
  • 639
  • 2
  • 6
  • 12
  • 2
    Consider using AppStorage, also look at https://stackoverflow.com/a/62716736/12299030. – Asperi Apr 13 '22 at 17:59
  • Thanks! While this answer didn’t precisely answer the question about why the configuration above isn’t working, it’s by far the better solution. I switched to the AppStorage property wrapper and everything now works + I’m using less code! – Ricky Apr 13 '22 at 19:10

1 Answers1

0

This happening because you have created two instances of UserSettings. One each in ContentView and UserSettingsView.

If you want to keep using .environmentObject(userSettings) the you need to use @EnvironmentObject var userSettings: UserSettings in UserSettingsView.

Otherwise you can drop the .environmentObject and use an @ObservedObject in UserSettingsView.

David Reich
  • 709
  • 6
  • 14