UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Cannot display .sheet referencing a boolean

Forums > SwiftUI

In ContentView I have this:

@Observable class SportMenuStatus {
    var isSportMenuShowing = false
}

which is passed into the ContentView like this:

struct ContentView: View {
    // Initialise the menu state
    @State var menuState = SportMenuStatus()
    @State private var tab = 0

    var body: some View {
    ...

Nested inside ContentView is Home, which starts like this:

struct Home: View {
    @State private var stackPath: [String] = []
    @Environment(SportMenuStatus.self) var menuState
    ...

And then at the bottom of this view I am trying to show a .sheet on the basis of that boolean changing:

.sheet(isPresented: $menuState.isSportMenuShowing, content: {
                Text("hi")
                })

However, this gives me the error: "Cannot find '$menuState' in scope"; why is this?

   

The reason is on HWS+ Introduction to Observation but will try to give you quotes

Right now @Environment does not have a projected value, which means we can’t use the $ prefix to access bindings to its data. This is what’s causing our problem: we’re trying to bind our text fields to something that doesn’t actually provide bindings any more, which isn’t possible.

This is Very Annoying, and I hope it will change before iOS 17 is released as final. During the WWDC23 digital lounges, one of the SwiftUI team specifically said, “for this to work we need an API that adds a projected value to @Environment – if that's something you'd like to see, please file a feedback report!” So, this is your chance: if you want this behavior to change, please tell Apple that you’d like them to add support for creating bindings from @Observable objects in the environment.

In the meantime – and if you’re watching this later, if Apple have yet to change this behavior – we need a fix, and like many SwiftUI fixes this is another property wrapper. Yes, just as Apple has removed @EnvironmentObject, @StateObject, and @ObservedObject, they have introduced another new one, this time called @Bindable.

So the main should be this. As you should put into environment at start of app.

@main
struct TestApp: App {
    @State var menuState = SportMenuStatus()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(menuState)
        }
    }
}

ContentView

struct ContentView: View {
    var body: some View {
        VStack {
            HomeView()
        }
        .padding()
    }
}

HomeView

struct HomeView: View {
    @Environment(SportMenuStatus.self) var menuState

    var body: some View {
        @Bindable var menuState = menuState

        Button {
            menuState.isSportMenuShowing = true
        } label: {
            Text("Show Sheet \(menuState.isSportMenuShowing)")
        }
        .sheet(isPresented: $menuState.isSportMenuShowing) {
            Text("Sheet Presented")
        }
    }
}

or ContentView

struct ContentView: View {
    @Environment(SportMenuStatus.self) var menuState

    var body: some View {
        VStack {
            HomeView(menuState: menuState)
        }
        .padding()
    }
}

and HomeView

struct HomeView: View {
    @Bindable var menuState: SportMenuStatus

    var body: some View {
        Button {
            menuState.isSportMenuShowing = true
        } label: {
            Text("Show Sheet")
        }
        .sheet(isPresented: $menuState.isSportMenuShowing) {
            Text("Sheet Presented")
        }
    }
}

   

@NigelGee thanks (as ever!), although I get the error: Thread 1: Fatal error: No Observable object of type SportMenuStatus found. A View.environmentObject(_:) for SportMenuStatus may be missing as an ancestor of this view.

I had moved:

@Observable class SportMenuStatus {
    var isSportMenuShowing = false
}

Into the start of the App (was in ContentView), and that now looks like this:

import SwiftUI

@Observable class SportMenuStatus {
    var isSportMenuShowing = false
}

@main
struct sportsnavApp: App {
    @State var menuState = SportMenuStatus()
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

ContentView does not have any @Environment or @Binding now, and Home (which is nested view inside ContentView) has:

struct Home: View {
    @State private var stackPath: [String] = []
    @Environment(SportMenuStatus.self) var menuState
//    @Bindable var menuState: SportMenuStatus

    var body: some View {
        @Bindable var menuState = menuState

At the top.

Previewing Home works as expected, but viewing ContentView gives a Preview crash (the big red cross in the Canvas)

Is the something else missing?

   

You need to add this to App in the WindowGroup

ContentView()
    .environment(menuState)

to stop the Preview crashing

#Preview {
   Home()
      .environment(SportMenuStatus())
}

   

Hacking with Swift is sponsored by RevenueCat.

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.