2
votes

Environment: Xcode Version 11.0 beta 4 (11M374r)

I'm unable to share the 'environment' with a second view.

I've instantiate the environment BindableObject in the SceneDelegate:

SceneDelegate.swift: enter image description here

I'm using @EnvironmentObject in both the base (ContentView) and the detail view.

The environment has already been set up in the SceneDelegate so it should be available to all views.

The ContentView does see the environment.
But DetailView blows up: enter image description here

Here's the complete code:

import Combine
import SwiftUI

struct UserInfo {
    var name: String
    var message: String
    init(name: String, msg: String) {
        self.name = name; self.message = msg
    }
}

// A BindableObject is always a class; NOT a struct.
class UserSettings: BindableObject {
    let willChange = PassthroughSubject<Void, Never>()
    var userInfo = UserInfo(name: "Ric", msg: "Mother had a feeling, I might be too appealing.") {
        didSet {
            willChange.send()
        }
    }
}

// =====================================================================================================

struct DetailView: View {
    @Binding var dismissFlag: Bool
    @EnvironmentObject var settings: UserSettings  // ...<error source>

    var body: some View {
        VStack {
            Spacer()
            Button(action: dismiss) {
                Text("Dismiss")
                    .foregroundColor(.white)
            }
            .padding()
            .background(Color.green)
            .cornerRadius(10)
            .shadow(radius: 10)

            Text("Hello")
            Spacer()
        }
    }

    private func dismiss() {
        settings.userInfo.message = "Rubber baby buggy bumpers."
        dismissFlag = false
    }
}

// ---------------------------------------------------------------------------
// Base View:

struct ContentView: View {
    @State var shown = false
    @EnvironmentObject var settings: UserSettings

    var body: some View {
        VStack {
            Spacer()
            Button(action: {
                self.settings.userInfo.name = "Troglodyte"
                self.settings.userInfo.message = "Top Secret"
                self.shown.toggle()
            }) {
                Text("Present")
                    .foregroundColor(.white)
            }.sheet(isPresented: $shown) { DetailView(dismissFlag: self.$shown) }
                .padding()
                .background(Color.red)
                .cornerRadius(10)
                .shadow(radius: 10)
            Text(self.settings.userInfo.message)
            Spacer()
        }
    }
}

// =====================================================================================================

#if DEBUG
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
#endif

What am I missing?
What am I doing wrong?


Revision per suggestion:
import SwiftUI
import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?
    var userSettings = UserSettings()

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

        // Use a UIHostingController as window root view controller
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(userSettings))
            self.window = window
            window.makeKeyAndVisible()
        }
    }
}

His the runtime-error message after modifying the SceneDelegate:

enter image description here


Here's a clue: enter image description here
1
See my edited answer to fix your second issue. Now I know why I haven't spent much time with Previews. :-) - dfd

1 Answers

0
votes

In SceneDelegate you need to declare an instance of your variable:

var userSettings = UserSettings()  // <<--- ADD THIS


func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UIHostingController(rootView: ContentView()
            .environmentObject(userSettings)  <<-- CHANGE THIS
        )
        self.window = window
        window.makeKeyAndVisible()
    }
}

That creates a global/environment instance of userSettings.

EDIT:

There's a second error happening, related to exposing your @EnvironmentObject to the preview. Per this answer by @MScottWaller, you need to create an separate instance in both SceneDelegate and PreviewProvider.

#if DEBUG
struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        ContentView().environmentObject(UserSettings())
}
}
#endif