Skip to content

Instantly share code, notes, and snippets.

@JadenGeller
Last active August 25, 2022 06:00
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 JadenGeller/0b1fe76ab0406dbd6564fa27549b50a0 to your computer and use it in GitHub Desktop.
Save JadenGeller/0b1fe76ab0406dbd6564fa27549b50a0 to your computer and use it in GitHub Desktop.
Virtual Scrolling in SwiftUI
struct ExampleView: View {
var body: some View {
VirtualScrollView(height: contentHeight) { bounds in
VStack {
Color.clear
.frame(height: leadingPadding(for: bounds))
.overlay(
VStack {
Text("This is my list of cells.")
Text("I hope you really enjoy it!")
Text("<3 Jaden")
}
.padding()
.background(RoundedRectangle(cornerRadius: /*@START_MENU_TOKEN@*/25.0/*@END_MENU_TOKEN@*/).fill(Color.gray))
.opacity(Double(leadingPadding(for: bounds) / leadingPadding))
)
ForEach(0..<cellCount(for: bounds), id: \.self) { reuseOffset in
HStack {
Text("\(reuseOffset + indexOffset(for: bounds))")
Spacer()
Text("(Reuse \(reuseOffset))").foregroundColor(.gray)
}.frame(height: cellHeight)
}
}
.padding(.horizontal)
.offset(y: contentOffset(for: bounds))
}
}
let contentHeight: CGFloat = 1000
let cellHeight: CGFloat = 30
let leadingPadding: CGFloat = 200
func leadingPadding(for bounds: CGRect) -> CGFloat {
max(0, leadingPadding - bounds.origin.y)
}
func cellCount(for bounds: CGRect) -> Int {
Int(((bounds.height - leadingPadding(for: bounds)) / cellHeight).rounded(.up))
}
func contentOffset(for bounds: CGRect) -> CGFloat {
guard leadingPadding(for: bounds) == 0 else { return 0 }
return -(bounds.origin.y - leadingPadding).truncatingRemainder(dividingBy: cellHeight)
}
func indexOffset(for bounds: CGRect) -> Int {
guard leadingPadding(for: bounds) == 0 else { return 0 }
return Int(((bounds.origin.y - leadingPadding) / cellHeight).rounded(.towardZero))
}
}
struct VirtualScrollView<Content: View>: View {
var axes: Axis.Set = .vertical
var showsIndicators: Bool = true
var alignment: Alignment = .topLeading
var width: CGFloat? = nil
var height: CGFloat? = nil
@ViewBuilder var content: (CGRect) -> Content
var body: some View {
GeometryReader { outerGeometry in
ScrollView(axes, showsIndicators: showsIndicators) {
GeometryReader { innerGeometry in
let origin = innerGeometry
.frame(in: .named("outer"))
.origin
.applying(ProjectionTransform(.init(scaleX: -1, y: -1)))
ZStack {
content(CGRect(
origin: origin,
size: outerGeometry.size
))
}
.frame(width: outerGeometry.size.width, height: outerGeometry.size.height, alignment: alignment)
.offset(x: origin.x, y: origin.y)
}
.frame(width: width, height: height)
}
.coordinateSpace(name: "outer")
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment