UICollectionView水平分页 – 我可以使用stream布局?

这与使用stream布局或自定义相关,但不同。 。

这是我想要做的一个例子: 我正在尝试做的插图

我想知道如果我可以做一个UICollectionViewFlowLayout ,它的子类,或者如果我需要创build一个完全自定义的布局? 根据UICollectionView上的WWDC 2012video,看起来如果您使用垂直滚动的stream布局,则您的布局线是水平的,而如果您水平滚动,则布局线是垂直的。 我想水平滚动的集合视图中的水平布局线。

我的模型中也没有任何固有的部分 – 这只是一组项目。 我可以将它们分组成不同的部分,但是集合视图可以resize,所以可以适合页面的项目数量有时会发生变化,看起来每个项目所在的页面的select最好留给布局,而不是该模型如果我没有任何有意义的部分。

那么,我可以使用stream布局来做到这一点,还是我需要创build一个自定义的布局?

你是对的 – 这不是一个股票水平滚动收集视图如何奠定细胞。 恐怕你将不得不实现你自己的自定义UICollectionViewLayout子类。 要么,要么将模型分成几个部分。

在这里,我分享我的简单实施!

.h文件:

 /** * CollectionViewLayout for an horizontal flow type: * * | 0 1 | 6 7 | * | 2 3 | 8 9 | ----> etc... * | 4 5 | 10 11 | * * Only supports 1 section and no headers, footers or decorator views. */ @interface HorizontalCollectionViewLayout : UICollectionViewLayout @property (nonatomic, assign) CGSize itemSize; @end 

.m文件:

 @implementation HorizontalCollectionViewLayout { NSInteger _cellCount; CGSize _boundsSize; } - (void)prepareLayout { // Get the number of cells and the bounds size _cellCount = [self.collectionView numberOfItemsInSection:0]; _boundsSize = self.collectionView.bounds.size; } - (CGSize)collectionViewContentSize { // We should return the content size. Lets do some math: NSInteger verticalItemsCount = (NSInteger)floorf(_boundsSize.height / _itemSize.height); NSInteger horizontalItemsCount = (NSInteger)floorf(_boundsSize.width / _itemSize.width); NSInteger itemsPerPage = verticalItemsCount * horizontalItemsCount; NSInteger numberOfItems = _cellCount; NSInteger numberOfPages = (NSInteger)ceilf((CGFloat)numberOfItems / (CGFloat)itemsPerPage); CGSize size = _boundsSize; size.width = numberOfPages * _boundsSize.width; return size; } - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { // This method requires to return the attributes of those cells that intsersect with the given rect. // In this implementation we just return all the attributes. // In a better implementation we could compute only those attributes that intersect with the given rect. NSMutableArray *allAttributes = [NSMutableArray arrayWithCapacity:_cellCount]; for (NSUInteger i=0; i<_cellCount; ++i) { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0]; UICollectionViewLayoutAttributes *attr = [self _layoutForAttributesForCellAtIndexPath:indexPath]; [allAttributes addObject:attr]; } return allAttributes; } - (UICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { return [self _layoutForAttributesForCellAtIndexPath:indexPath]; } - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { // We should do some math here, but we are lazy. return YES; } - (UICollectionViewLayoutAttributes*)_layoutForAttributesForCellAtIndexPath:(NSIndexPath*)indexPath { // Here we have the magic of the layout. NSInteger row = indexPath.row; CGRect bounds = self.collectionView.bounds; CGSize itemSize = self.itemSize; // Get some info: NSInteger verticalItemsCount = (NSInteger)floorf(bounds.size.height / itemSize.height); NSInteger horizontalItemsCount = (NSInteger)floorf(bounds.size.width / itemSize.width); NSInteger itemsPerPage = verticalItemsCount * horizontalItemsCount; // Compute the column & row position, as well as the page of the cell. NSInteger columnPosition = row%horizontalItemsCount; NSInteger rowPosition = (row/horizontalItemsCount)%verticalItemsCount; NSInteger itemPage = floorf(row/itemsPerPage); // Creating an empty attribute UICollectionViewLayoutAttributes *attr = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; CGRect frame = CGRectZero; // And finally, we assign the positions of the cells frame.origin.x = itemPage * bounds.size.width + columnPosition * itemSize.width; frame.origin.y = rowPosition * itemSize.height; frame.size = _itemSize; attr.frame = frame; return attr; } #pragma mark Properties - (void)setItemSize:(CGSize)itemSize { _itemSize = itemSize; [self invalidateLayout]; } @end 

最后,如果你想分页的行为,你只需要configuration你的UICollectionView:

 _collectionView.pagingEnabled = YES; 

希望能够有用。

将vilanovi代码转换成Swift以防万一某人在将来需要它。

 public class HorizontalCollectionViewLayout : UICollectionViewLayout { private var cellWidth = 90 // Don't kow how to get cell size dynamically private var cellHeight = 90 public override func prepareLayout() { } public override func collectionViewContentSize() -> CGSize { let numberOfPages = Int(ceilf(Float(cellCount) / Float(cellsPerPage))) let width = numberOfPages * Int(boundsWidth) return CGSize(width: CGFloat(width), height: boundsHeight) } public override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? { var allAttributes = [UICollectionViewLayoutAttributes]() for (var i = 0; i < cellCount; ++i) { let indexPath = NSIndexPath(forRow: i, inSection: 0) let attr = createLayoutAttributesForCellAtIndexPath(indexPath) allAttributes.append(attr) } return allAttributes } public override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! { return createLayoutAttributesForCellAtIndexPath(indexPath) } public override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool { return true } private func createLayoutAttributesForCellAtIndexPath(indexPath:NSIndexPath) -> UICollectionViewLayoutAttributes { let layoutAttributes = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath) layoutAttributes.frame = createCellAttributeFrame(indexPath.row) return layoutAttributes } private var boundsWidth:CGFloat { return self.collectionView!.bounds.size.width } private var boundsHeight:CGFloat { return self.collectionView!.bounds.size.height } private var cellCount:Int { return self.collectionView!.numberOfItemsInSection(0) } private var verticalCellCount:Int { return Int(floorf(Float(boundsHeight) / Float(cellHeight))) } private var horizontalCellCount:Int { return Int(floorf(Float(boundsWidth) / Float(cellWidth))) } private var cellsPerPage:Int { return verticalCellCount * horizontalCellCount } private func createCellAttributeFrame(row:Int) -> CGRect { let frameSize = CGSize(width:cellWidth, height: cellHeight ) let frameX = calculateCellFrameHorizontalPosition(row) let frameY = calculateCellFrameVerticalPosition(row) return CGRectMake(frameX, frameY, frameSize.width, frameSize.height) } private func calculateCellFrameHorizontalPosition(row:Int) -> CGFloat { let columnPosition = row % horizontalCellCount let cellPage = Int(floorf(Float(row) / Float(cellsPerPage))) return CGFloat(cellPage * Int(boundsWidth) + columnPosition * Int(cellWidth)) } private func calculateCellFrameVerticalPosition(row:Int) -> CGFloat { let rowPosition = (row / horizontalCellCount) % verticalCellCount return CGFloat(rowPosition * Int(cellHeight)) } 

}

前面的上述实现并不完整,错误,并且具有固定的单元大小。 下面是代码的更直接的翻译:

 import UIKit class HorizontalFlowLayout: UICollectionViewLayout { var itemSize = CGSizeZero { didSet { invalidateLayout() } } private var cellCount = 0 private var boundsSize = CGSizeZero override func prepareLayout() { cellCount = self.collectionView!.numberOfItemsInSection(0) boundsSize = self.collectionView!.bounds.size } override func collectionViewContentSize() -> CGSize { let verticalItemsCount = Int(floor(boundsSize.height / itemSize.height)) let horizontalItemsCount = Int(floor(boundsSize.width / itemSize.width)) let itemsPerPage = verticalItemsCount * horizontalItemsCount let numberOfItems = cellCount let numberOfPages = Int(ceil(Double(numberOfItems) / Double(itemsPerPage))) var size = boundsSize size.width = CGFloat(numberOfPages) * boundsSize.width return size } override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? { var allAttributes = [UICollectionViewLayoutAttributes]() for var i = 0; i < cellCount; i++ { let indexPath = NSIndexPath(forRow: i, inSection: 0) let attr = self.computeLayoutAttributesForCellAtIndexPath(indexPath) allAttributes.append(attr) } return allAttributes } override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? { return self.computeLayoutAttributesForCellAtIndexPath(indexPath) } override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool { return true } func computeLayoutAttributesForCellAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes { let row = indexPath.row let bounds = self.collectionView!.bounds let verticalItemsCount = Int(floor(boundsSize.height / itemSize.height)) let horizontalItemsCount = Int(floor(boundsSize.width / itemSize.width)) let itemsPerPage = verticalItemsCount * horizontalItemsCount let columnPosition = row % horizontalItemsCount let rowPosition = (row/horizontalItemsCount)%verticalItemsCount let itemPage = Int(floor(Double(row)/Double(itemsPerPage))) let attr = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath) var frame = CGRectZero frame.origin.x = CGFloat(itemPage) * bounds.size.width + CGFloat(columnPosition) * itemSize.width frame.origin.y = CGFloat(rowPosition) * itemSize.height frame.size = itemSize attr.frame = frame return attr } } 

只需将UICollectionView.xib中的滚动方向更改为水平即可。 并使用数组中正确的元素顺序。

这是@GuilhermeSprint答案的Swift 3版本

码:

 public class HorizontalCollectionViewLayout : UICollectionViewLayout { var itemSize = CGSize(width: 0, height: 0) { didSet { invalidateLayout() } } private var cellCount = 0 private var boundsSize = CGSize(width: 0, height: 0) public override func prepare() { cellCount = self.collectionView!.numberOfItems(inSection: 0) boundsSize = self.collectionView!.bounds.size } public override var collectionViewContentSize: CGSize { let verticalItemsCount = Int(floor(boundsSize.height / itemSize.height)) let horizontalItemsCount = Int(floor(boundsSize.width / itemSize.width)) let itemsPerPage = verticalItemsCount * horizontalItemsCount let numberOfItems = cellCount let numberOfPages = Int(ceil(Double(numberOfItems) / Double(itemsPerPage))) var size = boundsSize size.width = CGFloat(numberOfPages) * boundsSize.width return size } public override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { var allAttributes = [UICollectionViewLayoutAttributes]() for i in 0...(cellCount-1) { let indexPath = IndexPath(row: i, section: 0) let attr = self.computeLayoutAttributesForCellAt(indexPath: indexPath) allAttributes.append(attr) } return allAttributes } public override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { return computeLayoutAttributesForCellAt(indexPath: indexPath) } public override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { return true } private func computeLayoutAttributesForCellAt(indexPath:IndexPath) -> UICollectionViewLayoutAttributes { let row = indexPath.row let bounds = self.collectionView!.bounds let verticalItemsCount = Int(floor(boundsSize.height / itemSize.height)) let horizontalItemsCount = Int(floor(boundsSize.width / itemSize.width)) let itemsPerPage = verticalItemsCount * horizontalItemsCount let columnPosition = row % horizontalItemsCount let rowPosition = (row/horizontalItemsCount)%verticalItemsCount let itemPage = Int(floor(Double(row)/Double(itemsPerPage))) let attr = UICollectionViewLayoutAttributes(forCellWith: indexPath) var frame = CGRectMake(0, 0, 0, 0) frame.origin.x = CGFloat(itemPage) * bounds.size.width + CGFloat(columnPosition) * itemSize.width frame.origin.y = CGFloat(rowPosition) * itemSize.height frame.size = itemSize attr.frame = frame return attr } } 

用法:

  // I want to have 4 items in the page / see screenshot below let itemWidth = collectionView.frame.width / 2.0 let itemHeight = collectionView.frame.height / 2.0 let horizontalCV = HorizontalCollectionViewLayout(); horizontalCV.itemSize = CGSize(width: itemWidth, height: itemHeight) collectionView.collectionViewLayout = horizontalCV 

结果

截图

我的代表扩展,如果你想检查它也

 extension MyViewController : UICollectionViewDelegateFlowLayout, UICollectionViewDataSource{ // Delegate func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { print("Clicked") } // DataSource func numberOfSections(in collectionView: UICollectionView) -> Int { return 1 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: BottomMenuCCell.xib, for: indexPath) as? BottomMenuCCell { cell.ibi = bottomMenuButtons[indexPath.row] cell.layer.borderWidth = 0 return cell } return BaseCollectionCell() } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return CGSize.init(width: (collectionView.width / 2.0), height: collectionView.height / 2.0) } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return bottomMenuButtons.count } // removing spacing between items func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { return 0.0 } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { return 0.0 } } 

当然,最后的方法是在外部水平滚动集合视图的每个部分内使用多个垂直集合视图。 除了增加代码的复杂性和执行跨部分单元格animation的难度外,我不能想到这个方法的重大问题。

 @interface HorizontalCollectionViewLayout : UICollectionViewFlowLayout @end @implementation HorizontalCollectionViewLayout - (instancetype)init { self = [super init]; if (self) { self.scrollDirection = UICollectionViewScrollDirectionHorizontal; self.minimumLineSpacing = 0; self.minimumInteritemSpacing = 0; } return self; } - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { NSArray *attributesArray = [super layoutAttributesForElementsInRect:rect]; NSInteger verticalItemsCount = (NSInteger)floorf(self.collectionView.bounds.size.height / self.itemSize.height); NSInteger horizontalItemsCount = (NSInteger)floorf(self.collectionView.bounds.size.width / self.itemSize.width); NSInteger itemsPerPage = verticalItemsCount * horizontalItemsCount; for (NSInteger i = 0; i < attributesArray.count; i++) { UICollectionViewLayoutAttributes *attributes = attributesArray[i]; NSInteger currentPage = (NSInteger)floor((double)i / (double)itemsPerPage); NSInteger currentRow = (NSInteger)floor((double)(i - currentPage * itemsPerPage) / (double)horizontalItemsCount); NSInteger currentColumn = i % horizontalItemsCount; CGRect frame = attributes.frame; frame.origin.x = self.itemSize.width * currentColumn + currentPage * self.collectionView.bounds.size.width; frame.origin.y = self.itemSize.height * currentRow; attributes.frame = frame; } return attributesArray; } - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { return YES; } @end