Search code examples
iosswiftuiswiftui-listswiftui-navigationstackswiftui-layout

How to combine GroupBox or DisclosureGroup with a List in a SwiftUI NavigationStack


Ultimately, I am trying to replicate the layout used in Apple's Tips app:

Screenshot of Apple's Tips app on iOS

The app appears to have a NavigationStack that contains non-list items but that fit the overall list style. When I try to introduce a DisclosureGroup or GroupBox into the list, it has it's own padding for that item that I don't appear to be able to remove without removing it for all the lists breaking the layout achieved in the Tips app above.

If I introduce a ScrollView, then the List disappears and placing another item above the list makes only the List scrollable. This also stops the navigation bar from collapsing as you scroll.

Here's a simplified example of what I assumed would work:

NavigationStack {
    GroupBox(label: Text("My Label"), content: {
        Text("The content")
    })
    .padding()
    List {
        Section(header: Text("My First Section")) {
            Text("First list item")
            Text("Second list item")
        }
    }
    .navigationTitle("Navigation Title")
    .toolbarBackground(Color.blue)
}

In this example, the GroupBox appears tied to the NavigationStack title bar. If I give it a background colour, there's also a clear separation between the GroupBox and the List.

The Section headings are also non-standard but can be achieved with some simple modifiers but I am looking for a way to introduce non-list content into the scrollable view the List provides in SwiftUI.

I would happily place the GroupBox into a list if I could collapse the padding but that does not appear to work:

List {
    Section {
        GroupBox(label: Text("Label"), content: {
            Text("Content")
        })
        .listRowInsets(.none)
        .padding(0)
    }
}

I am not particularly bothered by the search bar, although this may be a useful requirement for the future. Right now I would just like to build a view that made use of the NavigationStack which sets a title for the view and when you scroll the entire page scrolls. The design language here fits my use case perfectly but I've struggled with the insertion of non-list elements and hope someone can point me in the right direction.


Solution

  • When the GroupBox is inside the List, you can get rid of the extra padding by using explicit EdgeInsets instead of .none. Then to set a background color, use .backgroundStyle:

    NavigationStack {
        List {
            Section {
                GroupBox("The Label") {
                    Text("The Content")
                }
                .backgroundStyle(.yellow)
            }
            .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
    
            Section(header: Text("My First Section")) {
                Text("First list item")
                Text("Second list item")
            }
        }
        .navigationTitle("Navigation Title")
        .toolbarBackground(Color.blue)
    }
    

    Screenshot

    Alternatively, you can just use a VStack and style it any way you like:

    NavigationStack {
        List {
            Section {
                VStack(alignment: .leading, spacing: 12) {
                    Text("The Label")
                        .font(.headline)
                    Text("The Content")
                        .frame(maxWidth: .infinity)
                }
            }
            .listRowBackground(Color.orange)
    
            Section(header: Text("My First Section")) {
                Text("First list item")
                Text("Second list item")
            }
        }
        .navigationTitle("Navigation Title")
        .toolbarBackground(Color.blue)
    }
    

    Screenshot