Search code examples
observablepublishswiftuicombine

How to set multiple EnvironmentObjects which are same type


I found this question SwiftUI: Putting multiple BindableObjects into Envionment

the answer said environmentObject(ObservableObject) returns modified view, therefore I can make call chain for multiple environmentObject.

like

let rootView = ContentView()
     .environmentObject(firstBindable)
     .environmentObject(secondBindable)

and I wonder what is result if firstBindable and secondBindable are same type. how .environmentObject() knows what is exect value which is a programmer intended to set between firstBindable and secondBindable.

so I tested this

  1. I made an ObservableObject class
final class TempStr: ObservableObject {
    @Published var tmpStr = "temp"
    
    init(initStr: String) {
        tmpStr = initStr
    }
}
  1. made call chain of environmentObject from sceneDelegate
window.rootViewController
  = UIHostingController(rootView:
      TestView()
        .environmentObject(TempStr(initStr: "1st")) 
        .environmentObject(TempStr(initStr: "2nd"))
  1. and used values from View
struct TestView: View {
  @EnvironmentObject var tmp1: TempStr
  @EnvironmentObject var tmp2: TempStr

   var body: some View {
      Text(tmp1.tmpStr + " " + tmp2.tmpStr)
   }
}
  1. result was '1st 1st'

And if my code calls one .environmentObject() like

TestView().environmentObject(TempStr(initStr: "1st")) 

both tmp1 and tmp2 from TestView have same value TempStr(initStr: "1st"). it looks like .environmentObject() call sets all values of same type.


Actually, I knew that it couldn't work but I just tried it for using this question.

I wonder what is correct way of achieving my goal.

Thanks


Solution

  • This works:

    SceneDelegate:

    class MyModel: ObservableObject {
        @Published var tmpStr1:TempStr!
        @Published var tmpStr2:TempStr!
    
        init(initStr1: String, initStr2:String) {
            tmpStr1 = TempStr(initStr: initStr1)
            tmpStr2 = TempStr(initStr: initStr2)
        }
    }
    class TempStr: ObservableObject {
        @Published var tmpStr:String!
    
        init(initStr: String) {
            tmpStr = initStr
        }
    }
    

    ContentView:

    struct ContentView: View {
        @EnvironmentObject var myModel: MyModel
        var body: some View {
            Text(myModel.tmpStr1.tmpStr + " " + myModel.tmpStr2.tmpStr)
        }
    }
    

    Granted, this may not be what you are looking for, but that begs the question - why do want want two instances of a class without wanting each in your model? You can easily have two separate classes as pat of your model (and as ObservableObjects) and if you want two separate instances of your model per SceneDelegate? There it is.

    But if you want two instances of your model/class/whatever, it feels like a candidate for refactoring.