iosswiftswiftuiswiftui-listhealthkit

SwiftUI: How to create different background colors for List sections?


I'm trying to achieve a specific UI design in SwiftUI where the bottom section of my List has a different background color than the top section. For example, the "Your Medications" Section has a different background than the top "Log" Section:

Goal layout:

enter image description here

Here some example code. I wonder if I am supposed to use two Lists instead. If I use two Lists though and nest it in a ScrollView, the height of the lists needs to be specified. I am working with dynamic content, though so I don't think that is ideal.

class ProtocolMedication {} // Example model

struct HomeView: View {
    @Query private var protocolMedications: [ProtocolMedication]
    
    var body: some View {
        NavigationStack {
            List {
                // Upper sections with default background
                Section {
                    Text("Content 1")
                } header: {
                    Text("Log")
                }
                // Bottom section that needs different background
                Section {
                    ForEach(protocolMedications) { medication in
                        Text(medication.name)
                    }
                } header: {
                    Text("Your Medications")
                }
            }
            .listStyle(.insetGrouped)
        }
    }
}

Solution

  • If I understand correctly, you want a different color for the outer background around the section, this being the list group background.

    If you had more sections then you can use the same technique for them too. Each section would have its own layer in the ZStack. In this case, you might want to avoid colors that are partially transparent (colors with opacity), unless you want the transparency effects to combine.

    For the list rows themselves, set your own background using .listRowBackground.

    struct HomeView: View {
        @Namespace private var ns
    
        var body: some View {
            NavigationStack {
                List {
                    // Upper sections with default background
                    Section {
                        Text("Content 1")
                    } header: {
                        Text("Log")
                    }
                    // Bottom section that needs different background
                    Section {
                        ForEach(1..<6) { i in
                            Text("Row \(i)")
                        }
                        .padding(.leading, 80)
                        .frame(minHeight: 80)
                        .listRowBackground(
                            HStack(spacing: 0) {
                                Image(systemName: "pills")
                                    .resizable()
                                    .scaledToFit()
                                    .frame(width: 50)
                                    .symbolRenderingMode(.hierarchical)
                                    .padding(.horizontal)
                                    .frame(maxHeight: .infinity)
                                    .background(Color(.tertiarySystemBackground).gradient)
                                Color(.secondarySystemGroupedBackground)
                            }
                        )
                    } header: {
                        Text("Your Medications")
                            .frame(maxWidth: .infinity, alignment: .leading)
                            .matchedGeometryEffect(
                                id: "Section2",
                                in: ns,
                                anchor: .top
                            )
                    }
                }
                .listStyle(.insetGrouped)
                .scrollContentBackground(.hidden)
                .background {
                    ZStack {
                        Color(.systemGroupedBackground)
                        Color.yellow
                            .opacity(0.5)
                            .padding(.top, -16)
                            .matchedGeometryEffect(
                                id: "Section2",
                                in: ns,
                                properties: .position,
                                anchor: .top,
                                isSource: false
                            )
                    }
                    .ignoresSafeArea()
                }
            }
        }
    }
    

    Screenshot