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

Userdefault not updating view when changed

Forums > SwiftUI

In my project, I have a settings screen that lets a user choose a different color them that is applied to different parts of the screen. The theme chosen is saved in UserDefaults. The theme changes all elements of my app on all screens, except the main ContentView.

My ContenView is using a custom NavigationBar and custom TabBar so that I can change the colors to the users liking. When you exit the app and restart it, the theme is updated. But in the screenshot below, it doesn't update theme when it is changed in the settings screen.

The NavigationBar and TabBar are using .background(FillColor(myTheme: userSettings.userTheme)) The FillColor is just a LinearGradient that is selected from the settings screen.

Is there a way to force a refresh of the UI to update the theme change without having to exit the app?

My ContentView file:

 import SwiftUI
 import MessageUI
 import MapKit

 struct HomeView: View {

     @State var title = "Add Waypoint"

     @StateObject var locationManager = LocationManager()
    @State private var userSettings = UserSettings()
     @State private var selectedTab = 0

     var body: some View {
        VStack(spacing: 0) {
            HStack(spacing: 20) {
                 Text(title)
                     .fontWeight(.bold)
                     .foregroundColor(.black)
                     .font(.title)

                 Spacer()

                 Button {

                 } label: {
                     Image(systemName: "magnifyingglass")
                         .resizable()
                         .frame(width: 20, height: 20)
                         .foregroundColor(.black)
                 }

                 Button {

                 } label: {
                     Image(systemName: "bell.fill")
                         .resizable()
                         .frame(width: 20, height: 22)
                         .foregroundColor(.black)
                 }
             }
             .padding()
             .padding(.top, 30)
             .background(FillColor(myTheme: userSettings.userTheme))

             GeometryReader {_ in

                 VStack {

                     // Change your views based on index...
                    switch selectedTab {
                     case 0:
                         VStack {
                             Spacer()
                             EntryScreen()
                             Spacer()
                         }
                     case 1:
                         WaypointListView()
                     case 2:
                         WaypointMapScreen(myCoordinate: MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: locationManager.lastLocation?.coordinate.latitude ?? 0, longitude: locationManager.lastLocation?.coordinate.longitude ?? 0), span: MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02)), isAnimating: false, altitude: locationManager.lastLocation?.altitude ?? 0)
                     case 3:
                         SettingsScreen()
                     default:
                         EmptyView()
                     }

                 }
             }
             .background(Color.black.opacity(0.10))

             TabBarView(index: $selectedTab, title: $title)

         }
         .edgesIgnoringSafeArea(.top)
     }
 }

My User Settings Class:

class UserSettings: ObservableObject {
    @Published var showUserLocation: Bool {
        didSet {
            UserDefaults.standard.set(showUserLocation, forKey: "userLocation")
        }
    }

    @Published var userTheme: Int {
        didSet {
            UserDefaults.standard.set(userTheme, forKey: "userTheme")
        }
    }

    init() {
        self.showUserLocation = UserDefaults.standard.object(forKey: "userLocation") as? Bool ?? false
        self.userTheme = UserDefaults.standard.object(forKey: "userTheme") as? Int ?? 0
    }

}

ScreenShot

3      

We'd need to see some code to assist you. Particularly, your ContentView (since that's the View that isn't updating) and the class for userSettings.

And a hint for the future: If you change the dl=0 at the end of that Dropbox link to raw=1 and use the correct tag, the image will be embedded in your post.

(Might be a good idea to shrink the image down a bit too if you're going to be embedding it.)

4      

Thanks for the tip on the dropbox image. I was trying to figure that out before I pasted the code, and was spending too much time on that. I have now included my code. If there is anything else, let me know. Thanks

3      

You said it was ContentView that wasn't seeing the changes but you posted HomeView. I'm going to assume you really meant HomeView in your initial post.

@State private var userSettings = UserSettings()

Don't use @State with classes; it requires a value type, like a struct or an enum, but classes are reference types. SwiftUI can't monitor the changes in a reference type using @State.

For classes, you should be using @StateObject, @ObservedObject, or @EnvironmentObject, depending on how the object is being created and used.

3      

Yes I did mean HomeView I just use that when making changes, and when it is working, I copy it over to ContentView. Anyway, I changed @State private var userSettings = UserSettings() to @StateObject private var userSettings = UserSettings() It was supposed to be @StateObject, but I missed that. I made the change and it didn't make any difference. That part of the UI is static and not changing when switching to different screen tabs since it is on every tab, but everything else is updating since the views are changing.

3      

Are you using @StateObject or @ObservedObject in your other Views? How is the UserSettings class being observed on your other screens?

It sounds like you should be instantiating it using @StateObjecton your main screen (ContentView or HomeView or whatever) or even in your app struct and then injecting it into the environment using .environmentObject so that you can read it out in your other Views using @EnvironmentObject. That makes the most sense for a class that should be observed by all the Views in your app.

3      

I finally got it working. I changed
@StateObject private var userSettings = UserSettings()
to
@EnvironmentObject var userSettings: UserSettings

in my SettingsView() and placed .environmentObject(userSettings) at the bottom of the VStacK{} in HomeView()

I made similar changes for the TabView(), but it didn't work there. So I moved the .background(FillColor(myTheme: userSettings.userTheme)) modifier that was applied to the VStack{} in the TabView to a modifier on the TabView that is inside the HomeView() and it now works perfectly.

Now I just need to come up with better looking Gradidents to use.

Thanks

3      

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

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

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

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.