struct StickyHeader: View {
var body: some View {
ScrollView {
ZStack {
// Bottom Layer
VStack(spacing: 20) {
Tile(imageName: "Arches", tileLabel: "Arches")
Tile(imageName: "Canyonlands", tileLabel: "Canyonlands")
Tile(imageName: "BryceCanyon", tileLabel: "Bryce Canyon")
Tile(imageName: "GoblinValley", tileLabel: "Goblin Valley")
Tile(imageName: "Zion", tileLabel: "Zion")
}
.padding(.horizontal, 20)
}
}.edgesIgnoringSafeArea(.vertical)
}
}
struct StickyHeader: View {
var body: some View {
ScrollView {
ZStack {
// Bottom Layer
VStack(spacing: 20) {
Tile(imageName: "Arches", tileLabel: "Arches")
Tile(imageName: "Canyonlands", tileLabel: "Canyonlands")
Tile(imageName: "BryceCanyon", tileLabel: "Bryce Canyon")
Tile(imageName: "GoblinValley", tileLabel: "Goblin Valley")
Tile(imageName: "Zion", tileLabel: "Zion")
}
.padding(.horizontal, 20)
// Top Layer (Header)
GeometryReader { gr in
VStack {
Image("Utah")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(height: 300)
.overlay(
Text("UTAH")
.font(.system(size: 70, weight: .black))
.foregroundColor(.white)
.opacity(0.8))
Spacer() // Push header to top
}
}
}
}.edgesIgnoringSafeArea(.vertical)
}
}
// Bottom Layer
VStack(spacing: 20) {
Tile(imageName: "Arches", tileLabel: "Arches")
Tile(imageName: "Canyonlands", tileLabel: "Canyonlands")
Tile(imageName: "BryceCanyon", tileLabel: "Bryce Canyon")
Tile(imageName: "GoblinValley", tileLabel: "Goblin Valley")
Tile(imageName: "Zion", tileLabel: "Zion")
}
.padding(.horizontal, 20)
.padding(.top, 300)
// Top Layer (Header)
GeometryReader { gr in
VStack {
Image("Utah")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(height: 300)
.overlay(
Text("UTAH")
.font(.system(size: 70, weight: .black))
.foregroundColor(.white)
.opacity(0.8))
// Offset just on the Y axis
.offset(y: gr.frame(in: .global).origin.y < 0 // Is it going up?
? abs(gr.frame(in: .global).origin.y) // Push it down!
: -gr.frame(in: .global).origin.y) // Push it up!
Spacer() // Push header to top
}
}
func calculateHeight(minHeight: CGFloat, maxHeight: CGFloat, yOffset: CGFloat) -> CGFloat {
// If scrolling up, yOffset will be a negative number
if maxHeight + yOffset < minHeight {
// SCROLLING UP
// Never go smaller than our minimum height
return minHeight
}
// SCROLLING DOWN
return maxHeight + yOffset
}
// Top Layer (Header)
GeometryReader { gr in
VStack {
Image("Utah")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(height:
self.calculateHeight(minHeight: 120,
maxHeight: 300,
yOffset: gr.frame(in: .global).origin.y))
.overlay(
Text("UTAH")
.font(.system(size: 70, weight: .black))
.foregroundColor(.white)
.opacity(0.8))
// Offset just on the Y axis
.offset(y: gr.frame(in: .global).origin.y < 0 // Is it going up?
? abs(gr.frame(in: .global).origin.y) // Push it down!
: -gr.frame(in: .global).origin.y) // Push it up!
Spacer() // Push header to top
}
}
// Show a shadow when minHeight is reached
.shadow(radius: self.calculateHeight(minHeight: 120,
maxHeight: 300,
yOffset: gr.frame(in: .global).origin.y) < 140 ? 8 : 0)
func calculateHeight(minHeight: CGFloat, maxHeight: CGFloat, yOffset: CGFloat) -> CGFloat {
// If scrolling up, yOffset will be a negative number
if maxHeight + yOffset < minHeight {
// SCROLLING UP
// Never go smaller than our minimum height
return minHeight
}
else if maxHeight + yOffset > maxHeight {
// SCROLLING DOWN PAST MAX HEIGHT
return maxHeight + (yOffset * 0.5) // Lessen the offset
}
// Return an offset that is between the min and max heights
return maxHeight + yOffset
}