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

Is this really a bad idea?

Forums > SwiftUI

I am slowly learning SwiftUI - things got "complicated" recently, and in an attempt to understand what is happening I put some (a lot) tracing prints everywhere. One thing this showed me is that many views were being updated often when they didn't need to be.

The problem appears to be with how I am using @EnvironmentObject.

I created an object that holds some generally used "flags" and injected it into the Environment as an EnvironmentObject:

class ProjectSettings: ObservableObject {

    @Published var projectType: String = "Series"

    @Published var selectedProject: Project? {
        didSet {
            if let value = selectedProject {
                print("ProjectSettings.selectedProject changed [\(value)]")
            }
        }
    }
    @Published var selectedSidebar: Sidebar? {
        didSet {
            if let value = selectedSidebar {
                print("ProjectSIDEBAR.selected changed [\(value)]")
            }
        }
    }

    @Published var selectedView: ViewType = .Select {
        didSet {
            print("ProjectSettings.selectedView changed [\(selectedView)]")
        }
    }

    @Published var sidebarShown: Bool = true {
        didSet {
            print("ProjectSettings.sidebarShown changed [\(sidebarShown)]")
        }
    }
    @Published var inspectorBarShown: Bool = true{
        didSet {
            print("ProjectSettings.sidebarShown changed [\(inspectorBarShown)]")
        }
    }

    @Published var showAddSidebarRow: Bool = false{
        didSet {
            print("ProjectSettings.sidebarShown changed [\(showAddSidebarRow)]")
        }
    }

    init() {}

    init(project: Project) {
        selectedProject = project
    }

This way I could just put a @EnvironmentObject at the start of any View that might need one of them and have access to all of them. I assumed SwiftUI would only pay attention to the ones referenced in the individual View, even though all are published.

   @EnvironmentObject var settings: ProjectSettings

I think this not a good idea. It results in many Views being updated when they don't need to be, depending on the view hierarchy.

I have changed this to have individual observable vars and then views only watch the ones they are interested in:

this var in the composite class

    @Published var selectedSidebar: Sidebar? {
        didSet {
            if let value = selectedSidebar {
                print("ProjectSIDEBAR.selected changed [\(value)]")
            }
        }
    }

was replaced by an individual class:

class SIDEBAR: ObservableObject {
    @Published var selected: Sidebar?
        didSet {
            if let value = selected {
                print("SIDEBAR.selected changed [\(value)]")
            }
        }

}

I needed to make this a class (type), so I could share it using the EnvironmentObject mechanism, since there can be only one instance of each type in the EnvironmentObject. This seems to solve my issue of views being refreshed more often than they need to be.

So, my question is, for those here that understand this better than I do, I am helping or hurting my self - is this a safer way to explicitly control the refreshing of views, or should I trust the "Force" (SwiftUI) to figure it all out and put it all back into a single big settings class?

2      

You could use @AppStorage for your settings.

2      

How would that be different than using individual EnvironmentObjects for each state flag/var?

2      

You don't need a separate object for it. You can read the value you're interested in directly in the view and you don't have to pass a object around. Depending on your use case it may be an option.

2      

For app settings UserDefaults is certainly a great option. You just place your checks on the views where applicable.

By injecting these settings into the Environment, you kinda turned them into Global Variables, which shows you why they are best avoided.

Now, if I understand your intention correctly, since you will make these checks or flags frequently, and aiming to "encapsulate" them in one place instead of repeting code all over, you went with the class.

An alternative would be to have ProjectSettings as a struct which contains all the variables. You can then create an instance for each view and that instance would be the @State private var for that specific view. So changing one won't change the others.

2      

@MarcusKay

Thank you. But, the point is that they are "global variables". As an C++ programmer for a long time, the idea of global variables is an anapthema to me. BUT... it seems SwiftUI needs/wants/uses Globals to share state.

The point of these variables is to maintain application wide state - the state being various compbinations of views and other things in various states - being shown, not shown, etc. Think of it as a part of the View Model - which is where I have them located in my folder structure in the app.

As far as the UserDefaults, I thought that @EnvironmentObject is basically @UserDefaults with a few extras thrown in? To quote our fearless leader:

For data that should be shared with all views in your entire app, SwiftUI gives us @EnvironmentObject. This lets us share model data anywhere it’s needed, while also ensuring that our views automatically stay updated when that data changes.

When one of them says, "this view is now shown" the app needs to rebuild the View tree so that one is shown - like if a local @State var viewIsShown: Bool would be used, but that would then require passing the var around to all the views that might change it and binding it as an incoming var in those views.

By, Using the @EnvironmentObject I get them automatically shared to all sub views, and I get them published and observed without having to do anything myself. The issue I am experiencing is that the publishing and observing seems to be associated with the class and not the individual vars within that Class. But, I certainly could be wrong.

Seemed like a good idea at the time - but, they are still basically "globals" which gives me serious indigestion. However, it seems that SwiftUI uses globals - like it or not. They just call them something else and look the other way.

Sort of like "Binding" appears (from what I can tell) is just another name for what C++ calls a reference - Bindings even use the same syntax as pass-by-reference - ie. $someVar or $someFunc. And it seems to work the same.

2      

I am intimately familiar with that indigestion 😉. A better way to see what is happening though would be to use instruments. They are more technical but certainly more accurate. You might want to give them a shot.

This link helps. Some stuff might have changed (it's a 1 yr old link), but it's a starting point. Helped give me the foundation to start experiment with them.

https://www.avanderlee.com/debugging/xcode-instruments-time-profiler/

PS: I did forget your last paragraph when answering. Hence the rather naive recommendation. Sorry about that.

PPS: I do recall reading about lazy loading of views but didn't really get into that, as I wanted to wrap my head around SwiftUI fundamentals first. Might also be worth a check.

I would trust the "Force" 👍

3      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

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.