I'm trying to create a parallax header effect in SwiftUI like this one https://twitter.com/KhaosT/status/1140814602017464320 , but don't really know how to get content offset of the list while scrolling.
Does anyone have know how to calculate the content offset of the list while scrolling?
I provided a reusable solution for the ScrollView
case on this answer which makes use of View Preferences as a method to notify layout information upstream in the View Hierarchy.
For a detail explanation of how View Preferences work, I will suggest reading this 3 articles series on the topic by kontiki
Unfortunately, such solution does not work for List
(perhaps a bug) as the View Preferences get trapped inside the List
and are not visible by its ancestors.
The only feasible solution so far is to observe the frame changes on the views inside the list. You could achieve this in two ways:
You can report and listen to layout changes on every view (cell) on the list (and act on it):
struct TestView1: View {
var body: some View {
GeometryReader { geometry in
List(TestEnum.allCases) { listValue in
Text(listValue.id)
.padding(60)
.transformAnchorPreference(key: MyKey.self, value: .bounds) {
$0.append(MyFrame(id: listValue.id, frame: geometry[$1]))
}
.onPreferenceChange(MyKey.self) {
print($0)
// Handle content frame changes here
}
}
}
}
}
or, alternatively, report and listen to frame changes on some table header view (or an empty header) if you do not need the frame changes on every cell:
struct TestView2: View {
var body: some View {
GeometryReader { geometry in
List {
Text("")
.transformAnchorPreference(key: MyKey.self, value: .bounds) {
$0.append(MyFrame(id: "tableTopCell", frame: geometry[$1]))
}
.onPreferenceChange(MyKey.self) {
print($0)
// Handle top view frame changes here.
// This only gets reported as long as this
// top view is part of the content. This could be
// removed when not visible by the List internals.
}
ForEach(TestEnum.allCases) {
Text($0.rawValue)
.padding(60)
}
}
}
}
}
Find below the supporting code for the solutions above: PreferenceKey
conforming struct, an identifiable view frame struct and a test enum as data source:
struct MyFrame : Equatable {
let id : String
let frame : CGRect
static func == (lhs: MyFrame, rhs: MyFrame) -> Bool {
lhs.id == rhs.id && lhs.frame == rhs.frame
}
}
struct MyKey : PreferenceKey {
typealias Value = [MyFrame] // The list of view frame changes in a View tree.
static var defaultValue: [MyFrame] = []
/// When traversing the view tree, Swift UI will use this function to collect all view frame changes.
static func reduce(value: inout [MyFrame], nextValue: () -> [MyFrame]) {
value.append(contentsOf: nextValue())
}
}
enum TestEnum : String, CaseIterable, Identifiable {
case one, two, three, four, five, six, seven, eight, nine, ten
var id: String {
rawValue
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With