ios 水平分页滚动视图中缺少 UICollectionView 对齐逻辑
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/13228600/
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
UICollectionView align logic missing in horizontal paging scrollview
提问by Devfly
I've got a UICollectionView
, which works ok, until I start scrolling.
Here some pics first:
我有一个UICollectionView
,它工作正常,直到我开始滚动。先来几张图:
As you can see it's great. As I start scrolling (paging enabled) the first one goes a bit offscreen:
正如你所看到的,它很棒。当我开始滚动(启用分页)时,第一个有点离屏:
This is the problem. Originaly my view have 3 views and I want to scroll and show 3 views only. But as it scrolls (paging enabled) it hides a little bit of the first view and show little bit of the next first view from the next page.
这就是问题。原来我的视图有 3 个视图,我只想滚动并显示 3 个视图。但是当它滚动(启用分页)时,它隐藏了第一个视图的一点点,并从下一页显示下一个第一个视图的一点点。
And here is a video, because it's kinda hard to explain: Video of the problem (Dropbox)
这是一个视频,因为它有点难以解释: 问题视频(Dropbox)
Here is a picture of my UICollectionView
settings:
这是我的UICollectionView
设置图片:
It's going to be great if someone can help!
如果有人可以提供帮助,那就太好了!
回答by Ji Fang
The fundamental issue is Flow Layout is not designed to support the paging. To achieve the paging effect, you will have to sacrifice the space between cells. And carefully calculate the cells frame and make it can be divided by the collection view frame without remainders. I will explain the reason.
根本问题是 Flow Layout 不是为了支持分页而设计的。为了达到分页效果,你将不得不牺牲单元格之间的空间。并仔细计算单元格框架,使其可以被集合视图框架无余数分割。我会解释原因。
Saying the following layout is what you wanted.
说以下布局是您想要的。
Notice, the most left margin (green) is not part of the cell spacing. It is determined by the flow layout section inset. Since flow layout doesn't support heterogeneous spacing value. It is not a trivial task.
请注意,最左边距(绿色)不是单元格间距的一部分。它由流布局部分插图确定。由于流布局不支持异构间距值。这不是一项微不足道的任务。
Therefore, after setting the spacing and inset. The following layout is what you will get.
因此,在设置间距和插图后。以下布局是您将得到的。
After scroll to next page. Your cells are obviously not aligned as what you expected.
滚动到下一页后。您的单元格显然没有按照您的预期对齐。
Making the cell spacing 0 can solve this issue. However, it limits your design if you want the extra margin on the page, especially if the margin is different from the cell spacing. It also requires the view frame must be divisible by the cell frame. Sometimes, it is a pain if your view frame is not fixed (considering the rotation case).
将单元格间距设为 0 可以解决这个问题。但是,如果您希望页面上有额外的边距,它会限制您的设计,尤其是当边距与单元格间距不同时。它还要求视图框架必须可被单元框架整除。有时,如果您的视图框架不固定(考虑旋转情况)会很痛苦。
The real solution is to subclass UICollectionViewFlowLayout and override following methods
真正的解决方案是继承 UICollectionViewFlowLayout 并覆盖以下方法
- (CGSize)collectionViewContentSize
{
// Only support single section for now.
// Only support Horizontal scroll
NSUInteger count = [self.collectionView.dataSource collectionView:self.collectionView
numberOfItemsInSection:0];
CGSize canvasSize = self.collectionView.frame.size;
CGSize contentSize = canvasSize;
if (self.scrollDirection == UICollectionViewScrollDirectionHorizontal)
{
NSUInteger rowCount = (canvasSize.height - self.itemSize.height) / (self.itemSize.height + self.minimumInteritemSpacing) + 1;
NSUInteger columnCount = (canvasSize.width - self.itemSize.width) / (self.itemSize.width + self.minimumLineSpacing) + 1;
NSUInteger page = ceilf((CGFloat)count / (CGFloat)(rowCount * columnCount));
contentSize.width = page * canvasSize.width;
}
return contentSize;
}
- (CGRect)frameForItemAtIndexPath:(NSIndexPath *)indexPath
{
CGSize canvasSize = self.collectionView.frame.size;
NSUInteger rowCount = (canvasSize.height - self.itemSize.height) / (self.itemSize.height + self.minimumInteritemSpacing) + 1;
NSUInteger columnCount = (canvasSize.width - self.itemSize.width) / (self.itemSize.width + self.minimumLineSpacing) + 1;
CGFloat pageMarginX = (canvasSize.width - columnCount * self.itemSize.width - (columnCount > 1 ? (columnCount - 1) * self.minimumLineSpacing : 0)) / 2.0f;
CGFloat pageMarginY = (canvasSize.height - rowCount * self.itemSize.height - (rowCount > 1 ? (rowCount - 1) * self.minimumInteritemSpacing : 0)) / 2.0f;
NSUInteger page = indexPath.row / (rowCount * columnCount);
NSUInteger remainder = indexPath.row - page * (rowCount * columnCount);
NSUInteger row = remainder / columnCount;
NSUInteger column = remainder - row * columnCount;
CGRect cellFrame = CGRectZero;
cellFrame.origin.x = pageMarginX + column * (self.itemSize.width + self.minimumLineSpacing);
cellFrame.origin.y = pageMarginY + row * (self.itemSize.height + self.minimumInteritemSpacing);
cellFrame.size.width = self.itemSize.width;
cellFrame.size.height = self.itemSize.height;
if (self.scrollDirection == UICollectionViewScrollDirectionHorizontal)
{
cellFrame.origin.x += page * canvasSize.width;
}
return cellFrame;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes * attr = [super layoutAttributesForItemAtIndexPath:indexPath];
attr.frame = [self frameForItemAtIndexPath:indexPath];
return attr;
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray * originAttrs = [super layoutAttributesForElementsInRect:rect];
NSMutableArray * attrs = [NSMutableArray array];
[originAttrs enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes * attr, NSUInteger idx, BOOL *stop) {
NSIndexPath * idxPath = attr.indexPath;
CGRect itemFrame = [self frameForItemAtIndexPath:idxPath];
if (CGRectIntersectsRect(itemFrame, rect))
{
attr = [self layoutAttributesForItemAtIndexPath:idxPath];
[attrs addObject:attr];
}
}];
return attrs;
}
Notice, above code snippet only supports single section and horizontal scroll direction. But it is not hard to expand.
注意,上面的代码片段只支持单节和水平滚动方向。但是扩展起来并不难。
Also, if you don't have millions of cells. Caching those UICollectionViewLayoutAttributes may be a good idea.
另外,如果您没有数百万个单元格。缓存那些 UICollectionViewLayoutAttributes 可能是个好主意。
回答by Leszek Szary
You could disable paging on UICollectionView and implement a custom horizontal scrolling/paging mechanism with a custom page width/offset like this:
您可以在 UICollectionView 上禁用分页,并使用自定义页面宽度/偏移量实现自定义水平滚动/分页机制,如下所示:
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
float pageWidth = 210;
float currentOffset = scrollView.contentOffset.x;
float targetOffset = targetContentOffset->x;
float newTargetOffset = 0;
if (targetOffset > currentOffset)
newTargetOffset = ceilf(currentOffset / pageWidth) * pageWidth;
else
newTargetOffset = floorf(currentOffset / pageWidth) * pageWidth;
if (newTargetOffset < 0)
newTargetOffset = 0;
else if (newTargetOffset > scrollView.contentSize.width)
newTargetOffset = scrollView.contentSize.width;
targetContentOffset->x = currentOffset;
[scrollView setContentOffset:CGPointMake(newTargetOffset, 0) animated:YES];
}
回答by devdavid
This answer is way late, but I have just been playing with this problem and found that the cause of the drift is the line spacing. If you want the UICollectionView/FlowLayoutto page at exact multiples of your cells width, you must set:
这个答案来晚了,但我刚刚在玩这个问题,发现漂移的原因是行间距。如果您希望UICollectionView/FlowLayout以单元格宽度的精确倍数进行分页,则必须设置:
UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *)collectionView.collectionViewLayout;
flowLayout.minimumLineSpacing = 0.0;
You wouldn't think the line spacing comes into play in horizontal scrolling, but apparently it does.
您不会认为行间距在水平滚动中起作用,但显然确实如此。
In my case I was experimenting with paging left to right, one cell at a time, with no space between cells. Every turn of the page introduced a tiny bit of drift from the desired position, and it seemed to accumulate linearly. ~10.0 pts per turn. I realized 10.0is the default value of minimumLineSpacingin the flow layout. When I set it to 0.0, no drift, when I set it to half the bounds width, each page drifted an extra half of the bounds.
就我而言,我正在尝试从左到右分页,一次一个单元格,单元格之间没有空格。页面的每一转都会从所需位置引入一点点漂移,并且它似乎是线性累积的。 ~每回合10.0分。我意识到10.0是流布局中minimumLineSpacing的默认值。当我将它设置为0.0 时,没有漂移,当我将它设置为边界宽度的一半时,每个页面都会额外漂移一半的边界。
Changing the minimumInteritemSpacinghad no effect.
更改minimumInteritemSpacing有没有影响。
edit -- from the documentation for UICollectionViewFlowLayout:
编辑——来自UICollectionViewFlowLayout的文档:
@property (nonatomic) CGFloat minimumLineSpacing;
Discussion
...
For a vertically scrolling grid, this value represents the minimum spacing between successive rows. For a horizontally scrolling grid, this value represents the minimum spacing between successive columns.This spacing is not applied to the space between the header and the first line or between the last line and the footer.
The default value of this property is 10.0.
@property (nonatomic) CGFloat minimumLineSpacing;
讨论
...
对于垂直滚动的网格,此值表示连续行之间的最小间距。对于水平滚动网格,此值表示连续列之间的最小间距。此间距不适用于页眉和第一行之间或最后一行和页脚之间的空间。
此属性的默认值为 10.0。
回答by nikolsky
The solution from the following article is elegant and simple. The main idea is creation the scrollView on top of your collectionView with passing all contentOffset values.
以下文章中的解决方案优雅而简单。主要思想是通过传递所有 contentOffset 值在 collectionView 之上创建滚动视图。
http://b2cloud.com.au/tutorial/uiscrollview-paging-size/
http://b2cloud.com.au/tutorial/uiscrollview-paging-size/
It should be said by implementing this method:
通过实现这个方法应该说:
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity;
I didn't achieve a smooth animation like it's happening with pagingEnabled = YES.
我没有像 pagingEnabled = YES 那样实现流畅的动画。
回答by Jonathan
I know this question is old, but for anyone who happens to stumble upon this.
我知道这个问题很老,但对于任何偶然发现这个问题的人来说。
All you have to do to correct this is set the MinimumInterItemSpacing to 0 and decrease the content's frame.
要纠正此问题,您只需将 MinimumInterItemSpacing 设置为 0 并减少内容的框架。
回答by raz0r
I think I understand the problem. I'll try and make you understand it too.
我想我明白这个问题了。我也会努力让你明白的。
If you look closely, then you will see that this issue happens only gradually and not just on the first page swipe.
如果仔细观察,您会发现此问题只是逐渐发生,而不仅仅是在第一页滑动时发生。
If I understand correctly, in your app, currently, every UICollectionView item are those rounded boxes which we see, and you have some offset/margin between all of them which is constant. This is what is causing the issue.
如果我理解正确的话,在您的应用程序中,目前,每个 UICollectionView 项目都是我们看到的那些圆角框,并且您在它们之间有一些偏移/边距,这是恒定的。这就是导致问题的原因。
Instead, what you should do, is make a UICollectionView item which is 1/3rd of the width of the whole view, and then add that rounded image view inside it. To refer to the image, the green colour should be your UICollectionViewItem and not the black one.
相反,您应该做的是制作一个 UICollectionView 项目,它是整个视图宽度的 1/3,然后在其中添加圆形图像视图。要引用图像,绿色应该是您的 UICollectionViewItem 而不是黑色。
回答by erickva
回答by k06a
My answer is based on answer https://stackoverflow.com/a/27242179/440168but is more simple.
我的答案基于答案https://stackoverflow.com/a/27242179/440168但更简单。
You should place UIScrollView
above UICollectionView
and give them equal sizes:
你应该放在UIScrollView
上面UICollectionView
并给它们相同的大小:
@property (nonatomic, weak) IBOutlet UICollectionView *collectionView;
@property (nonatomic, weak) IBOutlet UIScrollView *scrollView;
Then configure contentInset
of collection view, for example:
然后配置contentInset
集合视图,例如:
CGFloat inset = self.view.bounds.size.width*2/9;
self.collectionView.contentInset = UIEdgeInsetsMake(0, inset, 0, inset);
And contentSize
of scroll view:
和contentSize
滚动视图:
self.scrollView.contentSize = CGSizeMake(self.placesCollectionView.bounds.size.width*[self.collectionView numberOfItemsInSection:0],0);
Do not forget to set delegate of scroll view:
不要忘记设置滚动视图的委托:
self.scrollView.delegate = self;
And implement main magic:
并实现主要魔法:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView == self.scrollView) {
CGFloat inset = self.view.bounds.size.width*2/9;
CGFloat scale = (self.placesCollectionView.bounds.size.width-2*inset)/scrollView.bounds.size.width;
self.collectionView.contentOffset = CGPointMake(scrollView.contentOffset.x*scale - inset, 0);
}
}
回答by Audun Kjelstrup
Do you roll your own UICollectionViewFlowLayout
?
你自己卷UICollectionViewFlowLayout
吗?
If so, adding -(CGPoint) targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
withScrollingVelocity:(CGPoint)velocity
will help you to calculate where the scrollview should stop.
如果是这样,添加-(CGPoint) targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
withScrollingVelocity:(CGPoint)velocity
将帮助您计算滚动视图应该停止的位置。
This might work (NB: UNTESTED!):
这可能有效(注意:未经测试!):
-(CGPoint) targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset
withScrollingVelocity:(CGPoint)velocity
{
CGFloat offsetAdjustment = MAXFLOAT;
CGFloat targetX = proposedContentOffset.x + self.minimumInteritemSpacing + self.sectionInset.left;
CGRect targetRect = CGRectMake(proposedContentOffset.x, 0.0, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);
NSArray *array = [super layoutAttributesForElementsInRect:targetRect];
for(UICollectionViewLayoutAttributes *layoutAttributes in array) {
if(layoutAttributes.representedElementCategory == UICollectionElementCategoryCell) {
CGFloat itemX = layoutAttributes.frame.origin.x;
if (ABS(itemX - targetX) < ABS(offsetAdjustment)) {
offsetAdjustment = itemX - targetX;
}
}
}
return CGPointMake(proposedContentOffset.x + offsetAdjustment, proposedContentOffset.y);
}
回答by gardenofwine
your UICollectionView
's width should be an exact multiplication of the cell size width + the left and right insets. In your example, if the cell width is 96, then the UICollectionView
's width should be (96 + 5 + 5) * 3 = 318
.
Or, if you wish to keep UICollectionView's 320 width, your cell size width should be 320 / 3 - 5 - 5 = 96.666
.
你UICollectionView
的宽度应该是单元格大小宽度+左右插图的精确乘积。在您的示例中,如果单元格宽度为 96,则UICollectionView
的宽度应为(96 + 5 + 5) * 3 = 318
。或者,如果您希望保持 UICollectionView 的 320 宽度,您的单元格大小宽度应该是320 / 3 - 5 - 5 = 96.666
.
If this does not help, your UICollectionView
's width might be different than what is set in the xib file, when the application runs. To check this - add an NSLog
statement to printout the view's size in runtime:
如果这没有帮助UICollectionView
,当应用程序运行时,您的 宽度可能与 xib 文件中设置的不同。要检查这一点 - 添加一条NSLog
语句以在运行时打印出视图的大小:
NSLog(@"%@", NSStringFromCGRect(uiContentViewController.view.frame));