ios targetContentOffsetForProposedContentOffset:withScrollingVelocity 没有子类化 UICollectionViewFlowLayout
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/13492037/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
targetContentOffsetForProposedContentOffset:withScrollingVelocity without subclassing UICollectionViewFlowLayout
提问by Fogmeister
I've got a very simple collectionView in my app (just a single row of square thumbnail images).
我的应用程序中有一个非常简单的 collectionView(只有一行方形缩略图)。
I'd like to intercept the scrolling so that the offset always leaves a full image at the left side. At the moment it scrolls to wherever and will leave cut off images.
我想拦截滚动,以便偏移量始终在左侧留下完整的图像。目前它会滚动到任何地方,并会留下截断的图像。
Anyway, I know I need to use the function
无论如何,我知道我需要使用该功能
- (CGPoint)targetContentOffsetForProposedContentOffset:withScrollingVelocity
to do this but I'm just using a standard UICollectionViewFlowLayout
. I'm not subclassing it.
要做到这一点,但我只是使用标准的UICollectionViewFlowLayout
. 我不是子类化它。
Is there any way of intercepting this without subclassing UICollectionViewFlowLayout
?
有没有办法在没有子类化的情况下拦截它UICollectionViewFlowLayout
?
Thanks
谢谢
回答by Fogmeister
OK, answer is no, there is no way to do this without subclassing UICollectionViewFlowLayout.
好吧,答案是否定的,没有子类化 UICollectionViewFlowLayout 就没有办法做到这一点。
However, subclassing it is incredibly easy for anyone who is reading this in the future.
但是,对于将来阅读本文的任何人来说,将其子类化是非常容易的。
First I set up the subclass call MyCollectionViewFlowLayout
and then in interface builder I changed the collection view layout to Custom and selected my flow layout subclass.
首先我设置了子类调用MyCollectionViewFlowLayout
,然后在界面构建器中我将集合视图布局更改为自定义并选择了我的流布局子类。
Because you're doing it this way you can't specify items sizes, etc... in IB so in MyCollectionViewFlowLayout.m I have this...
因为你是这样做的,所以你不能在 IB 中指定项目大小等......所以在 MyCollectionViewFlowLayout.m 我有这个......
- (void)awakeFromNib
{
self.itemSize = CGSizeMake(75.0, 75.0);
self.minimumInteritemSpacing = 10.0;
self.minimumLineSpacing = 10.0;
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.sectionInset = UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0);
}
This sets up all the sizes for me and the scroll direction.
这为我设置了所有尺寸和滚动方向。
Then ...
然后 ...
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
CGFloat offsetAdjustment = MAXFLOAT;
CGFloat horizontalOffset = proposedContentOffset.x + 5;
CGRect targetRect = CGRectMake(proposedContentOffset.x, 0, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);
NSArray *array = [super layoutAttributesForElementsInRect:targetRect];
for (UICollectionViewLayoutAttributes *layoutAttributes in array) {
CGFloat itemOffset = layoutAttributes.frame.origin.x;
if (ABS(itemOffset - horizontalOffset) < ABS(offsetAdjustment)) {
offsetAdjustment = itemOffset - horizontalOffset;
}
}
return CGPointMake(proposedContentOffset.x + offsetAdjustment, proposedContentOffset.y);
}
This ensures that the scrolling ends with a margin of 5.0 on the left hand edge.
这确保滚动结束时左边缘的边距为 5.0。
That's all I needed to do. I didn't need to set the flow layout in code at all.
这就是我需要做的。我根本不需要在代码中设置流布局。
回答by DarthMike
Dan's solution is flawed. It does not handle user flicking well. The cases when user flicks fast and scroll did not move so much, have animation glitches.
丹的解决方案是有缺陷的。它不能很好地处理用户轻弹。用户快速滑动和滚动没有那么多移动的情况下,会出现动画故障。
My proposed alternative implementation has the same pagination as proposed before, but handles user flicking between pages.
我提议的替代实现与之前提议的分页相同,但处理用户在页面之间轻弹。
#pragma mark - Pagination
- (CGFloat)pageWidth {
return self.itemSize.width + self.minimumLineSpacing;
}
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
CGFloat rawPageValue = self.collectionView.contentOffset.x / self.pageWidth;
CGFloat currentPage = (velocity.x > 0.0) ? floor(rawPageValue) : ceil(rawPageValue);
CGFloat nextPage = (velocity.x > 0.0) ? ceil(rawPageValue) : floor(rawPageValue);
BOOL pannedLessThanAPage = fabs(1 + currentPage - rawPageValue) > 0.5;
BOOL flicked = fabs(velocity.x) > [self flickVelocity];
if (pannedLessThanAPage && flicked) {
proposedContentOffset.x = nextPage * self.pageWidth;
} else {
proposedContentOffset.x = round(rawPageValue) * self.pageWidth;
}
return proposedContentOffset;
}
- (CGFloat)flickVelocity {
return 0.3;
}
回答by André Abreu
Swift version of the accepted answer.
已接受答案的 Swift 版本。
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
var offsetAdjustment = CGFloat.greatestFiniteMagnitude
let horizontalOffset = proposedContentOffset.x
let targetRect = CGRect(origin: CGPoint(x: proposedContentOffset.x, y: 0), size: self.collectionView!.bounds.size)
for layoutAttributes in super.layoutAttributesForElements(in: targetRect)! {
let itemOffset = layoutAttributes.frame.origin.x
if (abs(itemOffset - horizontalOffset) < abs(offsetAdjustment)) {
offsetAdjustment = itemOffset - horizontalOffset
}
}
return CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y)
}
Valid for Swift 5.
对Swift 5有效。
回答by Dan Abramov
While this answerhas been a great help to me, there is a noticeable flicker when you swipe fast on a small distance. It's much easier to reproduce it on the device.
虽然这个答案对我有很大帮助,但是当您在一小段距离内快速滑动时,会出现明显的闪烁。在设备上重现它要容易得多。
I found that this always happens when collectionView.contentOffset.x - proposedContentOffset.x
and velocity.x
have different sings.
我发现,这总是发生时collectionView.contentOffset.x - proposedContentOffset.x
,并velocity.x
有不同的歌唱。
My solution was to ensure that proposedContentOffset
is more than contentOffset.x
if velocity is positive, and less if it is negative. It's in C# but should be fairly simple to translate to Objective C:
我的解决方案是确保proposedContentOffset
比contentOffset.x
速度为正时更多,如果速度为负则更少。它在 C# 中,但转换为目标 C 应该相当简单:
public override PointF TargetContentOffset (PointF proposedContentOffset, PointF scrollingVelocity)
{
/* Determine closest edge */
float offSetAdjustment = float.MaxValue;
float horizontalCenter = (float) (proposedContentOffset.X + (this.CollectionView.Bounds.Size.Width / 2.0));
RectangleF targetRect = new RectangleF (proposedContentOffset.X, 0.0f, this.CollectionView.Bounds.Size.Width, this.CollectionView.Bounds.Size.Height);
var array = base.LayoutAttributesForElementsInRect (targetRect);
foreach (var layoutAttributes in array) {
float itemHorizontalCenter = layoutAttributes.Center.X;
if (Math.Abs (itemHorizontalCenter - horizontalCenter) < Math.Abs (offSetAdjustment)) {
offSetAdjustment = itemHorizontalCenter - horizontalCenter;
}
}
float nextOffset = proposedContentOffset.X + offSetAdjustment;
/*
* ... unless we end up having positive speed
* while moving left or negative speed while moving right.
* This will cause flicker so we resort to finding next page
* in the direction of velocity and use it.
*/
do {
proposedContentOffset.X = nextOffset;
float deltaX = proposedContentOffset.X - CollectionView.ContentOffset.X;
float velX = scrollingVelocity.X;
// If their signs are same, or if either is zero, go ahead
if (Math.Sign (deltaX) * Math.Sign (velX) != -1)
break;
// Otherwise, look for the closest page in the right direction
nextOffset += Math.Sign (scrollingVelocity.X) * SnapStep;
} while (IsValidOffset (nextOffset));
return proposedContentOffset;
}
bool IsValidOffset (float offset)
{
return (offset >= MinContentOffset && offset <= MaxContentOffset);
}
This code is using MinContentOffset
, MaxContentOffset
and SnapStep
which should be trivial for you to define. In my case they turned out to be
该代码使用MinContentOffset
,MaxContentOffset
而SnapStep
这应该是平凡的你定义。就我而言,他们原来是
float MinContentOffset {
get { return -CollectionView.ContentInset.Left; }
}
float MaxContentOffset {
get { return MinContentOffset + CollectionView.ContentSize.Width - ItemSize.Width; }
}
float SnapStep {
get { return ItemSize.Width + MinimumLineSpacing; }
}
回答by Pion
After long testing I found solution to snap to center with custom cell width (each cell has diff. width) which fixes the flickering. Feel free to improve the script.
经过长时间的测试,我找到了使用自定义单元格宽度(每个单元格都有不同的宽度)对齐中心的解决方案,它修复了闪烁。随意改进脚本。
- (CGPoint) targetContentOffsetForProposedContentOffset: (CGPoint) proposedContentOffset withScrollingVelocity: (CGPoint)velocity
{
CGFloat offSetAdjustment = MAXFLOAT;
CGFloat horizontalCenter = (CGFloat) (proposedContentOffset.x + (self.collectionView.bounds.size.width / 2.0));
//setting fastPaging property to NO allows to stop at page on screen (I have pages lees, than self.collectionView.bounds.size.width)
CGRect targetRect = CGRectMake(self.fastPaging ? proposedContentOffset.x : self.collectionView.contentOffset.x,
0.0,
self.collectionView.bounds.size.width,
self.collectionView.bounds.size.height);
NSArray *attributes = [self layoutAttributesForElementsInRect:targetRect];
NSPredicate *cellAttributesPredicate = [NSPredicate predicateWithBlock: ^BOOL(UICollectionViewLayoutAttributes * _Nonnull evaluatedObject,
NSDictionary<NSString *,id> * _Nullable bindings)
{
return (evaluatedObject.representedElementCategory == UICollectionElementCategoryCell);
}];
NSArray *cellAttributes = [attributes filteredArrayUsingPredicate: cellAttributesPredicate];
UICollectionViewLayoutAttributes *currentAttributes;
for (UICollectionViewLayoutAttributes *layoutAttributes in cellAttributes)
{
CGFloat itemHorizontalCenter = layoutAttributes.center.x;
if (ABS(itemHorizontalCenter - horizontalCenter) < ABS(offSetAdjustment))
{
currentAttributes = layoutAttributes;
offSetAdjustment = itemHorizontalCenter - horizontalCenter;
}
}
CGFloat nextOffset = proposedContentOffset.x + offSetAdjustment;
proposedContentOffset.x = nextOffset;
CGFloat deltaX = proposedContentOffset.x - self.collectionView.contentOffset.x;
CGFloat velX = velocity.x;
// detection form gist.github.com/rkeniger/7687301
// based on http://stackoverflow.com/a/14291208/740949
if (fabs(deltaX) <= FLT_EPSILON || fabs(velX) <= FLT_EPSILON || (velX > 0.0 && deltaX > 0.0) || (velX < 0.0 && deltaX < 0.0))
{
}
else if (velocity.x > 0.0)
{
// revert the array to get the cells from the right side, fixes not correct center on different size in some usecases
NSArray *revertedArray = [[array reverseObjectEnumerator] allObjects];
BOOL found = YES;
float proposedX = 0.0;
for (UICollectionViewLayoutAttributes *layoutAttributes in revertedArray)
{
if(layoutAttributes.representedElementCategory == UICollectionElementCategoryCell)
{
CGFloat itemHorizontalCenter = layoutAttributes.center.x;
if (itemHorizontalCenter > proposedContentOffset.x) {
found = YES;
proposedX = nextOffset + (currentAttributes.frame.size.width / 2) + (layoutAttributes.frame.size.width / 2);
} else {
break;
}
}
}
// dont set on unfound element
if (found) {
proposedContentOffset.x = proposedX;
}
}
else if (velocity.x < 0.0)
{
for (UICollectionViewLayoutAttributes *layoutAttributes in cellAttributes)
{
CGFloat itemHorizontalCenter = layoutAttributes.center.x;
if (itemHorizontalCenter > proposedContentOffset.x)
{
proposedContentOffset.x = nextOffset - ((currentAttributes.frame.size.width / 2) + (layoutAttributes.frame.size.width / 2));
break;
}
}
}
proposedContentOffset.y = 0.0;
return proposedContentOffset;
}
回答by JoniVR
Here's my implementation in Swift 5for verticalcell-based paging:
这是我在Swift 5 中实现的基于单元格的垂直分页:
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = self.collectionView else {
let latestOffset = super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
return latestOffset
}
// Page height used for estimating and calculating paging.
let pageHeight = self.itemSize.height + self.minimumLineSpacing
// Make an estimation of the current page position.
let approximatePage = collectionView.contentOffset.y/pageHeight
// Determine the current page based on velocity.
let currentPage = velocity.y == 0 ? round(approximatePage) : (velocity.y < 0.0 ? floor(approximatePage) : ceil(approximatePage))
// Create custom flickVelocity.
let flickVelocity = velocity.y * 0.3
// Check how many pages the user flicked, if <= 1 then flickedPages should return 0.
let flickedPages = (abs(round(flickVelocity)) <= 1) ? 0 : round(flickVelocity)
let newVerticalOffset = ((currentPage + flickedPages) * pageHeight) - collectionView.contentInset.top
return CGPoint(x: proposedContentOffset.x, y: newVerticalOffset)
}
Some notes:
一些注意事项:
- Doesn't glitch
- SET PAGING TO FALSE! (otherwise this won't work)
- Allows you to set your own flickvelocityeasily.
- If something is still not working after trying this, check if your
itemSize
actually matches the size of the item as that's often a problem, especially when usingcollectionView(_:layout:sizeForItemAt:)
, use a custom variable with the itemSize instead. - This works best when you set
self.collectionView.decelerationRate = UIScrollView.DecelerationRate.fast
.
- 不会出现故障
- 将分页设置为假!(否则这将不起作用)
- 允许您轻松设置自己的轻弹速度。
- 如果尝试此操作后仍然无法正常工作,请检查您的
itemSize
实际大小是否与项目的大小匹配,因为这通常是一个问题,尤其是在使用时collectionView(_:layout:sizeForItemAt:)
,请改用带有 itemSize 的自定义变量。 - 当您设置
self.collectionView.decelerationRate = UIScrollView.DecelerationRate.fast
.
Here's a horizontalversion (haven't tested it thoroughly so please forgive any mistakes):
这是一个横向版本(尚未彻底测试,因此请原谅任何错误):
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = self.collectionView else {
let latestOffset = super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
return latestOffset
}
// Page width used for estimating and calculating paging.
let pageWidth = self.itemSize.width + self.minimumInteritemSpacing
// Make an estimation of the current page position.
let approximatePage = collectionView.contentOffset.x/pageWidth
// Determine the current page based on velocity.
let currentPage = velocity.x == 0 ? round(approximatePage) : (velocity.x < 0.0 ? floor(approximatePage) : ceil(approximatePage))
// Create custom flickVelocity.
let flickVelocity = velocity.x * 0.3
// Check how many pages the user flicked, if <= 1 then flickedPages should return 0.
let flickedPages = (abs(round(flickVelocity)) <= 1) ? 0 : round(flickVelocity)
// Calculate newHorizontalOffset.
let newHorizontalOffset = ((currentPage + flickedPages) * pageWidth) - collectionView.contentInset.left
return CGPoint(x: newHorizontalOffset, y: proposedContentOffset.y)
}
This code is based on the code I use in my personal project, you can check it out hereby downloading it and running the Example target.
此代码基于我在个人项目中使用的代码,您可以在此处下载并运行 Example 目标来查看它。
回答by katopz
refer to this answer by Dan Abramovhere's Swift version
参考Dan Abramov 的这个答案,这里是 Swift 版本
override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
var _proposedContentOffset = CGPoint(x: proposedContentOffset.x, y: proposedContentOffset.y)
var offSetAdjustment: CGFloat = CGFloat.max
let horizontalCenter: CGFloat = CGFloat(proposedContentOffset.x + (self.collectionView!.bounds.size.width / 2.0))
let targetRect = CGRect(x: proposedContentOffset.x, y: 0.0, width: self.collectionView!.bounds.size.width, height: self.collectionView!.bounds.size.height)
let array: [UICollectionViewLayoutAttributes] = self.layoutAttributesForElementsInRect(targetRect)! as [UICollectionViewLayoutAttributes]
for layoutAttributes: UICollectionViewLayoutAttributes in array {
if (layoutAttributes.representedElementCategory == UICollectionElementCategory.Cell) {
let itemHorizontalCenter: CGFloat = layoutAttributes.center.x
if (abs(itemHorizontalCenter - horizontalCenter) < abs(offSetAdjustment)) {
offSetAdjustment = itemHorizontalCenter - horizontalCenter
}
}
}
var nextOffset: CGFloat = proposedContentOffset.x + offSetAdjustment
repeat {
_proposedContentOffset.x = nextOffset
let deltaX = proposedContentOffset.x - self.collectionView!.contentOffset.x
let velX = velocity.x
if (deltaX == 0.0 || velX == 0 || (velX > 0.0 && deltaX > 0.0) || (velX < 0.0 && deltaX < 0.0)) {
break
}
if (velocity.x > 0.0) {
nextOffset = nextOffset + self.snapStep()
} else if (velocity.x < 0.0) {
nextOffset = nextOffset - self.snapStep()
}
} while self.isValidOffset(nextOffset)
_proposedContentOffset.y = 0.0
return _proposedContentOffset
}
func isValidOffset(offset: CGFloat) -> Bool {
return (offset >= CGFloat(self.minContentOffset()) && offset <= CGFloat(self.maxContentOffset()))
}
func minContentOffset() -> CGFloat {
return -CGFloat(self.collectionView!.contentInset.left)
}
func maxContentOffset() -> CGFloat {
return CGFloat(self.minContentOffset() + self.collectionView!.contentSize.width - self.itemSize.width)
}
func snapStep() -> CGFloat {
return self.itemSize.width + self.minimumLineSpacing;
}
or gist here https://gist.github.com/katopz/8b04c783387f0c345cd9
回答by Oliver Pearmain
For anyone looking for a solution that...
对于任何正在寻找解决方案的人...
- DOES NOT GLITCH when the user performs a short fast scroll (i.e. it considers positive and negative scroll velocities)
- takes the
collectionView.contentInset
(and safeArea on iPhone X) into consideration - only considers thoes cells visible at the point of scrolling (for peformance)
- uses well named variables and comments
- is Swift 4
- 当用户执行短的快速滚动时不会出现故障(即它考虑正和负滚动速度)
- 考虑
collectionView.contentInset
(和 iPhone X 上的安全区域) - 只考虑滚动点可见的 thoes 单元格(为了性能)
- 使用命名良好的变量和注释
- 是斯威夫特 4
then please see below...
那么请看下面...
public class CarouselCollectionViewLayout: UICollectionViewFlowLayout {
override public func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = collectionView else {
return super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity)
}
// Identify the layoutAttributes of cells in the vicinity of where the scroll view will come to rest
let targetRect = CGRect(origin: proposedContentOffset, size: collectionView.bounds.size)
let visibleCellsLayoutAttributes = layoutAttributesForElements(in: targetRect)
// Translate those cell layoutAttributes into potential (candidate) scrollView offsets
let candidateOffsets: [CGFloat]? = visibleCellsLayoutAttributes?.map({ cellLayoutAttributes in
if #available(iOS 11.0, *) {
return cellLayoutAttributes.frame.origin.x - collectionView.contentInset.left - collectionView.safeAreaInsets.left - sectionInset.left
} else {
return cellLayoutAttributes.frame.origin.x - collectionView.contentInset.left - sectionInset.left
}
})
// Now we need to work out which one of the candidate offsets is the best one
let bestCandidateOffset: CGFloat
if velocity.x > 0 {
// If the scroll velocity was POSITIVE, then only consider cells/offsets to the RIGHT of the proposedContentOffset.x
// Of the cells/offsets to the right, the NEAREST is the `bestCandidate`
// If there is no nearestCandidateOffsetToLeft then we default to the RIGHT-MOST (last) of ALL the candidate cells/offsets
// (this handles the scenario where the user has scrolled beyond the last cell)
let candidateOffsetsToRight = candidateOffsets?.toRight(ofProposedOffset: proposedContentOffset.x)
let nearestCandidateOffsetToRight = candidateOffsetsToRight?.nearest(toProposedOffset: proposedContentOffset.x)
bestCandidateOffset = nearestCandidateOffsetToRight ?? candidateOffsets?.last ?? proposedContentOffset.x
}
else if velocity.x < 0 {
// If the scroll velocity was NEGATIVE, then only consider cells/offsets to the LEFT of the proposedContentOffset.x
// Of the cells/offsets to the left, the NEAREST is the `bestCandidate`
// If there is no nearestCandidateOffsetToLeft then we default to the LEFT-MOST (first) of ALL the candidate cells/offsets
// (this handles the scenario where the user has scrolled beyond the first cell)
let candidateOffsetsToLeft = candidateOffsets?.toLeft(ofProposedOffset: proposedContentOffset.x)
let nearestCandidateOffsetToLeft = candidateOffsetsToLeft?.nearest(toProposedOffset: proposedContentOffset.x)
bestCandidateOffset = nearestCandidateOffsetToLeft ?? candidateOffsets?.first ?? proposedContentOffset.x
}
else {
// If the scroll velocity was ZERO we consider all `candidate` cells (regarless of whether they are to the left OR right of the proposedContentOffset.x)
// The cell/offset that is the NEAREST is the `bestCandidate`
let nearestCandidateOffset = candidateOffsets?.nearest(toProposedOffset: proposedContentOffset.x)
bestCandidateOffset = nearestCandidateOffset ?? proposedContentOffset.x
}
return CGPoint(x: bestCandidateOffset, y: proposedContentOffset.y)
}
}
fileprivate extension Sequence where Iterator.Element == CGFloat {
func toLeft(ofProposedOffset proposedOffset: CGFloat) -> [CGFloat] {
return filter() { candidateOffset in
return candidateOffset < proposedOffset
}
}
func toRight(ofProposedOffset proposedOffset: CGFloat) -> [CGFloat] {
return filter() { candidateOffset in
return candidateOffset > proposedOffset
}
}
func nearest(toProposedOffset proposedOffset: CGFloat) -> CGFloat? {
guard let firstCandidateOffset = first(where: { _ in true }) else {
// If there are no elements in the Sequence, return nil
return nil
}
return reduce(firstCandidateOffset) { (bestCandidateOffset: CGFloat, candidateOffset: CGFloat) -> CGFloat in
let candidateOffsetDistanceFromProposed = fabs(candidateOffset - proposedOffset)
let bestCandidateOffsetDistancFromProposed = fabs(bestCandidateOffset - proposedOffset)
if candidateOffsetDistanceFromProposed < bestCandidateOffsetDistancFromProposed {
return candidateOffset
}
return bestCandidateOffset
}
}
}
回答by Scott Kaiser
Here is my Swift solution on a horizontally scrolling collection view. It's simple, sweet and avoids any flickering.
这是我在水平滚动集合视图上的 Swift 解决方案。它简单,甜美,避免任何闪烁。
override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = collectionView else { return proposedContentOffset }
let currentXOffset = collectionView.contentOffset.x
let nextXOffset = proposedContentOffset.x
let maxIndex = ceil(currentXOffset / pageWidth())
let minIndex = floor(currentXOffset / pageWidth())
var index: CGFloat = 0
if nextXOffset > currentXOffset {
index = maxIndex
} else {
index = minIndex
}
let xOffset = pageWidth() * index
let point = CGPointMake(xOffset, 0)
return point
}
func pageWidth() -> CGFloat {
return itemSize.width + minimumInteritemSpacing
}
回答by Anton Gaenko
I prefer to allow user flicking through several pages. So here is my version of targetContentOffsetForProposedContentOffset
(which based on DarthMike answer) for verticallayout.
我更喜欢允许用户快速浏览多个页面。所以这是我targetContentOffsetForProposedContentOffset
的垂直布局版本(基于 DarthMike 的回答)。
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity {
CGFloat approximatePage = self.collectionView.contentOffset.y / self.pageHeight;
CGFloat currentPage = (velocity.y < 0.0) ? floor(approximatePage) : ceil(approximatePage);
NSInteger flickedPages = ceil(velocity.y / self.flickVelocity);
if (flickedPages) {
proposedContentOffset.y = (currentPage + flickedPages) * self.pageHeight;
} else {
proposedContentOffset.y = currentPage * self.pageHeight;
}
return proposedContentOffset;
}
- (CGFloat)pageHeight {
return self.itemSize.height + self.minimumLineSpacing;
}
- (CGFloat)flickVelocity {
return 1.2;
}