如何避免在滑动 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
我创建了一个 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