UIScrollView滚动时更改contentInset

3

我正在尝试实现一个自定义顶部栏,其行为类似于iOS 11+的大标题导航栏,当向下滚动内容时,栏的大标题部分会折叠:

iOS navigation bar

我的需求是定制高度的导航栏,并且滚动时底部部分不会折叠。我已经成功实现了这个部分。

My custom bar

使用UIStackView实现了该条,同时添加了一些非必需的布局约束,但我认为其内部实现并不重要。最重要的是该条的高度与scrollview的顶部contentInset相关联。这些由scrollview的contentOffset在UIScrollViewDelegate.scrollViewDidScroll方法中驱动:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let topInset = (-scrollView.contentOffset.y).limitedBy(topBarHeightRange)

    // changes both contentInset and scrollIndicatorInsets
    adjustTopContentInset(topInset)

    // changes top bar height
    heightConstraint?.constant = topInset

    adjustSmallTitleAlpha()
}

topBarHeightRange 存储了导航栏高度的最小值和最大值。

我遇到的一个问题是,当用户停止滚动 scrollview 时,导航栏可能会处于半折叠状态。再次看一下期望的行为: iOS default bar details

内容偏移量被捕捉到更接近的紧凑或展开高度。我正在尝试在 UIScrollViewDelegate.scrollViewWillEndDragging 方法中实现相同的效果:

func scrollViewWillEndDragging(_ scrollView: UIScrollView,
                               withVelocity velocity: CGPoint,
                               targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    let targetY = targetContentOffset.pointee.y

    // snaps to a "closer" value
    let snappedTargetY = targetY.snappedTo([topBarHeightRange.lowerBound, topBarHeightRange.upperBound].map(-))

    targetContentOffset.pointee.y = snappedTargetY
    print("Snapped: \(targetY) -> \(snappedTargetY)")
}

效果远非完美: My custom bar details 当我查看打印输出时,它显示targetContentOffset被正确修改。然而,在应用程序中,视觉上内容偏移仅捕捉到紧凑高度,而不是扩展高度(您可以观察到大的“标题”标签最终被切成两半,而不是回到“扩展”位置)。
我怀疑这个问题与在用户滚动时更改contentInset.top有关,但我无法弄清楚如何修复此行为。
有点难以解释问题,所以希望GIF能够帮助。这是存储库:https://github.com/AleksanderMaj/ScrollView 有什么想法可以使scrollview/bar组合正确地捕捉紧凑/扩展高度吗?

UidynamicItem可能是您需要实现的。 - E.Coms
谢谢。我对UIDynamics只有非常模糊的了解。您能简要描述一下(至少在概念上)这样一个实现将如何工作吗? - Aleksander Maj
@AleksanderMaj 你有没有想出解决办法? - SAHM
1个回答

1
我查看了您的项目并喜欢您的实现。
在您的scrollViewWillEndDragging方法中,我提出了一个解决方案,通过在该方法的末尾添加以下代码:
    if abs(targetY) < abs(snappedTargetY) {
        scrollView.setContentOffset(CGPoint(x: 0, y: snappedTargetY), animated: true)
    }

基本上,如果滚动距离不足以隐藏大标题(如果targetY小于snappedTargetY,则会发生这种情况),则只需滚动到snappedTargetY的值以显示大标题。

目前似乎可以工作,但如果您遇到任何错误或发现改进方法,请告诉我。

整个scrollViewWillEndDragging方法:

func scrollViewWillEndDragging(_ scrollView: UIScrollView,
                               withVelocity velocity: CGPoint,
                               targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    let targetY = targetContentOffset.pointee.y

    // snaps to a "closer" value
    let snappedTargetY = targetY.snappedTo([topBarHeightRange.lowerBound, topBarHeightRange.upperBound].map(-))

    targetContentOffset.pointee.y = snappedTargetY

    if abs(targetY) < abs(snappedTargetY) {
        scrollView.setContentOffset(CGPoint(x: 0, y: snappedTargetY), animated: true)
    }

    print("Snapped: \(targetY) -> \(snappedTargetY)")
}

嗯,这是一个有趣的想法。我希望有一个更优雅的解决方案,但你的方法可能行得通。还有一个问题需要解决:当速度不接近零时,进度条仍然会卡在半折叠状态。这表示当滚动视图被快速拖动且仍有很多减速要完成时,scrollViewWillEndDragging方法被调用的情况。我一定会努力完善它,并在某个时候发布更新。 - Aleksander Maj
谢谢,是的,这只是一个简单的修复而不是一个具体的解决方案,我会在有时间的时候再仔细看看,如果你也想到了更好的解决方案,请告诉我。新年快乐 :)) - emrepun

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接