带有内容框架更新功能的SwiftUI ScrollView

时间:2019-10-16 14:55:03

标签: ios macos scrollview swiftui contentoffset

我想拥有一个ScrollView,您可以在其中了解用户滚动时内容框架的变化(类似于UIKit didScroll中的UIScrollView委托)。

这样,您便可以根据滚动行为执行布局更改。

2 个答案:

答案 0 :(得分:1)

通过使用 View偏好设置作为在View层次结构中向上游通知布局信息的方法,我设法提供了一个很好的解决方案。

要详细了解查看首选项的工作原理,我建议您3 articles series阅读此kontiki主题

对于我的解决方案,我实现了两个ViewModifiers:一个使用 anchor偏好设置对视图进行布局更改视图查看,第二个允许View处理更新子树视图上的框架。

为此,我们首先定义一个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
    }
}

,我们定义了一个符合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())
    }
}

现在我们可以定义我提到的ViewModifiers

制作视图报告,以更改其布局

这只是使用处理程序向视图中添加transformAnchorPreference修饰符,该处理程序仅构造一个具有当前帧ViewFrame值的Anchor实例并将其附加到{{1 }}:

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)) } } 修饰符,其中框架锚更改列表在视图的坐标空间上转换为框架(onPreferenceChange),并报告为由框架键入的框架更新字典查看ID:

CGRect

让我们使用它:

我将通过一个示例来说明用法:

在这里,我会收到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)) } } Header View 框架更改的通知。由于此 Header View (页眉视图)位于ScrollView内容的顶部,因此,报告的帧原点上的帧更改等效于ScrollView < / p>

contentOffset

答案 1 :(得分:0)

有一个很好的解决方案,Soroush Khanlou已经发布了要点,所以我不会将其复制粘贴。您可以找到它here,是的...很遗憾,它还不是框架的一部分!