SwiftiSwift
SwiftiSwift

Reputation: 8767

Cannot preview in this file - [App Name].app may have crashed on Xcode 11 Beta 5

The Xcode preview does not work if i add a EnviromentObject property wrapper. Everytime i add one the Canvas doesn't build and i get this error:

Cannot preview in this file - [App Name].app may have crashed

If i replace the EnviromentObject property wrapper with ObservedObject and initialize it everything works fine.

Here's my code:

class NetworkManager: ObservableObject {

}

struct ContentView : View {
    @EnvironmentObject var networkManager: NetworkManager

    var body: some View {
        Text("Canvas not working")
    }
}

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

Update:

It doesn’t load the preview as well when i am using a binding:

struct ContentView : View {
    @EnvironmentObject var networkManager: NetworkManager
    @Binding var test123: String

    var body: some View {
        Text("Canvas not working")
    }
}

#if DEBUG
struct ContentView_Previews: PreviewProvider {
    @State static var test1 = ""
    static var previews: some View {
        ContentView(test123: $test1).environmentObject(NetworkManager())
    }
}
#endif

Upvotes: 9

Views: 6739

Answers (5)

Timon
Timon

Reputation: 31

I had the same problem, and I found out what the reason was. I simply forgot to add the .environmentObject() modifier to ContentView() in the preview part.

struct Content_Previews: PreviewProvider {
    static var previews: some View {
       ContentView().environmentObject(NetworkManager())
    }
}

That was why Xcode built it, without showing a code-mistake, but crashed on preview in canvas. - Simple mistake, I know.

Upvotes: 3

Kiran Jasvanee
Kiran Jasvanee

Reputation: 6564

I do have a workaround for this, but yes that's true that bug is in XCode.
To fix this. you must set up your SceneDelegate first.

if let windowScene = scene as? UIWindowScene {
    let window = UIWindow(windowScene: windowScene)
    window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(NetworkManager()))
    self.window = window
    window.makeKeyAndVisible()
}. 

You must set up your preview too. As seems you have already done that.

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

At last, just declare one @State variable inside your ContentView, of the same type you @Published your @EnvironmentObject, whether or not you use it for binding anywhere in ContentView doesn't matter.

@State var bindingVar: Double = 0.0

As I said earlier that it's XCode bug and I don't know why ContentView needs one @State variable of the same type to start previewing @EnvironmentObject bound code.
May be ContentView_Previews unable to find out binding from @EnvironmentObject.

You can see below in my code, it works like a charm.

enter image description here

Upvotes: 0

Alex Fringes
Alex Fringes

Reputation: 671

As @graycampbell suggested, you need to ensure that the EnvironmentObject is provided to your ContentView in the SceneDelegate. While a lot of the preview / canvas mechanics are in a black box, Xcode's UI would suggest that invoking a new preview or refreshing an existing one, builds (or updates relevant parts of) a variant of your app even for the regular preview, as opposed to the "Live Preview". This process can fail if the SceneDelegate isn't set up correctly.

For your @Binding problem, Binding.constant(_:) should help. Per the SwiftUI Documentation .constant does the following:

Creates a binding with an immutable value.

This is what you want for your preview, instead of the @State your sample code shows. You can see an example of .constant in use in Section 3 of this Apple tutorial.

So instead of this:

#if DEBUG
struct ContentView_Previews: PreviewProvider {
    @State static var test1 = "Some Preview String"
    static var previews: some View {
        ContentView(test123: $test1)
             .environmentObject(NetworkManager())
    }
}
#endif

you can do the following:

#if DEBUG
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(test123: .constant("Some Preview String"))
            .environmentObject(NetworkManager())
    }
}
#endif

With this change, previews of your code work perfectly for me. Keep in mind that you again need to provide a value to this Binding in your SceneDelegate or any other place in which you use this particular ContentView. Otherwise you will run into a problem similar to the one you faced with EnvironmentObject, just that this particular omission luckily is highlighted by a compiler error.

Upvotes: 9

graycampbell
graycampbell

Reputation: 7810

I'm assuming based on the code you provided that your SceneDelegate looks like this:

if let windowScene = scene as? UIWindowScene {
    let window = UIWindow(windowScene: windowScene)
    window.rootViewController = UIHostingController(rootView: ContentView())
    self.window = window
    window.makeKeyAndVisible()
}

I'm not going to pretend I know exactly what the canvas is doing behind the scenes when it generates a preview, but based on the fact that the error specifically states that the app may have crashed, I'm assuming that it's attempting to launch the entire app when it tries to generate a preview. Maybe it needs to use the SceneDelegate to launch the preview, maybe it's something else entirely - I can't say for sure.

Regardless, the reason the app is crashing is because you aren't passing an environment object in your SceneDelegate. Your SceneDelegate should look like this:

if let windowScene = scene as? UIWindowScene {
    let window = UIWindow(windowScene: windowScene)
    window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(NetworkManager()))
    self.window = window
    window.makeKeyAndVisible()
}

Upvotes: 8

Nikolay Tabunchenko
Nikolay Tabunchenko

Reputation: 939

Looks like Xcode issue. Try to use blue button instead of "Try Again". enter image description here

Upvotes: 1

Related Questions