How to Create a Full-Width Container (Edge-to-Edge Layout) Above a List View

I’m trying to add a container view above a list view that stretches edge-to-edge across the device.

What’s the recommended approach to achieve this layout with Swift UI?

Example

Hello @danilopeixoto,

One way is to place both views in a VStack, while making sure the top content uses .ignoresSafeArea().

You can see this demonstrated in our sample code Landmarks

If you still need help feel free to provide more information in this thread!

 Travis Trotto - DTS Engineer

Do you mean something like this?

VStack {
    ContainerView()
        .ignoresSafeArea()
    List {}
}

However, with this approach only the List is scrollable. The behavior I’m trying to achieve is similar to the image in the post above. Perhaps something like this?

ScrollView {
    ContainerView()
        .ignoresSafeArea()
    List { }
}

However, this doesn’t work because List is scrollable on its own. However, Im still interested in the look and feel of a List below the container.

The Apple sample code Landmarks project uses a LazyVStack inside a ScrollView.

For a List, which you want, you will need to:

  1. Put the ContainerView in the List as the first row.
  2. Set the listRowBackground of the ContainerView to EmptyView().
  3. Set the listStyle to plain, for listRowBackground to work.
  4. Add .background(alignment: .top) { with the background you want for the ContainerView.
  5. Use readFrame() to dynamically adjust the height of the background to match the maxY position of the ContainerView.
import SwiftUI

struct ListHeaderScene: View {
    @State var headerRect: CGRect?
    
    var headerHeight: CGFloat {
        headerRect?.maxY ?? 0
    }
    
    var body: some View {
        List {
            ContainerView()
                .readFrame { headerRect = $0 }
                .listRowSeparator(.hidden)
                .listRowBackground(EmptyView())
            Section("Section Title") {
                Text("Row 1")
                Text("Row 2")
            }
        }
        .listStyle(.plain)
        .background(alignment: .top) {
            // You can swap this for an image or whatever.
            Color.purple
                .frame(height: headerHeight)
                .ignoresSafeArea()
        }
    }
}

struct ContainerView: View {
    var body: some View {
        VStack {
            Image(systemName: "fork.knife.circle.fill")
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(height: 66)
                .foregroundStyle(Color.purple)
                .padding(8)
                .background(.white)
                .clipShape(RoundedRectangle(cornerRadius: 24))
            Text("Nutrition Healthcare & Fitness")
                .font(.title)
                .bold()
                .multilineTextAlignment(.center)
                .colorScheme(.dark)
            HStack {
                Button("Open") {}
                Button("Feedback") {}
            }
            .buttonStyle(.borderedProminent)
            .accentColor(Color.black.opacity(0.2))
        }
        .padding()
        .frame(maxWidth: .infinity, alignment: .center)
    }
}

#Preview {
    NavigationStack {
        ListHeaderScene()
    }
}
//  Extracted from BFWViews: https://bitbucket.org/barefeetware/bfwviews/

import SwiftUI

public extension View {
    func readFrame(
        in coordinateSpace: CoordinateSpace = .global,
        writer: @escaping (CGRect) -> Void
    ) -> some View {
        background(
            GeometryReader { geometry in
                Color.clear.preference(
                    key: FramePreferenceKey.self,
                    value: geometry.frame(in: coordinateSpace)
                )
                .onPreferenceChange(FramePreferenceKey.self) {
                    writer($0)
                }
            }
        )
    }
}

private struct FramePreferenceKey: PreferenceKey {
    
    static let defaultValue: CGRect = .zero
    
    static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
        value = nextValue()
    }
    
}

How to Create a Full-Width Container (Edge-to-Edge Layout) Above a List View
 
 
Q