Getting content offset of List in SwiftUI

拜拜、爱过 提交于 2019-12-10 13:17:25

问题


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?


回答1:


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
    }
}


来源:https://stackoverflow.com/questions/56726369/getting-content-offset-of-list-in-swiftui

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!