swiftuiswiftui-state

SwiftUI: sheet not presented if triggered from subview when superview can also present sheet


Let's say I have a view in a NavigationView, with leading and trailing buttons. Because I can present two different sheets from this view, I use an enum like that:

enum AccountActiveSheet {
   case about, settings
}

This main view has two @State properties in the view to trigger the sheet presentation.

@State private var isSheetPresented: Bool = false
@State private var activeSheet: AccountActiveSheet = .about

Each button can trigger a sheet.

var aboutButton: some View {
    Button(action: {
        self.activeSheet = .about
        self.isSheetPresented.toggle()
    }, label: {
        Image(systemName: "info.circle.fill")
    })
}

At the end of the navigation view, I select the correct sheet to present like that

.sheet(isPresented: $isSheetPresented) {
   self.activeSheet.sheet
}

It's working fine and presenting the correct view in a modal.

But I also have multiple subviews in this main view that can present sheets when tapping on a button. In this case, the sheet managed by the subview (i.e. a @State variable to track the $isSheetPresented + content for the sheet provided by the subview) is not presented. Nothing happens. It seems the superview sheet modifier is preventing the subview to present the sheet.

Is it possible to have both a superview and a subview manage sheet presentation?

enum AccountActiveSheet {
    case about, settings

    @ViewBuilder
    var sheet: some View {
        switch self {
        case .about: Text("About")
        case .settings: Text("Settings")
        }
    }
}

struct AccountView: View {
    @State private var isSheetPresented: Bool = false
    @State private var activeSheet: AccountActiveSheet = .about
    
    var aboutButton: some View {
        Button(action: {
            self.activeSheet = .about
            self.isSheetPresented.toggle()
        }, label: {
            Image(systemName: "info.circle.fill")
        })
    }
    
    var settingsButton: some View {
        Button(action: {
            self.activeSheet = .settings
            self.isSheetPresented.toggle()
        }, label: {
            Image(systemName: "gearshape.fill")
        })
    }

    var body: some View {
        NavigationView {
            VStack {
                Subview()
            }
            .navigationBarTitle("Account", displayMode: .inline)
            .navigationBarItems(leading: aboutButton, trailing: settingsButton)
            .sheet(isPresented: $isSheetPresented) {
                self.activeSheet.sheet
            }
        }
    }
}

struct Subview: View {
    @State var isSheetPresented: Bool = false

    var body: some View {
        Button("Present sheet") {
            self.isSheetPresented.toggle()
        }
        .sheet(isPresented: $isSheetPresented) {
            Text("Subview")
        }
    }
}

Solution

  • Move root view sheet out of NavigationView (tested with Xcode 12.1 / iOS 14.1)

    var body: some View {
        NavigationView {
            VStack {
                Subview()
            }
            .navigationBarTitle("Account", displayMode: .inline)
            .navigationBarItems(leading: aboutButton, trailing: settingsButton)
        }
        .sheet(isPresented: $isSheetPresented) {   // << here !!
            self.activeSheet.sheet
        }
    }