I need a UICollectionView
to display a grid that is potentially larger than the visible frame in both width and height, while maintaining row and column integrity. The default UICollectionViewFlowLayout
allows sections to scroll off-screen, but it wraps items within a section to keep them all on-screen, which screws up my grid.
Recognizing that UICollectionView
is a subclass of UIScrollView
, I tried just manually setting the collection view's content size property in viewDidLoad:
self.collectionView.contentSize = CGSizeMake((columns * (cellWidth + itemSpacingX), (rows * (cellHeight + itemSpacingY));
But this had no effect. Questions:
- Is there an easy way to accomplish this without building a custom layout?
- Would using a custom layout and overriding the
collectionViewContentSize
method succeed in getting the collection view to stop wrapping items and scroll in both directions? - If I do have to build a custom layout--which I'll have to invest some time to learn--do I subclass
UICollectionViewLayout
, or would subclassingUICollectionViewFlowLayout
save time?
UPDATE: I tried embedding the UICollectionView as a subview of a UIScrollView. The collection view itself is behaving correctly--the rows aren't wrapping at the edge of the scroll view, telling me that it is filling the UIScrollView content size that I set. But the scroll view doesn't pan in the horizontal direction, i.e. it only scrolls vertically, which the collection view does by itself anyway. So stuck again. Could there be an issue with the responder chain?
Figured out two ways to do this. Both required a custom layout. The problem is that the default flow layout--and I now know from the Collection View Programming Guide this is partly the definition of a flow layout--generates the cell layout attributes based on the bounds of the superview, and will wrap items in a section to keep them in bounds so that scrolling occurs in only one axis. Will skip the code details, as it isn't hard, and my problem was mainly confusion on what approach to take.
Easy way: use a UIScrollView and subclass 'UICollectionViewFlowLayout'. Embed the
UICollectionView
in aUIScrollView
. Set the contentSize property of the scroll view inviewDiDLoad
to match the full size that your collection view will occupy (this will let the default flow layout place items in a single line within a section without wrapping). SubclassUICollectionViewFlowLayout
, and set that object as your custom layout for the collection view. In the custom flow layout, overridecollectionViewContentSize
to return the full size of the collection view matrix. With this approach, you'll be using a flow layout, but will be able to scroll in both directions to view un-wrapped sections. The disadvantage is that you still have a flow layout that is pretty limited. Plus, it seems clunky to put aUICollectionView
inside an instance of its own superclass just to get the functionality that the collection view by itself should have.Harder way, but more versatile and elegant: subclass UICollectionViewLayout. I used this tutorial to learn how to implement a complete custom layout. You don't need a
UIScrollView
here. If you forego the flow layout, subclassUICollectionViewLayout
, and set that as the custom layout, you can build out the matrix and get the right behavior from the collection view itself. It's more work because you have to generate all the layout attributes, but you'll be positioned to make the collection view do whatever you want.In my opinion, Apple should add a property to the default flow layout that suppresses wrapping. Getting a device to display a 2D matrix with intact rows and columns isn't an exotic functionality and it seems like it should be easier to do.
Here is complete matrix customLayout:
sections are rows, items are columns
Here is a version of AndrewK's code updated to Swift 4: