首页 > 解决方案 > 滚动到顶部时,UIScrollView 出现故障和抖动

问题描述

我有一个UIViewController包含一个UICollectionView和一个UIView作为视图标题的。

我想在视图滚动时折叠标题。我目前正在通过在变量中捕获我的标题的顶部锚点并使用scrollViewDidScroll来设置constant该值来执行此操作。

  func scrollViewDidScroll(_ scrollView: UIScrollView) {
    headerTopAnchor.constant = max(-headerView.frame.height, -scrollView.contentOffset.y)
  }

这是有效的,因为集合视图向上滚动,标题滚动到屏幕外,反之亦然。

但是,如果集合视图中的内容不太适合 - 如果只有半个左右的单元格滚动到屏幕外,则会出现奇怪的颤抖行为。

如果我向其中添加打印语句,scrollViewDidScroll我可以看到集合视图过度滚动了少量,这导致顶部锚点多次少量更改

  func scrollViewDidScroll(_ scrollView: UIScrollView) {
    print(max(-headerView.frame.height, -scrollView.contentOffset.y))
    headerTopAnchor.constant = max(-headerView.frame.height, -scrollView.contentOffset.y)
  }

我怎样才能防止这种行为?

我已经包含了一个应该演示问题的视图控制器 -

final class TestViewController: UIViewController {

  private let headerView: UIView = {
    let view = UIView(frame: .zero)
    view.translatesAutoresizingMaskIntoConstraints = false
    view.backgroundColor = .systemTeal
    return view
  }()

  private(set) lazy var collectionView: UICollectionView = {
    let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
    collectionView.translatesAutoresizingMaskIntoConstraints = false
    collectionView.backgroundColor = .clear
    collectionView.dataSource = self
    collectionView.delegate = self
    collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "UICollectionViewCell")
    return collectionView
  }()

  private lazy var headerTopAnchor = NSLayoutConstraint()

  override func viewDidLoad() {
    super.viewDidLoad()
    view.backgroundColor = .systemGray
    [headerView, collectionView].forEach(view.addSubview(_:))
    NSLayoutConstraint.activate([
      headerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
      headerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
      headerView.heightAnchor.constraint(equalToConstant: 180),

      collectionView.topAnchor.constraint(equalTo: headerView.bottomAnchor),
      collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
      collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
      collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
    ])

    headerTopAnchor = headerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor)
    headerTopAnchor.isActive = true
  }
}

extension TestViewController: UICollectionViewDataSource {
  func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    // return 300 // no problem
    return 10 // problem :(
  }

  func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "UICollectionViewCell", for: indexPath)
    cell.backgroundColor = indexPath.item % 2 == 0 ? .darkGray : .lightGray
    return cell
  }
}

extension TestViewController: UICollectionViewDelegateFlowLayout {
  func scrollViewDidScroll(_ scrollView: UIScrollView) {
    print(max(-headerView.frame.height, -scrollView.contentOffset.y))
    headerTopAnchor.constant = max(-headerView.frame.height, -scrollView.contentOffset.y)
  }

  func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    return .init(width: collectionView.frame.width, height: 60)
  }
}

标签: swiftuicollectionviewuiscrollviewautolayout

解决方案


似乎UIScrollView有一个默认bounces设置为的属性。true

从文档中:

如果此属性的值为 true,则滚动视图在遇到内容边界时会弹跳。在视觉上弹跳表示滚动已到达内容的边缘。如果该值为 false,则滚动立即在内容边界处停止而不弹跳。默认值是true。

这解释了在应用动画的同时在到达边界时被顶部约束纠正时的颤抖行为。

我设置collectionView.bounces = falseviewDidLoad它,它现在按预期工作。


推荐阅读