modemlooper avatar
About Blog

SwiftUI Stretchy Header

4 min read
swiftui

Adding a sticky header that stretches on scroll to a ScrollView.

Import SwiftUI
/// A reusable SwiftUI view that features a stretchy header positioned below a navigation bar.
///
/// This view is generic and accepts two custom view builders ("slots"):
/// - `header`: The content to display in the stretchy header area.
/// - `content`: The main scrollable content to display below the header.
struct ReusableStretchyHeaderView<HeaderContent: View, MainContent: View>: View {
    
    // MARK: Properties
    private let headerHeight: CGFloat
    private let header: () -> HeaderContent
    private let content: () -> MainContent

    // MARK: Initializer
    init(
        headerHeight: CGFloat = 220,
        @ViewBuilder header: @escaping () -> HeaderContent,
        @ViewBuilder content: @escaping () -> MainContent
    ) {
        self.headerHeight = headerHeight
        self.header = header
        self.content = content
    }

    // MARK: Body
    var body: some View {
        ScrollView(.vertical, showsIndicators: false) {
            VStack(spacing: 0) {
                // Header slot
                GeometryReader { geometry in
                    let minY = geometry.frame(in: .named("scroll")).minY
                    let isStretching = minY > 0
                    
                    let height = isStretching ? headerHeight + minY : headerHeight
                    let yOffset = isStretching ? -minY : 0
                    
                    // The ZStack ensures the user's header content is properly framed and clipped.
                    ZStack {
                        header()
                    }
                    .frame(width: geometry.size.width, height: height)
                    .clipped()
                    .offset(y: yOffset)
                }
                .frame(height: headerHeight)

                // Main content slot
                content()

            }
        }
        .coordinateSpace(name: "scroll")
    }
}