Skip Navigation

SwiftUI macOS Target List with Section Row Spacing

I am trying to build a macOS (14.6) app that uses a combination of List(_:selection:rowContent:) and Section(isExpanded:content:header:), and while it "works", it looks like garbage. When using .plain, there are list row/section seperators, and even when using .listRowSeparator(.hidden) or .listSectionSeparator(.hidden), there is always one separator that is still visible between the first item and the remaining items.

When I try to use .listStyle(.sidebar), it adds its own disclosure indicator, but on the right side of the list row. It's tolerable, though I'd prefer the indicator on the left and not to auto-hide when not being hovered.

The kicker is that regardless of the .listStyle() used, there seems to be spacing/padding between the sections that cannot be removed. In Apple's infinite wisdom, they added .listRowSpacing(), but decided macOS shouldn't get to use it.

I am still new to all of this, and would really appreciate any advice on how I can style my UI the way I need it to be. I am using SwiftUI, but if there is another method (maybe UIKit or somthing?), I'm open to suggestion.

Here is my playground code used to generate the screenshots:

 
    
import SwiftUI
import PlaygroundSupport

struct Content: Hashable, Identifiable {
    var id: Self { self }
    var header: String
    var contents: [String]
}

struct ContentView: View {
    var contents: [Content] = [
        Content(header: "My Section 1", contents: ["Hello", "world"]),
        Content(header: "My Section 2", contents: ["Foo", "bar"]),
        Content(header: "My Section 3", contents: ["Help", "Me"]),
    ]

    @State private var expanded: Set<Content> = []
    @State private var selected: String?

    var body: some View {
        NavigationSplitView {
            List(contents, selection: $selected) { content in
                Section(isExpanded: Binding<Bool>(
                    get: { return expanded.contains(content) },
                    set: { expanding in
                        if expanding {
                            expanded.insert(content)
                        } else {
                            expanded.remove(content)
                        }
                    }
                ), content: {
                    ForEach(content.contents, id: \.self) { data in
                        Text(data)
                            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
                            .border(Color.orange)
                    }
                }, header: {
                    Text(content.header)
                        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
                        .onTapGesture {
                            if expanded.contains(content) {
                                expanded.remove(content)
                            } else {
                                expanded.insert(content)
                            }
                        }
                })
                .listRowInsets(EdgeInsets(
                    top: 0,
                    leading: -2,
                    bottom: 0,
                    trailing: -14
                ))
            }
        } detail: {
            ContentUnavailableView {
                Label("No selection made", systemImage: "tray")
            }
        }
        .border(Color.gray)
    }
}

// Present the view in Playground
PlaygroundPage.current.setLiveView(ContentView())

  

Edit: 3 hours later... I was able to remove the spacing from the section content's items by using listRowInsets(_ insets:) and removing some of the padding I put in the code. But, I still do not know how to affect the section headers. I've updated the code above, and here's a new screenshot:

4 comments