如何避免在滑动 collectionView 时重复调用 scrollViewDidScroll() 方法?

How to avoid a double call of scrollViewDidScroll() method while swiping the collectionView?

我创建了一个 Collection View 用户入职培训,其中包含 5 个单元格(页面)。

Collection View 有一个 UIPageControl 显示用户当前的活动页面和 2 UIButtons(上一个和下一个)如果用户不这样做则需要手动滚动页面想刷卡

以下是我在用户点击时管理按钮 IBAction 的方式:

@IBAction func prevButtonClicked(_ sender: UIButton) {
    if currentPage != 0 {
        currentPage -= 1
        let indexPath = IndexPath(item: currentPage, section: 0)
        collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
    }
}

@IBAction func nextButtonClicked(_ sender: UIButton) {
    if currentPage == slides.count - 1 {
        //hide onboarding
    } else {
        currentPage += 1
        let indexPath = IndexPath(item: currentPage, section: 0)
        collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
    }
}

此外,如果用户滑动页面而不是点击按钮,我会使用 scrollViewDidScroll() 方法来更新 UIPageControl 点:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let visibleRectangle = CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size)
    let visiblePoint = CGPoint(x: visibleRectangle.midX, y: visibleRectangle.midY)
    currentPage = collectionView.indexPathForItem(at: visiblePoint)?.row ?? 0
}

currentPage 是计算得到的 属性:

private var currentPage = 0 {
    didSet {
        pageControl.currentPage = currentPage
        currentPage == 0 ? hidePreviousButton() : showPreviousButton()
    }
}

我有一个问题:点击按钮时我强制 collectionView 滚动并更新 currentPage,因此 scrollViewDidScroll 调用并再次 currentPage 更新。

因此,当我点击按钮时,我可以看到 UIPageControl 点和 backButton 闪烁,因为代码运行了两次:

    didSet {
        pageControl.currentPage = currentPage
        currentPage == 0 ? hidePreviousButton() : showPreviousButton()
    }

这是有问题的 GIF:GIF

如何避免在点击按钮时重复调用 scrollViewDidScroll

可以为每个collectionView设置tag,在scrollViewDidScroll中查看scrollView的tag。

为您的 OnboardingViewController 添加一个 Bool 变量:

var programmedScroll: Bool = false

然后,当点击上一个或下一个按钮时,而不是:

@IBAction func prevButtonPressed(_ sender: UIButton) {
    if currentPage != 0 {
        currentPage -= 1
        let indexPath = IndexPath(item: currentPage, section: 0)
        collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
    }
}

这样做:

@IBAction func prevButtonPressed(_ sender: UIButton) {
    if currentPage != 0 {
        currentPage -= 1
        let indexPath = IndexPath(item: currentPage, section: 0)
        // instead of this
        //collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
        self.programmedScroll = true
        UIView.animate(withDuration: 0.3, animations: {
            self.collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: false)
        }, completion: { _ in
            self.programmedScroll = false
        })
    }
}

现在您的 scrollViewDidScroll 不会在该动画期间被调用。


编辑

scrollViewDidScroll实施中:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    if !programmedScroll {
        let visibleRectangle = CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size)
        let visiblePoint = CGPoint(x: visibleRectangle.midX, y: visibleRectangle.midY)
        currentPage = collectionView.indexPathForItem(at: visiblePoint)?.row ?? 0
    }
}

编辑 2

使用上述方法产生了 less-than-acceptable 滚动效果,因为 UICollectionView 仅呈现 将显示的单元格

当使用 animated: false 告诉集合视图 .scrollToItem 时,集合视图立即放弃单元格的渲染,该单元格将 不再可见

因此,我们将采用相同的方法,但在 Next / Prev 按钮调用 .scrollToItem 后找到另一种方法来“re-enable”scrollViewDidScroll 代码。

在 prev/next 中,我们仍然设置 self.programmedScroll = true,但我们使用 built-in 动画代替动画块:

@IBAction func prevButtonPressed(_ sender: UIButton) {
    if currentPage != 0 {
        currentPage -= 1
        let indexPath = IndexPath(item: currentPage, section: 0)
        // disable scrollViewDidScroll code execution
        self.programmedScroll = true
        collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
    }
}

@IBAction func nextButtonPressed(_ sender: UIButton) {
    if currentPage == slides.count - 1 {
        //hide onboarding
    } else {
        currentPage += 1
        let indexPath = IndexPath(item: currentPage, section: 0)
        // disable scrollViewDidScroll code execution
        self.programmedScroll = true
        collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
    }
}

然后我们需要“re-enable”代码来在拖动时更改单元格之间的页面控制点mid-way,所以我们将实现:

func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
    // re-enable execution of scrollViewDidScroll code
    programmedScroll = false
}

应该可以了。我在以下位置更新了回购协议:https://github.com/DonMag/TestCollectionViewOnboarding