UICollectionView - 水平滚动,水平布局?
Posted
技术标签:
【中文标题】UICollectionView - 水平滚动,水平布局?【英文标题】:UICollectionView - Horizontal scroll, horizontal layout? 【发布时间】:2013-10-18 13:36:34 【问题描述】:我有一个UIScrollView
,它显示了一个图标网格。如果您想象 ios Springboard 的布局,您将非常接近正确。它有一个水平的分页滚动(就像跳板一样)。但是,布局似乎不太正确。看起来好像它正在从上到下布置项目。结果,由于要显示的项目数量,我的最后一列只有 2 行。我宁愿在最后一页的最后一行有 2 个项目,就像您在跳板中看到的那样。
如何使用UICollectionView
及其相关类来实现这一点?我必须写一个自定义的UICollectionViewFlowLayout
吗?
【问题讨论】:
您想出的解决方案是什么?我也在尝试水平布局。 【参考方案1】:第一种方法
将UIPageViewController
与UICollectionViewControllers
的数组一起使用怎么样?您必须在每个UICollectionViewController
中获取适当数量的项目,但这应该不难。您将获得与 Springboard 完全相同的外观。
第二种方法
我已经考虑过了,我认为你必须设置:
self.collectionView.pagingEnabled = YES;
并通过子类化UICollectionViewLayout
创建您自己的集合视图布局。您可以从自定义布局对象访问self.collectionView
,因此您将知道集合视图的frame
、numberOfSections
和numberOfItemsInSection:
的大小。使用该信息,您可以计算单元格的frames
(在prepareLayout
中)和collectionViewContentSize
。以下是一些关于创建自定义布局的文章:
第三种方法
您可以在不创建自定义布局的情况下执行此操作(或其近似值)。在空白视图中添加UIScrollView
,在其中设置分页。在滚动视图中添加一个集合视图。然后添加一个宽度约束,在代码中检查你有多少项目并将其constant
设置为正确的值,例如(self.view.frame.size.width * numOfScreens)
。这是它的外观(单元格上的数字显示indexPath.row
):https://www.dropbox.com/s/ss4jdbvr511azxz/collection_view.mov 如果您对单元格的排序方式不满意,那么恐怕您必须选择 1. 或 2。
【讨论】:
Adopting the 1st approach, I have problems displaying the view controller via push, when a cell is selected.如何解决? @Scott:什么样的问题? 我已经发布了question earlier here。我能够在每个页面中加载不同的 UICollectionViewController,但是一旦我选择单元格就会出现问题,错误指出 Push segues 只能在源控制器由 UINavigationController 的实例管理时使用 如果有人想要第二种方法的工作示例***.com/questions/25963987/…【参考方案2】:您是否尝试将 UICollectionViewFlowLayout 的滚动方向设置为水平?
[yourFlowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
如果你想让它像跳板一样分页,你需要像这样在你的集合视图上启用分页:
[yourCollectionView setPagingEnabled:YES];
【讨论】:
是的,但是正如我在问题中提到的,这会在每个页面上从上到下填充集合视图。结果,最后一列仅被部分填充。我宁愿它从左到右填充集合视图,使最后一行部分填充。【参考方案3】:只是为了好玩,另一种方法是只保留分页和水平滚动集,添加一个方法来更改数组项的顺序以从“从上到下,从左到右”转换为视觉上 '从左到右,从上到下' 并用空的隐藏单元格填充中间单元格以使间距正确。如果 9 个网格中有 7 个项目,则如下所示:
[1][4][7]
[2][5][ ]
[3][6][ ]
应该变成
[1][2][3]
[4][5][6]
[7][ ][ ]
所以 1=1、2=4、3=7 等等,6=空。您可以通过计算总行数和总列数对它们重新排序,然后计算每个单元格的行数和列数,更改列的行数,反之亦然,然后您就有了新的索引。当单元格没有与图像对应的值时,您可以返回一个空单元格并将cell.hidden = YES;
设置为它。
它在我构建的音板应用程序中运行良好,所以如果有人想要工作代码,我会添加它。只需要很少的代码就可以使这个技巧发挥作用,听起来比它更难!
更新
我怀疑这是最好的解决方案,但根据要求,这里的工作代码:
- (void)viewDidLoad
// Fill an `NSArray` with items in normal order
items = [NSMutableArray arrayWithObjects:
[NSDictionary dictionaryWithObjectsAndKeys:@"Some label 1", @"label", @"Some value 1", @"value", nil],
[NSDictionary dictionaryWithObjectsAndKeys:@"Some label 2", @"label", @"Some value 2", @"value", nil],
[NSDictionary dictionaryWithObjectsAndKeys:@"Some label 3", @"label", @"Some value 3", @"value", nil],
[NSDictionary dictionaryWithObjectsAndKeys:@"Some label 4", @"label", @"Some value 4", @"value", nil],
[NSDictionary dictionaryWithObjectsAndKeys:@"Some label 5", @"label", @"Some value 5", @"value", nil],
nil
];
// Calculate number of rows and columns based on width and height of the `UICollectionView` and individual cells (you might have to add margins to the equation based on your setup!)
CGFloat w = myCollectionView.frame.size.width;
CGFloat h = myCollectionView.frame.size.height;
rows = floor(h / cellHeight);
columns = floor(w / cellWidth);
// Calculate number of sections
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
return ceil((float)items.count / (float)(rows * columns));
// Every section has to have every cell filled, as we need to add empty cells as well to correct the spacing
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
return rows*columns;
// And now the most important one
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier@"myIdentifier" forIndexPath:indexPath];
// Convert rows and columns
int row = indexPath.row % rows;
int col = floor(indexPath.row / rows);
// Calculate the new index in the `NSArray`
int newIndex = ((int)indexPath.section * rows * columns) + col + row * columns;
// If the newIndex is within the range of the items array we show the cell, if not we hide it
if(newIndex < items.count)
NSDictionary *item = [items objectAtIndex:newIndex];
cell.label.text = [item objectForKey:@"label"];
cell.hidden = NO;
else
cell.hidden = YES;
return cell;
如果您想使用didSelectItemAtIndexPath
方法,您必须使用与cellForItemAtIndexPath
相同的转换来获得相应的项目。如果您有单元格边距,则需要将它们添加到行和列计算中,因为这些必须正确才能使其起作用。
【讨论】:
你能分享你的代码吗?这听起来像是最好的解决方案! 添加了工作代码。如果您对此有任何疑问,请告诉我! 答案很好,但没有指定行、列、单元格高度、单元格宽度的数据类型。if(newIndex < items.count)
由于这条线,应用程序正在崩溃。所以我将它更新为if(newIndex < items.count-1)
所以如果我的数组有 4 个对象,它在集合视图中只显示 3 个项目。有什么帮助吗?
这是一个非常好的想法并且易于实施。【参考方案4】:
您需要将UICollectionView
的高度降低到其单元格/项目高度,然后从“Scroll Direction
”中选择“Horizontal
”,如下面的屏幕截图所示。然后它将根据您在其数据源实现中返回的numberOfItems
水平滚动。
【讨论】:
这并不能回答问题。 感谢“您需要将 UICollectionView 的高度降低到它的单元格/项目高度”这个技巧对我非常有用。【参考方案5】:我正在开发 Xcode 6.2,对于水平滚动,我在属性检查器中更改了滚动方向。
点击collectionView->属性检查器->滚动方向->改为水平
希望对大家有所帮助。
【讨论】:
【参考方案6】:如果您在代码中定义 UICollectionViewFlowLayout
,它将覆盖 Interface Builder 配置。因此,您需要再次重新定义scrollDirection
。
let layout = UICollectionViewFlowLayout()
...
layout.scrollDirection = .Horizontal
self.awesomeCollectionView.collectionViewLayout = layout
【讨论】:
这是实际答案。【参考方案7】:来自@Erik Hunter,我发布了 make Horizontal UICollectionView
的完整代码
UICollectionViewFlowLayout *collectionViewFlowLayout = [[UICollectionViewFlowLayout alloc] init];
[collectionViewFlowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
self.myCollectionView.collectionViewLayout = collectionViewFlowLayout;
在斯威夫特中
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .Horizontal
self.myCollectionView.collectionViewLayout = layout
在 Swift 3.0 中
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
self.myCollectionView.collectionViewLayout = layout
希望有帮助
【讨论】:
如果您需要水平滚动以默认方向填充单元格,那么它很好(默认:从上到下)但是,如果需要使水平滚动与单元格填充水平(从左到右)然后需要自定义它...(参考:5_Output_H-Scroll_H-Fill.png)【参考方案8】:我们可以使用 UICollectionView 执行相同的 Springboard 行为,为此我们需要为自定义布局编写代码。
我通过 "SMCollectionViewFillLayout"
的自定义布局类实现实现了它代码库:
https://github.com/smindia1988/SMCollectionViewFillLayout
输出如下:
1.png
2_Code_H-Scroll_V-Fill.png
3_Output_H-Scroll_V-Fill.png
4_Code_H-Scroll_H-Fill.png
5_Output_H-Scroll_H-Fill.png
【讨论】:
我想像“5_Output_H-Scroll_H-Fill.png”那样实现。请建议我,我怎样才能做到这一点? @iPhoneDev 我已共享代码存储库链接。您可以从那里下载代码。希望我的代码对你有帮助!!! :) @Sandy 我知道 OP 没有要求这个,但是你有没有机会在 Swift 中使用你的代码?【参考方案9】:对于 xcode 8,我做到了这一点,并且成功了:
【讨论】:
【参考方案10】:您可以编写一个自定义的UICollectionView
布局来实现这一点,这是我的实现的演示图像:
这里是代码库:KSTCollectionViewPageHorizontalLayout
@iPhoneDev(这也许对你也有帮助)
【讨论】:
【参考方案11】:此代码在 Swift 3.1 和 Xcode 8.3.2 中运行良好
override func viewDidLayoutSubviews()
super.viewDidLayoutSubviews()
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
self.collectionView.collectionViewLayout = layout
self.collectionView!.contentInset = UIEdgeInsets(top: -10, left: 0, bottom:0, right: 0)
if let layout = self.collectionView.collectionViewLayout as? UICollectionViewFlowLayout
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
layout.itemSize = CGSize(width: self.view.frame.size.width-40, height: self.collectionView.frame.size.height-10)
layout.invalidateLayout()
【讨论】:
为什么不在将布局分配给集合视图之前设置这些属性? - 会干净得多。 这段代码不是产生了无限循环吗?您在viewDidLayoutSubviews
中调用它,然后触发invalidateLayout()
,它会一次又一次地调用viewWillLayoutSubviews
和viewDidLayoutSubviews
。【参考方案12】:
如果您需要设置 UICollectionView 滚动方向水平并且您需要将单元格宽度和高度设置为静态。 请将 collectionview 估计大小 Automatic 设置为 None 。
View The ScreenShot
【讨论】:
以上是关于UICollectionView - 水平滚动,水平布局?的主要内容,如果未能解决你的问题,请参考以下文章
UICollectionView - 垂直滚动,带有自定义布局的水平分页