Skip to content

Instantly share code, notes, and snippets.

@modemlooper
Created September 17, 2025 20:56
Show Gist options
  • Select an option

  • Save modemlooper/597c441b630ac05d69c95e2b00f6a1a2 to your computer and use it in GitHub Desktop.

Select an option

Save modemlooper/597c441b630ac05d69c95e2b00f6a1a2 to your computer and use it in GitHub Desktop.
swiftui stretchy header
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")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment