We inherited some code that has a variable that begins in the "SwiftUI world", so to speak, and is copied over to a global variable in the "Swift world" for use in non-SwiftUI classes (POSOs? Plain Ol' Swift Objects?).
Here's a contrived example showing the basic gist of it. Note how there's an AppViewModel
that maintains the state, and an .onChange
that copies the value to a global var, which is used in the plain class DoNetworkStuff.
I would like to weed out the redundant global var, but I kind of see why it was done this way--how DO you bridge between the 2 worlds? I don't think you can add a ref to AppViewModel
inside DoNetworkStuff.
I was thinking you could add a function to the AppViewModel
that returns devid,
and stash a ref to the function in a var for use whenever devid
is needed., so at least you're eliminating the var value being stored in 2 places, but that might be confusing a year from now. I'm trying to think of a way to rewrite this without ripping out too much code (it could be that maybe it's better to leave it).
var gblDevID = "" //global var
class AppViewModel: ObservableObject {
@Published var devid = ""
...
}
struct ContentView: View {
@StateObject var appViewModel = AppViewModel()
var body: some View {
TextField("Enter device id", text: $appViewModel.devid)
.onChange(of: appViewModel.devid) { newVal in
gblDevID = newVal
}
...
}
}
class DoNetworkStuff {
func networkingTask() {
doSomeTask(gblDevID)
}
}
There's quite a lot to unpack here.
You have one of 2 possibilities in your app:
-
You have only once instance of AppViewModel in your app's lifecycle. This means you must prevent the creation of more than one window (aka scene) using that ContentView.
-
You allow the creation of multiple instances of your window, which means you'll have a different ContentView in each possible window, and so possibly more than one AppViewModel at a time. In this case, your code is already broken, since you have only one global, and you can't tell which instance of AppViewModel it's related to.
If you want to do #1, assuming you've found a way to prevent creation of more than one instance, then you can solve your problem in a couple of ways:
Possible solution: Create your AppViewModel outside of ContentView, and pass it in as a parameter or via an .environmentObject modifier on ContentView. You can store your AppViewModel reference in a place that your non-view code can find it.
Possible solution: Use a singleton model, where (for example) AppViewModel has a shared
static property that returns the one and only instance. Your non-view code can get this reference as AppViewModel.shared
.
For a slightly different perspective, consider that you've perhaps "under-designed" your solution here, because the fact that you need a "devID" for your networking code means that it's something that should be in an app data model, not in a view model. You may need both, too — for example, while a user is editing some input in a text field, you might need a temporary state variable in a view model to bind to in SwiftUI, but you don't want to update the corresponding data model value until the user has finished editing the value.
Sometimes, when you feel discomfort with the code you've already written, it's necessary to go back and think about the design more than the current implementation. :)