在UICollectionViewController中向上滚动时随机"vertical jumps"



我有一个UIViewController,它包括一个UIPageViewController,而它本身也包括一个UI CollectionViewController。

在UIPageViewController上方:一个UIView(名为pagerView(,它根据UICollectionViewController的垂直滚动偏移量向上或向下移动。

最后,UIPageViewController的顶部锚点被约束为pagerView的底部锚点。

问题是向上滚动不是连续的,UICollectionViewController有时会"向上滚动";跳跃";(可以是几百点。(

要复制:滚动到底部(第9行(,然后开始向上滚动,看看它是如何跳到第7行的。

UIViewController的来源:

import UIKit
protocol ViewControllerChildDelegate: AnyObject {
func childScrollViewWillBeginDragging(with offset: CGFloat)
func childScrollViewDidScroll(to offset: CGFloat)
}
class ViewController: UIViewController {
private lazy var pageViewController: UIPageViewController = {
let viewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: [:])
viewController.delegate = self
viewController.dataSource = self
return viewController
}()

private lazy var pagerView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .brown
return view
}()

private let pagerViewHeight: CGFloat = 44
private var lastContentOffset: CGFloat = 0

lazy var pagerViewTopAnchorConstraint: NSLayoutConstraint = {
return pagerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)
}()

override func viewDidLoad() {
super.viewDidLoad()

title = "xxx"
view.addSubview(pagerView)

addChild(pageViewController)
view.addSubview(pageViewController.view)
pageViewController.didMove(toParent: self)
pageViewController.view.translatesAutoresizingMaskIntoConstraints = false

NSLayoutConstraint.activate([
pagerViewTopAnchorConstraint,
pagerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
pagerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
pagerView.heightAnchor.constraint(equalToConstant: pagerViewHeight),

pageViewController.view.topAnchor.constraint(equalTo: pagerView.bottomAnchor),
pageViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
pageViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
pageViewController.view.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
])

let viewController = CustomViewController()
viewController.delegate = self

pageViewController.setViewControllers(
[viewController],
direction: .forward,
animated: false,
completion: nil
)
}
}
extension ViewController: UIPageViewControllerDataSource, UIPageViewControllerDelegate {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
return nil
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
return nil
}

func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
}
}
extension ViewController: ViewControllerChildDelegate {
func childScrollViewWillBeginDragging(with offset: CGFloat) {
lastContentOffset = offset
}

func childScrollViewDidScroll(to offset: CGFloat) {
if lastContentOffset > offset {
view.layoutIfNeeded()
pagerViewTopAnchorConstraint.constant = 0
UIView.animate(withDuration: 0.3, animations: { [weak self] in
self?.view.layoutIfNeeded()
})
} else if lastContentOffset < offset {
view.layoutIfNeeded()
pagerViewTopAnchorConstraint.constant = -pagerViewHeight
UIView.animate(withDuration: 0.3, animations: { [weak self] in
self?.view.layoutIfNeeded()
})
}
}
}

整个代码可以从这个要点中查看。还有一个XCode项目可以下载。

还有:一段演示问题的视频。

在花了一些时间研究您的项目后,我还没有找到解决方案,但我相信我已经找到了可以为您提供解决方案的方法。

以下是我的观察结果:

  1. 当您从CustomViewController向上/向下滚动collectionView时,您正在使用通过ViewControllerChildDelegate更新的滚动偏移量来调整pagerViewTopAnchorConstraint

  2. 调整此约束也会调整pageViewController的高度,从而调整CustomViewControllercollectionView高度

  3. 根据当前偏移量的不同,调整collectionView的高度可能会导致contentOffset发生跳跃。以下是解释这一点的答案:https://stackoverflow.com/a/21787754/9293498

    为了核实这一点,你可以评论你更新pagerViewTopAnchorConstraint的部分,你会注意到这样做会消除"跳跃">

  4. 为了进一步验证情况是否确实如此,我创建了两个KVO观察器:一个用于collectionView的contentSize,另一个用于collectionView的contentOffset,如下所示:

    contentSizeObserver = collectionView.observe(.contentSize) { [weak self] collectionView, _ in
    //Ignore the values on "Found you!" checks, as its relative to my device
    if collectionView.contentSize.height < 3000 {
    print("Found you!")
    }
    print("CONTENT SIZE SET: ", collectionView.contentSize)
    }
    contentOffsetObserver = collectionView.observe(.contentOffset) { [weak self] collectionView, _ in
    //Ignore the values on "Found you!" checks, as its relative to my device
    if collectionView.contentOffset.y < 1460 {
    print("Found you!")
    }
    print("CONTENTOFFSET SET: ", collectionView.contentOffset)
    }
    

    我注意到我可以根据我的"跳跃"中的值用断点捕捉"跳跃";找到你了"检查。基本上,当你到达第9行并试图向上滚动时,每当"跳跃"即将发生时,就会发生两件事:

    • 您的collectionViewcontentOffset将根据collectionView.contentSize.height - collectionView.frame.height的值进行更新,我猜这是由于帧更改引起的
    • 出于某种原因,你的collectionViewcontentSize的高度也大幅下降/上升(我说的是1000(,这与你的pager的高度(44(相去甚远

    这些添加在一起的更改就是我们所注意到的"跳转",它在帧更改过程中经过一些递归调用后自行排序。

查看所提供的视频,问题似乎是由于可重用单元的实现。

当你第一次启动应用程序时,会有n个单元格(在你的情况下是4个(被创建为n个实例。此时,n个实例在内存中。当你向下滚动时,你不会看到任何问题,因为屏幕上的单元格数量减少了,但你仍然没有在现场创建任何单元格。另一方面,当向上滚动时,你有n=2个单元格,而当滚动时,突然需要更多的单元格,这会在原地创建缺失的单元格,并导致滞后跳跃。

在典型的表视图中,可见单元格的数量是恒定的,因此您将始终在内存中缓存相同的数量,并在超出屏幕时重用它。

总结如下:
从n到n->没有问题,您可能缓存n+1,所以您总是有从n到k(k<n(->没有问题,当您从n滚动到k(k>n(->时,您将减少缓存的单元格;导致滞后,你必须在现场创建可重复使用的单元格,或者在时重复使用屏幕上已经显示的内容

您可以通过简单地将collectionView.dequeueReusableCell替换为您自己的数组来解决此问题,在该数组中存储启动的单元格并重用它们。这当然需要特殊处理,但我相信它会解决你的问题

希望这个解释有帮助:(

最新更新