Skip to content

Instantly share code, notes, and snippets.

@cjnevin
Last active April 14, 2023 04:20
Show Gist options
  • Save cjnevin/cd75757230496342bada7a432d2f8dd9 to your computer and use it in GitHub Desktop.
Save cjnevin/cd75757230496342bada7a432d2f8dd9 to your computer and use it in GitHub Desktop.
import SwiftUI
extension View {
func sticky(in coordinateSpace: CoordinateSpace) -> some View {
modifier(StickyViewModifier(coordinateSpace: coordinateSpace))
}
}
private struct StickyViewModifier: ViewModifier {
let coordinateSpace: CoordinateSpace
@State private var frame: CGRect = .zero
private var isSticking: Bool {
frame.minY < 0
}
private var offset: CGFloat {
isSticking ? -frame.minY : 0
}
private var opacity: CGFloat {
isSticking ? (frame.height + frame.minY) / frame.height : 1.0
}
func body(content: Content) -> some View {
content
.offset(y: offset)
.opacity(opacity)
.overlay(GeometryReader { proxy in
let f = proxy.frame(in: coordinateSpace)
Color.clear
.onAppear { frame = f }
.onChange(of: f) { frame = $0 }
.preference(key: FramePreference.self, value: [frame])
})
}
}
private struct FramePreference: PreferenceKey {
static var defaultValue: [CGRect] = []
static func reduce(value: inout [CGRect], nextValue: () -> [CGRect]) {
value.append(contentsOf: nextValue())
}
}
struct LayeredScrollView: View {
private let coordinateSpaceName: String = "container"
var body: some View {
ScrollView {
contents
}
.background(Color.black)
.clipped()
.coordinateSpace(name: coordinateSpaceName)
}
@ViewBuilder var contents: some View {
LazyVStack(spacing: 0, pinnedViews: [.sectionHeaders]) {
Section {
ZStack {
Rectangle().fill(.green)
Text("Background")
}
.frame(height: 450)
.sticky(in: .named(coordinateSpaceName))
ZStack {
Rectangle().fill(.blue)
Text("Background")
}
.frame(height: 450)
.sticky(in: .named(coordinateSpaceName))
ZStack {
Rectangle().fill(.brown)
Text("Content")
}
.frame(height: 350)
}
Section {
ZStack {
Rectangle().fill(.brown)
Text("Content")
}
.frame(height: 350)
} header: {
ZStack {
Rectangle().fill(.red)
Text("Header")
}
.frame(height: 80)
}
Section {
ZStack {
Rectangle().fill(.yellow)
Text("Background")
}
.frame(height: 500)
.sticky(in: .named(coordinateSpaceName))
ZStack {
Rectangle().fill(.brown)
Text("Content")
}
.frame(height: 350)
ZStack {
Rectangle().fill(.gray)
Text("Content")
}
.frame(height: 350)
}
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
ZStack {
GeometryReader { reader in
Color.white
.ignoresSafeArea()
.frame(height: reader.safeAreaInsets.top)
}
LayeredScrollView()
}
.navigationTitle("Categories")
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.hidden, for: .navigationBar)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment