I have a UIScrollView that lays out a grid of icons. If you were to imagine the layout for the iOS Springboard, you'd be pretty close to correct. It has a horizontal, paged scroll (just like Springboard). However, it appears that the layout is not quite right. It appears as though it is laying out the items from top to bottom. As a result, my last column only has 2 rows in it, due to the number of items to be displayed. I'd rather have my last row on the last page have 2 items, like you would see in the Springboard.
How can this be accomplished with UICollectionView and its related classes? Do I have to write a custom UICollectionViewFlowLayout?
We can do same Springboard behavior using UICollectionView and for that we need to write code for custom layout.
I have achieved it with my custom layout class implementation with "SMCollectionViewFillLayout"
Code repository:
https://github.com/smindia1988/SMCollectionViewFillLayout
Output as below:
1.png
2_Code_H-Scroll_V-Fill.png
3_Output_H-Scroll_V-Fill.png![enter image description here](https://i.stack.imgur.com/bfind.png)
4_Code_H-Scroll_H-Fill.png![enter image description here](https://i.stack.imgur.com/mxwSH.png)
5_Output_H-Scroll_H-Fill.png![enter image description here](https://i.stack.imgur.com/cwWqq.png)
If you are defining
UICollectionViewFlowLayout
in code, it will override Interface Builder configs. Hence you need to re-define thescrollDirection
again.1st approach
What about using
UIPageViewController
with an array ofUICollectionViewControllers
? You'd have to fetch proper number of items in eachUICollectionViewController
, but it shouldn't be hard. You'd get exactly the same look as the Springboard has.2nd approach
I've thought about this and in my opinion you have to set:
and create your own collection view layout by subclassing
UICollectionViewLayout
. From the custom layout object you can accessself.collectionView
, so you'll know what is the size of the collection view'sframe
,numberOfSections
andnumberOfItemsInSection:
. With that information you can calculate cells'frames
(inprepareLayout
) andcollectionViewContentSize
. Here're some articles about creating custom layouts:3rd approach
You can do this (or an approximation of it) without creating the custom layout. Add
UIScrollView
in the blank view, set paging enabled in it. In the scroll view add the a collection view. Then add to it a width constraint, check in code how many items you have and set itsconstant
to the correct value, e.g.(self.view.frame.size.width * numOfScreens)
. Here's how it looks (numbers on cells show theindexPath.row
): https://www.dropbox.com/s/ss4jdbvr511azxz/collection_view.mov If you're not satisfied with the way cells are ordered, then I'm afraid you'd have to go with 1. or 2.You can write a custom
UICollectionView
layout to achieve this, here is demo image of my implementation:Here's code repository: KSTCollectionViewPageHorizontalLayout
@iPhoneDev (this maybe help you too)
Have you tried setting the scroll direction of your UICollectionViewFlowLayout to horizontal?
And if you want it to page like springboard does, you'll need to enable paging on your collection view like so:
Just for fun, another approach would be to just leave the paging and horizontal scrolling set, add a method that changes the order of the array items to convert from 'top to bottom, left to right' to visually 'left to right, top to bottom' and fill the in-between cells with empty hidden cells to make the spacing right. In case of 7 items in a grid of 9, this would go like this:
should become
so 1=1, 2=4, 3=7 etc. and 6=empty. You can reorder them by calculating the total number of rows and columns, then calculate the row and column number for each cell, change the row for the column and vice versa and then you have the new indexes. When the cell doesn't have a value corresponding to the image you can return an empty cell and set
cell.hidden = YES;
to it.It works quite well in a soundboard app I built, so if anyone would like working code I'll add it. Only little code is required to make this trick work, it sounds harder than it is!
Update
I doubt this is the best solution, but by request here's working code:
If you'd like to use the
didSelectItemAtIndexPath
method you have to use the same conversion that is used incellForItemAtIndexPath
to get the corresponding item. If you have cell margins you need to add them to the rows and columns calculation, as those have to be correct in order for this to work.