alf*_*ern 0 macos scrollview ios contentoffset swiftui
我想要一个ScrollView你可以在用户滚动时知道内容框架变化的地方(类似于didScrollUIKit 中的委托UIScrollView)。
这样,您就可以根据滚动行为执行布局更改。
通过使用视图首选项作为通知视图层次结构上游布局信息的方法,我设法为这个问题提供了一个很好的解决方案。
有关“查看首选项”如何工作的非常详细的说明,我建议您阅读kontiki撰写的关于该主题的3 篇系列文章
对于我的解决方案,我实现了两个ViewModifiers:一个使用锚点首选项在其布局上更改视图报告,第二个允许 aView处理其子树上视图上的帧的更新。
为此,我们首先定义一个Struct来携带可识别的帧信息上行:
/// Represents the `frame` of an identifiable view as an `Anchor`
struct ViewFrame: Equatable {
/// A given identifier for the View to faciliate processing
/// of frame updates
let viewId : String
/// An `Anchor` representation of the View
let frameAnchor: Anchor<CGRect>
// Conformace to Equatable is required for supporting
// view udpates via `PreferenceKey`
static func == (lhs: ViewFrame, rhs: ViewFrame) -> Bool {
// Since we can currently not compare `Anchor<CGRect>` values
// without a Geometry reader, we return here `false` so that on
// every change on bounds an update is issued.
return false
}
}
Run Code Online (Sandbox Code Playgroud)
我们定义了一个Struct符合PreferenceKey协议来保存视图树首选项更改:
/// A `PreferenceKey` to provide View frame updates in a View tree
struct FramePreferenceKey: PreferenceKey {
typealias Value = [ViewFrame] // The list of view frame changes in a View tree.
static var defaultValue: [ViewFrame] = []
/// When traversing the view tree, Swift UI will use this function to collect all view frame changes.
static func reduce(value: inout [ViewFrame], nextValue: () -> [ViewFrame]) {
value.append(contentsOf: nextValue())
}
}
Run Code Online (Sandbox Code Playgroud)
现在我们可以定义ViewModifiers我提到的:
在其布局上更改视图报告:
这只是transformAnchorPreference通过一个处理程序向 View添加一个修饰符,该处理程序简单地构造一个ViewFrame具有当前帧Anchor值的实例并将其附加到 的当前值FramePreferenceKey:
/// Adds an Anchor preference to notify of frame changes
struct ProvideFrameChanges: ViewModifier {
var viewId : String
func body(content: Content) -> some View {
content
.transformAnchorPreference(key: FramePreferenceKey.self, value: .bounds) {
$0.append(ViewFrame(viewId: self.viewId, frameAnchor: $1))
}
}
}
extension View {
/// Adds an Anchor preference to notify of frame changes
/// - Parameter viewId: A `String` identifying the View
func provideFrameChanges(viewId : String) -> some View {
ModifiedContent(content: self, modifier: ProvideFrameChanges(viewId: viewId))
}
}
Run Code Online (Sandbox Code Playgroud)
为视图的子树上的帧更改提供更新处理程序:
这会onPreferenceChange向视图添加一个修饰符,其中框架锚点更改列表转换为CGRect视图坐标空间上的框架(),并报告为由视图 ID 键控的框架更新字典:
typealias ViewTreeFrameChanges = [String : CGRect]
/// Provides a block to handle internal View tree frame changes
/// for views using the `ProvideFrameChanges` in own coordinate space.
struct HandleViewTreeFrameChanges: ViewModifier {
/// The handler to process Frame changes on this views subtree.
/// `ViewTreeFrameChanges` is a dictionary where keys are string view ids
/// and values are the updated view frame (`CGRect`)
var handler : (ViewTreeFrameChanges)->Void
func body(content: Content) -> some View {
GeometryReader { contentGeometry in
content
.onPreferenceChange(FramePreferenceKey.self) {
self._updateViewTreeLayoutChanges($0, in: contentGeometry)
}
}
}
private func _updateViewTreeLayoutChanges(_ changes : [ViewFrame], in geometry : GeometryProxy) {
let pairs = changes.map({ ($0.viewId, geometry[$0.frameAnchor]) })
handler(Dictionary(uniqueKeysWithValues: pairs))
}
}
extension View {
/// Adds an Anchor preference to notify of frame changes
/// - Parameter viewId: A `String` identifying the View
func handleViewTreeFrameChanges(_ handler : @escaping (ViewTreeFrameChanges)->Void) -> some View {
ModifiedContent(content: self, modifier: HandleViewTreeFrameChanges(handler: handler))
}
}
Run Code Online (Sandbox Code Playgroud)
让我们使用它:
我将通过一个例子来说明用法:
在这里,我会得到的通知头查看内部框架的变化ScrollView。由于这个Header View是在ScrollView内容的顶部,所以上报的框架原点上的框架contentOffset变化相当于ScrollView
enum TestEnum : String, CaseIterable, Identifiable {
case one, two, three, four, five, six, seven, eight, nine, ten
var id: String {
rawValue
}
}
struct TestView: View {
private let _listHeaderViewId = "testView_ListHeader"
var body: some View {
ScrollView {
// Header View
Text("This is some Header")
.provideFrameChanges(viewId: self._listHeaderViewId)
// List of test values
ForEach(TestEnum.allCases) {
Text($0.rawValue)
.padding(60)
}
}
.handleViewTreeFrameChanges {
self._updateViewTreeLayoutChanges($0)
}
}
private func _updateViewTreeLayoutChanges(_ changes : ViewTreeFrameChanges) {
print(changes)
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1730 次 |
| 最近记录: |