Skip to content

Instantly share code, notes, and snippets.

@LePips
Last active March 17, 2023 21:37
Show Gist options
  • Save LePips/f2ea32ee2748b6baef0e896f69f665aa to your computer and use it in GitHub Desktop.
Save LePips/f2ea32ee2748b6baef0e896f69f665aa to your computer and use it in GitHub Desktop.
SwiftUI ViewModifier to create a parallax and stretchy background header based on ScrollView offset
// Assumes usage of:
// - https://gist.github.com/LePips/3640ad0cd9b6e2ceb407e9d0e9e32b5c
// - https://gist.github.com/LePips/5c6b4546e9bd8f91c029f705347be974
//
// Last gist not required, but nice to have for headers.
struct BackgroundParallaxHeaderModifier<Header: View>: ViewModifier {
@Binding
var scrollViewOffset: CGFloat
let height: CGFloat
let multiplier: CGFloat
let header: () -> Header
init(_ scrollViewOffset: Binding<CGFloat>,
height: CGFloat,
multiplier: CGFloat = 1,
@ViewBuilder header: @escaping () -> Header) {
self._scrollViewOffset = scrollViewOffset
self.height = height
self.multiplier = multiplier
self.header = header
}
func body(content: Content) -> some View {
content.background(alignment: .top, content: {
header()
.offset(y: scrollViewOffset > 0 ? -scrollViewOffset * multiplier : 0)
.scaleEffect(scrollViewOffset < 0 ? (height - scrollViewOffset) / height : 1, anchor: .top)
.mask(alignment: .top) {
Color.black
.frame(height: max(0, height - scrollViewOffset))
}
.ignoresSafeArea()
})
}
}
extension View {
func backgroundParallaxHeader<Header: View>(_ scrollViewOffset: Binding<CGFloat>,
height: CGFloat,
multiplier: CGFloat = 1,
@ViewBuilder header: @escaping () -> Header) -> some View {
self.modifier(BackgroundParallaxHeaderModifier(scrollViewOffset,
height: height,
multiplier: multiplier,
header: header))
}
}
// Example
struct ContentView: View {
@State
private var scrollViewOffset: CGFloat = 0
var body: some View {
NavigationView {
ScrollView {
VStack {
// Show background parallax header
Color.clear
.frame(height: 300)
VStack {
ForEach(0..<100) { _ in
Text("Hello There")
}
}
.background(Color.black)
}
.frame(maxWidth: .infinity)
}
.navigationTitle("Test")
.navigationBarTitleDisplayMode(.inline)
.ignoresSafeArea()
.scrollViewOffset($scrollViewOffset)
.navBarOffset($scrollViewOffset, start: 270, end: 320)
.backgroundParallaxHeader($scrollViewOffset, height: 300, multiplier: 0.5) { // Will offset slower than scroll speed
Color.red
.frame(height: 300)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment