The Apple sample code Landmarks project uses a LazyVStack inside a ScrollView.
For a List, which you want, you will need to:
Put the ContainerView in the List as the first row.
Set the listRowBackground of the ContainerView to EmptyView().
Set the listStyle to plain, for listRowBackground to work.
Add .background(alignment: .top) { with the background you want for the ContainerView.
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()
}
}