Search code examples
iosswiftswiftui

.sheet dismissed immediately when presented over DisclosureGroup


I have a DisclosureGroup which when expanded, has a Button that presents a .sheet. However, when the .sheet is presented the first time, it is immediately dismissed with an error to the console:

Attempt to present <TtGC7SwiftUI29PresentationHostingControllerVS_7AnyView: 0x11c210700> on <TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier_: 0x104188600> (from <TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier_: 0x104188600>) while a presentation is in progress.

Here is the code:

struct HomeView: View
{
    var body: some View
    {
        List
        {
            CompositedTestDisclosureGroup()
        }
    }
 }

struct CompositedTestDisclosureGroup: View
{
    @State private var disclosureGroupIsExpanded = false
    @State private var sheetIsPresented = false
        
    var body: some View
    {
        TestDisclosureGroup(disclosureGroupIsExpanded: self.$disclosureGroupIsExpanded)
        {
            self.sheetIsPresented = true
        }
        .sheet(isPresented: self.$sheetIsPresented)
        {
            Text("Test")
        }
    }
}

struct TestDisclosureGroup: View
{
    @Binding var disclosureGroupIsExpanded: Bool
    var presentSheet: () -> Void
    
    var body: some View
    {
        DisclosureGroup("Disclosure Group", isExpanded: self.$disclosureGroupIsExpanded)
        {
            Button("Push Me")
            {
                presentSheet()
            }
            .buttonStyle(.borderedProminent)
        }
    }
}

I know if I remove the outer List in HomeView,remove the DisclosureGroup and just have the Button by itself or have the .sheet presented in HomeView, it will work but I'd like to try and keep the design the way it is.

Any insight into why this is happening is much appreciated, thanks.


Solution

  • When a DisclosureGroup is shown inside a List, it seems it is just like a Group. In other words, it behaves like a collection of views, rather than like a container.

    In this context, applying .sheet as modifier to DisclosureGroup (or to the wrapper view TestDisclosureGroup) is like adding it separately to the label and to the content. When the flag sheetIsPresented is set to true, both sheets try to show simultaneously, hence the error.


    One way to fix is to wrap the DisclosureGroup with a container and apply the .sheet to the container instead. It's interesting to try a ZStack as container:

    ZStack {
        TestDisclosureGroup(disclosureGroupIsExpanded: $disclosureGroupIsExpanded) {
            sheetIsPresented = true
        }
    }
    .sheet(isPresented: $sheetIsPresented) {
        Text("Test")
    }
    

    When the DisclosureGroup is expanded, it looks like this:

    enter image description here

    This illustrates how the DisclosureGroup behaves like a group of views. It also demonstrates, that ZStack is not a good choice of container.

    A VStack works a bit better, but the animation is not good.

    So a better way to fix is to move the .sheet modifier to the Button inside the DisclosureGroup, or to the Label (by using a different initializer for DisclosureGroup), or to the parent List. This of course means changing the way you are passing the bindings and closures around.

    Here is how it can be moved to the List:

    struct HomeView: View {
        @State private var sheetIsPresented = false
        var body: some View {
            List {
                CompositedTestDisclosureGroup(sheetIsPresented: $sheetIsPresented)
            }
            .sheet(isPresented: $sheetIsPresented) {
                Text("Test")
            }
        }
     }
    
    struct CompositedTestDisclosureGroup: View {
        @State private var disclosureGroupIsExpanded = false
        @Binding var sheetIsPresented: Bool
    
        var body: some View {
            TestDisclosureGroup(disclosureGroupIsExpanded: $disclosureGroupIsExpanded) {
                sheetIsPresented = true
            }
        }
    }