0

I am going to create a SwiftUI application where I want to be able to swap between 3 modes. I am trying EnvironmentObject without success. I am able to change the view displayed locally, but from another View (in the end will be a class) I get a

fatal error: No ObservableObject of type DisplayView found. A View.environmentObject(_:) for DisplayView may be missing as an ancestor of this view.

Here is my code. The first line of the ContentView if/else fails.

enum ViewMode {
    case Connect, Loading, ModeSelection
}
class DisplayView: ObservableObject {
    @Published var displayMode: ViewMode = .Connect
}

struct ContentView: View {
    @EnvironmentObject var viewMode: DisplayView
    var body: some View {
        VStack {
            if viewMode.displayMode == .Connect {
                ConnectView()
            } else if viewMode.displayMode == .Loading {
                LoadingView()
            } else if viewMode.displayMode == .ModeSelection {
                ModeSelectView()
            } else {
                Text("Error.")
            }
            TestView() //Want this to update the var & change UI.
        }
        .environmentObject(viewMode)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .environmentObject(DisplayView())
    }
}

//FAILS
struct TestView: View {
    @EnvironmentObject var showView: DisplayView
    var body: some View {
        HStack {
            Button("-> load") {
                self.showView.displayMode = .Loading
            }
        }
    }
}

struct ConnectView: View {
    var body: some View {
        Text("Connect...")
    }
}

struct LoadingView: View {
    var body: some View {
        Text("Loading...")
    }
}

struct ModeSelectView: View {
    var body: some View {
        Text("Select Mode")
    }
}

I would like to be able to update DisplayView from anywhere and have the ContentView UI adapt accordingly. I can update from within ContentView but I want to be able update from anywhere and have my view change.

  • 1
    You need to initialize your `DisplayView` and inject it into the environment like you did in your preview. Do it where you initialize `ContentView`. This way you won´t need the line `.environmentObject(viewMode)` in `ContentView`. – burnsi Jan 10 '23 at 23:08
  • You have `.environmentObject(DisplayView())` in your preview code. Is your preview crashing? I suspect not. Where are you seeing the message `fatal error: No ObservableObject of type DisplayView found`? (Xcode debug console? Console.app? Elsewhere?) I suspect you have left the `environmentObject` modifier out of the non-preview code that uses `ContentView`. Show us that code. – rob mayoff Jan 10 '23 at 23:10

2 Answers2

0

I needed to inject BEFORE - so this fixed things up:

@main
struct fooApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(DisplayView()) //super key!
        }
    }
}
-1

I also tried a Singleton class to store some properties - and thus they are available from anywhere and can be updated anywhere - without having to declare EnvironmentObject. It's just another way that can work in different circumstances.

class PropContainerModel {
    public var foo = "Hello"
    static let shared = PropContainerModel()
    private override init(){}
}

And then somewhere else

let thisFoo = PropContainerModel.shared.foo
//
PropContainerModel.shared.foo = "There"

Update here (Singleton but changes reflect in the SwiftUI UI).

class PropContainerModel: ObservableObject
{
    @Published var foo: String = "Foo"
    static let shared = PropContainerModel()
    private init(){}
}

struct ContentView: View
{
    @ObservedObject var propertyModel = PropContainerModel.shared

    var body: some View {
        VStack {
            Text("foo = \(propertyModel.foo)")
                .padding()
            Button {
                tapped(value: "Car")
            } label: {
                Image(systemName:"car")
                    .font(.system(size: 24))
                    .foregroundColor(.black)
            }
            Spacer()
                .frame(height:20)
            Button {
                tapped(value: "Star")
            } label: {
                Image(systemName:"star")
                    .font(.system(size: 24))
                    .foregroundColor(.black)
            }
        }
    }
    func tapped(value: String)
    {
        PropContainerModel.shared.foo = value
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}