Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xta/498899aa2191b6325a00ad684d7cbaf0 to your computer and use it in GitHub Desktop.
Save xta/498899aa2191b6325a00ad684d7cbaf0 to your computer and use it in GitHub Desktop.
A content view which renders a collapsable header that adapts to the current scroll position. Based on OffsetObservingScrollView from https://swiftbysundell.com/articles/observing-swiftui-scrollview-content-offset.
/// View that renders scrollable content beneath a header that
/// automatically collapses when the user scrolls down.
struct ContentView<Content: View>: View {
var title: String
var headerGradient: Gradient
@ViewBuilder var content: () -> Content
private let headerHeight = (collapsed: 50.0, expanded: 150.0)
@State private var scrollOffset = CGPoint()
var body: some View {
GeometryReader { geometry in
OffsetObservingScrollView(offset: $scrollOffset) {
VStack(spacing: 0) {
makeHeaderText(collapsed: false)
content()
}
}
.overlay(alignment: .top) {
makeHeaderText(collapsed: true)
.background(alignment: .top) {
headerLinearGradient.ignoresSafeArea()
}
.opacity(collapsedHeaderOpacity)
}
.background(alignment: .top) {
// We attach the expanded header's background to the scroll
// view itself, so that we can make it expand into both the
// safe area, as well as any negative scroll offset area:
headerLinearGradient
.frame(height: max(0, headerHeight.expanded - scrollOffset.y) + geometry.safeAreaInsets.top)
.ignoresSafeArea()
}
}
}
}
private extension ContentView {
var collapsedHeaderOpacity: CGFloat {
let minOpacityOffset = headerHeight.expanded / 2
let maxOpacityOffset = headerHeight.expanded - headerHeight.collapsed
guard scrollOffset.y > minOpacityOffset else { return 0 }
guard scrollOffset.y < maxOpacityOffset else { return 1 }
let opacityOffsetRange = maxOpacityOffset - minOpacityOffset
return (scrollOffset.y - minOpacityOffset) / opacityOffsetRange
}
var headerLinearGradient: LinearGradient {
LinearGradient(
gradient: headerGradient,
startPoint: .top,
endPoint: .bottom
)
}
func makeHeaderText(collapsed: Bool) -> some View {
Text(title)
.font(collapsed ? .body : .title)
.lineLimit(1)
.padding()
.frame(height: collapsed ? headerHeight.collapsed : headerHeight.expanded)
.frame(maxWidth: .infinity)
.foregroundColor(.white)
.accessibilityHeading(.h1)
.accessibilityHidden(collapsed)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment