UICollectionView (3x3 Grid) paging with static cell - ios

I have a UICollectionView in a 3x3 grid layout displaying images.
The UICollectionView has "pagingEnabled" set to YES, inorder to "page" between pages with the 3x3 grid with 9 images.
This has been accomplished using a custom UICollectionViewLayout:
#implementation HorizontalCollectionViewLayout
- (instancetype)init
{
self = [super init];
if (self) {
}
return self;
}
- (CGSize)collectionViewContentSize
{
// We should return the content size. Lets do some math:
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;
NSInteger numberOfItems = [self.collectionView numberOfItemsInSection:0];
NSInteger numberOfPages = (NSInteger)ceilf((CGFloat)numberOfItems / (CGFloat)itemsPerPage);
CGSize size = self.collectionView.bounds.size;
size.width = numberOfPages * self.collectionView.bounds.size.width;
return size;
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
// Get all the attributes for the elements in the specified frame
NSArray *allAttributesInRect = [super layoutAttributesForElementsInRect:rect];
NSArray *attributesArray = [[NSArray alloc] initWithArray:allAttributesInRect copyItems:YES];
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;
CGRect f = UIEdgeInsetsInsetRect(frame, UIEdgeInsetsMake(2, 2, 5, 2));
attributes.frame = f;
}
return attributesArray;
}
#end
This is working as expected, as it creates a new page when the first page contains 9 images.
What I want to accomplish then, is to create a static / fixed "next page" button in the right bottom corner of grid at index 8, 17, 26, 35 ... based on the number of pages. (A button at index 8 if the collectionview only has 1 page, a button at index 8 and index 17 if the collectionview only has 2 pages ... and so on). Number of images and pages can vary based on how many images the user uploads to the app.
The following picture illustrates the idea:
Any tips on how I can accomplish this?
What logic should go into "collectionView:itemForIndexPath:"?

In your cellForItemAtIndexPath look at the indexPath.item for the number you are interested in replacing and return a collection view cell for your [ > ] button.
The way the collection view works is that you are never really concerned with the visible cells on the screen (your UICollectionViewLayout is handling that). You are only returning a cell for the indexPath you are given.
Create a dedicated UICollectionViewCell subclass just for this [ > ] cell with its own reuse identifier. It will simplify your code and you don't need to touch your current image cells.

Related

UICollectionView throws uncaught exception using custom layout

I'm using collection view with custom layout, Calling webservice I received first time from the API returns 20 objects, and the second time it will return 1 object, while reloading the data applications throws the following error.
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: UICollectionView received layout attributes for a cell with an index
path that does not exist: {length = 2, path
= 0 - 0}
Code piece of creating new layout
-(void)doNewLayout
{
id<UICollectionViewDelegateJSPintLayout> delegate = (id<UICollectionViewDelegateJSPintLayout>)self.collectionView.delegate;
// get column width from delegate. If the method isn't implemented fall back to our property
NSUInteger columnWidth = self.columnWidth;
if(delegate && [delegate respondsToSelector:#selector(columnWidthForCollectionView:layout:)])
{
columnWidth = [delegate columnWidthForCollectionView:self.collectionView
layout:self];
}
// find out how many cells there are
NSUInteger cellCount = [self.collectionView numberOfItemsInSection:0];
// get max number of columns from the delegate. If the method isn't implemented, fall back to our property
NSUInteger maximumNumberOfColumns = self.numberOfColumns;
if(delegate && [delegate respondsToSelector:#selector(maximumNumberOfColumnsForCollectionView:layout:)]){
maximumNumberOfColumns = [delegate maximumNumberOfColumnsForCollectionView:self.collectionView layout:self];
}
// build an array of all the cell heights.
NSMutableArray* cellHeights = [NSMutableArray arrayWithCapacity:cellCount];
for(NSUInteger cellIndex = 0; cellIndex < cellCount; ++cellIndex)
{
CGFloat itemHeight = self.itemHeight; // set default item size, then optionally override it
if(delegate && [delegate respondsToSelector:#selector(collectionView:layout:heightForItemAtIndexPath:)])
{
itemHeight = [delegate collectionView:self.collectionView
layout:self
heightForItemAtIndexPath:[NSIndexPath indexPathForItem:cellIndex
inSection:0]];
}
cellHeights[cellIndex] = #(itemHeight);
}
// now build the array of layout attributes
self.pendingLayoutAttributes = [NSMutableArray arrayWithCapacity:cellCount];
// will need an array of column heights
CGFloat* columnHeights = calloc(maximumNumberOfColumns,sizeof(CGFloat)); // calloc() initializes to zero.
CGFloat contentHeight = 0.0;
CGFloat contentWidth = 0.0;
for(NSUInteger cellIndex = 0; cellIndex < cellCount; ++cellIndex)
{
CGFloat itemHeight = [cellHeights[cellIndex] floatValue];
// find shortest column
NSUInteger useColumn = 0;
CGFloat shortestHeight = DBL_MAX;
for(NSUInteger col = 0; col < maximumNumberOfColumns; ++col)
{
if(columnHeights[col] < shortestHeight)
{
useColumn = col;
shortestHeight = columnHeights[col];
}
}
NSIndexPath* indexPath = [NSIndexPath indexPathForItem:cellIndex
inSection:0];
UICollectionViewLayoutAttributes* layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
layoutAttributes.size = CGSizeMake(columnWidth,itemHeight);
layoutAttributes.center = CGPointMake((useColumn * (columnWidth + self.interitemSpacing)) + (columnWidth / 2.0),columnHeights[useColumn] + (itemHeight / 2.0));
self.pendingLayoutAttributes[cellIndex] = layoutAttributes;
columnHeights[useColumn] += itemHeight;
if(columnHeights[useColumn] > contentHeight)
contentHeight = columnHeights[useColumn];
CGFloat rightEdge = (useColumn * (columnWidth + self.interitemSpacing)) + columnWidth;
if(rightEdge > contentWidth)
contentWidth = rightEdge;
columnHeights[useColumn] += self.lineSpacing;
}
self.contentSize = CGSizeMake(contentWidth,contentHeight+100);
free(columnHeights);
}
Any Quick solution would be really appreciated.Thanks
There are couple of things need consideration , as you are custom layout then you need to create layoutAttribute for each indexPath. In your case your data source array count and offerModel.arrayOffers and self.pendingLayoutAttributes should be same, if not then it might be problem and its crushable problem if offerModel.arrayOffershave more items then self.pendingLayoutAttributes.
If you are loading data async then make sure when you are adding rows in arraysOffers also add layoutAttributes in customLayout pendLayoutAttributes,which I think you are not doing at the moment, do that by adding a method and provide new indexPaths to that which create layoutAttributes.
I usually do like this
- (void)insertItems:(NSArray*)items
{
NSInteger startIndex = <start index for new item>;
[self.items addObjectsFromArray:items];
[self calculateLayoutForItems:items startIndex:startIndex];
}
This method will calculate layoutAttributes
- (void)calculateLayoutForItems:(NSArray*)items startIndex:(NSInteger)startIndex
{
// collection view and loop over them as well and nest indexPath creation
NSInteger section = 0;
NSInteger endIndex = self.items.count;
// Lets create indexPaths for provided items and get there frames
for (NSInteger index = startIndex ;index < endIndex; index++)
{
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:section];
CGRect frame = [self frameForItemAtIndexPath:indexPath];// call your method for frame at indexPath
UICollectionViewLayoutAttributes *itemAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
itemAttributes.frame = frame;
self.pendingLayoutAttributes[indexPath] = itemAttributes; // you can use your index
}
}
Now when you got more items in data source call this method on customLayoutObject [self.customLayout insertItems:newItemsArray];
Also if you have stored properties then its worth overriding invalidate method in custom layout to reset all properties to initial state,Then
you can just invalidate customLayout before reloadData method, then collection view will force it to compute layout again.

UICollectionView: How to keep the items of a section in a single row?

I would like to have three sections inside a UICollectionView.
Each section shall display its items in the same row. If the items won't fit on the screen they should still remain in the same row and the user should scroll to right to see them all.
In a nutshell, I would like to prevent the items flow into next line. Hope it makes sense.
I have found some strange solutions as here. But these don't really work, if you have more than one section.
How can I tackle this problem? Is there a setting on Storyboard or do I have to use a custom Layout afterall?
Use multiple UICollectionViews, each with its own scrollable area.
I wrote this layout a while ago, but I think it should still work. The data source is an array of arrays, so each row is a new section. Here's the flow layout class,
#define space 5
#import "MultpleLineLayout.h"
#implementation MultpleLineLayout { // a subclass of UICollectionViewFlowLayout
NSInteger itemWidth;
NSInteger itemHeight;
}
-(id)init {
if (self = [super init]) {
itemWidth = 60;
itemHeight = 60;
}
return self;
}
-(CGSize)collectionViewContentSize {
NSInteger xSize = [self.collectionView numberOfItemsInSection:0] * (itemWidth + space); // "space" is for spacing between cells.
NSInteger ySize = [self.collectionView numberOfSections] * (itemHeight + space);
return CGSizeMake(xSize, ySize);
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path {
UICollectionViewLayoutAttributes* attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:path];
attributes.size = CGSizeMake(itemWidth,itemHeight);
long xValue = itemWidth/2 + path.row * (itemWidth + space);
long yValue = itemHeight + path.section * (itemHeight + space);
attributes.center = CGPointMake(xValue, yValue);
return attributes;
}
-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect {
NSInteger minRow = (rect.origin.x > 0)? rect.origin.x/(itemWidth + space) : 0; // need to check because bounce gives negative values for x.
NSInteger maxRow = rect.size.width/(itemWidth + space) + minRow;
NSMutableArray* attributes = [NSMutableArray array];
for(NSInteger i=0 ; i < self.collectionView.numberOfSections; i++) {
for (NSInteger j=minRow ; j < maxRow; j++) {
NSIndexPath* indexPath = [NSIndexPath indexPathForItem:j inSection:i];
[attributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]];
}
}
return attributes;
}

Change the sequence of uicollectionviewcell

I am trying to change the sequence to cells rendered in collectionview. By default in vertical layout collectionview cells are displayed in
1 2 3
4 5 6
7 8 9
What i am trying to do is change this sequence in following ways
1 2 3
6 5 4 <----- this order is reversed
7 8 9
12 11 10 <----- this order is reversed
Can any one tell me how do i achieve this kind of behaviour.
So far i have implemented UICollectionViewFlowLayout and trying to manipulate its methods but got no success
Note: All items must be loaded in one section only
I found the solution by inheriting UICollectionViewLayout and calculated the desired frame for each item. in prepareLayout method.
Still there are scope for improvement in below code.
-(void)prepareLayout{
NSInteger itemCount = [self.collectionView numberOfItemsInSection:0];
NSMutableDictionary *layoutAttr = [NSMutableDictionary dictionary];
CGFloat y=0,x=0;
for (int i = 0; i<itemCount; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
CGRect frame;
if (indexPath.row == 0) {
x= 0;
y=0;
frame = CGRectMake(x, y, self.itemSize.width, self.itemSize.height);
x += self.itemSize.width;
}else if (indexPath.row % 2 == 0 ) {
y += self.itemSize.height;
x -=self.itemSize.width;
frame = CGRectMake(x, y, self.itemSize.width, self.itemSize.height);
if (x <= 0) {
x += self.itemSize.width;
}else{
x -= self.itemSize.width;
}
}else{
frame = CGRectMake(x, y, self.itemSize.width, self.itemSize.height);
x += self.itemSize.width;
}
attributes.frame=frame;
attributes.zIndex = i;
layoutAttr[indexPath] = attributes;
}
layoutAttributes=layoutAttr;
}
In order to achieve this you will be required to build your own collection view layout. Start by reading the documentation here: https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/CreatingCustomLayouts/CreatingCustomLayouts.html
Otherwise, you might be lucky and find one on http://cocoapods.org/
I made a quick search and came back with nothing.
As suggested by richarddas...
Here's a tutorial:
http://skeuo.com/uicollectionview-custom-layout-tutorial
You should also check out the WWDC video:
http://devstreaming.apple.com/videos/wwdc/2014/232xxz8gxpbstio/232/232_hd_advanced_user_interfaces_with_collection_views.mov?dl=1
Good luck!
How to implement a custom UICollectionFlowLayout
To learn how to get started with Custom Collection View Layouts, I highly recommend this link: http://www.objc.io/issue-3/collection-view-layouts.html
Start by subclassing UICollectionViewLayout and overriding layoutAttributesForItemAtIndexPath:
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
CollectionViewDataSource *dataSource = self.collectionView.dataSource;
id<CollectionViewDataSourceItem> item = [dataSource itemAtIndexPath:indexPath];
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
// here you can customise the frame of your item if it is on a row that is even
if (indexPath.row % 2 == 0)
{
CGFloat horizontalOffset = ???; // change to your new offset, positive or negative
CGRect rect = CGRectOffset( attributes.frame, horizontalOffset, 0);
attributes.frame = rect;
}
return attributes;
}

Dynamically resize UICollectionViewCell in non-scrolling UICollectionView

I have a small, single row horizontal layout UICollectionView at the top of the screen. It can contain up to a maximum of 6 items. The problem is that I want all 6 items visible without scrolling (this collection view is also going to be used in a Today extension which doesn't allow scrolling). What I want to do is reduce the cell-size and inter-item spacing a little bit to allow all 6 cells to fit.
Basically I'm trying to avoid this:
I've been playing with this for a while but I'm not sure how to approach it. I created a method that's fired every time an item is added or removed from the collection view, just before [self.collectionview reloadData] is called.
-(void)setupCollectionViewLayout{
UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout*)self.buttonBarCollectionView.collectionViewLayout;
//Figure out if cells are wider than screen
CGFloat screenwidth = self.view.frame.size.width;
CGFloat sectionInsetLeft = 10;
CGFloat sectionInsetRight = 10;
CGFloat minItemSpacing = flowLayout.minimumInteritemSpacing;
CGSize itemsize = CGSizeMake(58,58);
CGFloat itemsizeWidth = itemsize.width;
CGFloat totalWidth = sectionInsetLeft + sectionInsetRight +
(itemsizeWidth * _armedRemindersArray.count) +
(minItemSpacing * (_armedRemindersArray.count -2));
CGFloat reductionAmount = itemsizeWidth;
if (totalWidth > screenwidth) {
while (totalWidth > screenwidth) {
totalWidth = totalWidth - 1;
reductionAmount = reductionAmount - 1;
}
CGSize newCellSize = CGSizeMake(reductionAmount, reductionAmount);
flowLayout.itemSize = newCellSize;
}
else flowLayout.itemSize = itemsize;
}
This is the result.
Not exactly what I was expecting. Not only did it squash everything to the left and also added a second line, but I also seem to have a cell-reuse issue. Truthfully I would just use static-cells if it was even an option, but unfortunately it seems like it's not possible.
What should I be doing? Subclassing UICollectionViewFlowLayout? Won't that basically do the same thing I'm doing here with the built-in flow layout?
EDIT:
Kujey's answer is definitely closer to what I need. I still have a cell-reuse issue though.
Xcode provides an object designed for your need. It's called UICollectionViewFlowLayout and all you need to do is subclass it and place your cells the way you want. The function prepareForLayout is call every time the collection view needs to update the layout.
The piece of code you need is below :
#import "CustomLayout.h"
#define MainCell #"MainCell"
#interface CustomLayout ()
#property (nonatomic, strong) NSMutableDictionary *layoutInfo;
#end
#implementation CustomLayout
-(NSMutableDictionary *) layoutInfo
{
if (!_layoutInfo) {
_layoutInfo = [NSMutableDictionary dictionary];
}
return _layoutInfo;
}
-(void) prepareLayout
{
NSMutableDictionary *cellLayoutInfo = [NSMutableDictionary dictionary];
NSIndexPath *indexPath;
CGFloat itemWidth;
CGFloat itemSpacing;
CGFloat widthWithoutSpacing = [self collectionViewContentSize].width / ([self.collectionView numberOfItemsInSection:0]);
if (widthWithoutSpacing > [self collectionViewContentSize].height) {
itemWidth = [self collectionViewContentSize].height;
itemSpacing = ([self collectionViewContentSize].width - itemWidth*[self.collectionView numberOfItemsInSection:0])/
([self.collectionView numberOfItemsInSection:0]+1);
}
else {
itemWidth = widthWithoutSpacing;
itemSpacing = 0;
}
CGFloat xPosition = itemSpacing;
for (NSInteger section = 0; section < [self.collectionView numberOfSections]; section++) {
for (NSInteger index = 0 ; index < [self.collectionView numberOfItemsInSection:section] ; index++) {
indexPath = [NSIndexPath indexPathForItem:index inSection:section];
UICollectionViewLayoutAttributes *itemAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
CGRect currentFrame=itemAttributes.frame;
currentFrame.origin.x = xPosition;
currentFrame.size.width = itemWidth;
currentFrame.size.height = itemWidth;
itemAttributes.frame=currentFrame;
cellLayoutInfo[indexPath] = itemAttributes;
xPosition += itemWidth + itemSpacing;
}
}
self.layoutInfo[MainCell] = cellLayoutInfo;
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray *allAttributes = [NSMutableArray arrayWithCapacity:self.layoutInfo.count];
[self.layoutInfo enumerateKeysAndObjectsUsingBlock:^(NSString *elementIdentifier, NSDictionary *elementsInfo, BOOL *stop) {
[elementsInfo enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *indexPath, UICollectionViewLayoutAttributes *attributes, BOOL *innerStop) {
if (CGRectIntersectsRect(rect, attributes.frame)) {
[allAttributes addObject:attributes];
}
}];
}];
return allAttributes;
}
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
return self.layoutInfo[MainCell][indexPath];
}
-(CGSize) collectionViewContentSize
{
return self.collectionView.frame.size;
}
#end
You can also change the y origin of your cells if you need to center them vertically.
try with this code. I get the width and use _armedRemindersArray (i guess you use this array for the items).
-(void)setupCollectionViewLayout{
UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout*)self.buttonBarCollectionView.collectionViewLayout;
//Figure out if cells are wider than screen
CGFloat screenwidth = self.view.frame.size.width;
CGFloat width = screenwidth - ((sectionInsetLeft + sectionInsetRight) *_armedRemindersArray.count + minItemSpacing * (_armedRemindersArray.count -2));
CGSize itemsize = CGSizeMake(width,width);
flowLayout.itemSize = itemsize;
}
I don't know why you're setting the itemsize first, and then reducing it. I think you should do it the other way around:
-(void)setupCollectionViewLayout{
UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout*)self.buttonBarCollectionView.collectionViewLayout;
CGFloat sectionInsetLeft = 10;
CGFloat sectionInsetRight = 10;
CGFloat minItemSpacing = flowLayout.minimumInteritemSpacing;
// Find the appropriate item width (<= 58)
CGFloat screenwidth = self.view.frame.size.width;
CGFloat itemsizeWidth = (screenwidth - sectionInsetLeft - sectionInsetRight - (minItemSpacing * (_armedRemindersArray.count -2))) / _armedRemindersArray.count
itemsizeWidth = itemsizeWidth > 58 ? 58 : itemsizeWidth;
flowLayout.itemSize = CGSizeMake(itemsizeWidth, itemsizeWidth);
}
Does this work? If not, could you please include more of your code?

iOS - Creating a SpringBoard screen

I'm trying so create a collection view that behaves like the iOS springboard screen.
That means that I want to have a collection view, with all of my items, and to have a page controller underneath the grid (just like the springboard screen). I also want to implement the same edit mode, meaning that one my icons are in edit mode, I can drag them from one page to another, in such a way that a page doesn't have to be full.
I've tried to set a single collection view to scroll horizontally, but it doesn't act as I want. first of all, the icons are added one below the other, instead of one beside the other. secondly, I don't have the page dots this way, and the user can't put icons on different pages
I also tried to set a page controller, and put in every page the collection view. In this case, I get that while in edit mode, I can't go between screens. I guess that is because I have more that one collection view, and they don't interact with each other.
I though of another way, of using a scroll view, and try to put a collection view inside that scroll view. But it sounds to me like a very not elegant solution.
Does anyone have an idea about how to create such a scene?
Edit:
I think the answer for my problem is hiding in this question. It describes the same situation I want to have: a collection view, that is scrolled horizontally, with paging, and that the order of the icons is not flowing vertically, but also flowing horizontally. There is a partially good answer there. I say it's partially good since the answer describes a subclass of UICollectionViewFlowLayout, but in the sample project uploaded the subclass is of UICollectionViewLayout, and I think the whole point was to maintain the functions that the UICollectionViewFlowLayout has.
This question was asked a year ago, so I believe someone has an answer, or at least, I hope.
I have partial solution to my question.
I did use the answer (of Bogdan) to the linked question above. Here's the code:
layout.h:
#import <UIKit/UIKit.h>
#interface V9Layout : UICollectionViewLayout
#property (nonatomic, assign) NSInteger itemsInOneRow;
#property (nonatomic, assign) NSInteger itemsInOneCol;
#property (nonatomic, assign) CGSize itemSize;
#end
layout.m:
#import "V9Layout.h"
#interface V9Layout()
-(void)calculateLayoutProperties;
-(int)pagesInSection:(NSInteger)section;
#property (nonatomic, strong) NSMutableArray *frames;
#property (nonatomic, assign) CGFloat lineSpacing;
#property (nonatomic, assign) CGFloat interitemSpacing;
#property (nonatomic, assign) CGSize pageSize;
#end
#implementation V9Layout
#synthesize itemsInOneRow = _itemsInOneRow, itemsInOneCol = _itemsInOneCol, itemSize = _itemSize;
#synthesize lineSpacing = _lineSpacing, interitemSpacing = _interitemSpacing;
#synthesize frames = _frames;
- (id)init
{
self = [super init];
if (self) {
[self setup];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
[self setup];
}
return self;
}
- (void)setup
{
self.itemsInOneRow = 3;
self.itemsInOneCol = 4;
self.frames = [NSMutableArray array];
}
-(void)calculateLayoutProperties {
self.pageSize = self.collectionView.bounds.size;
// The width of all line spacings is equal to width of ONE item. The same for interitem spacing
CGFloat itemWidth = self.pageSize.width / (self.itemsInOneRow + 1); // +1 because we all spaces make width of one additional item
CGFloat itemHeight = self.pageSize.height / (self.itemsInOneCol + 1); // the same
NSLog(#"calculateLayoutProperties item width: %f, item height: %f", itemWidth, itemHeight);
self.itemSize = CGSizeMake(itemWidth, itemHeight);
// this part is only to make a global parameter, because I'm using this data to create the size of the UICollectionViewCell. I think I need to use a delegate method for the UICollectionViewCell to ask the UICollectionViewLayout for the calculated size of cell.
NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
if (standardUserDefaults) {
[standardUserDefaults setObject:[NSNumber numberWithFloat:itemWidth] forKey:#"cellWidth"];
[standardUserDefaults setObject:[NSNumber numberWithFloat:itemHeight] forKey:#"cellHeight"];
[standardUserDefaults synchronize];
}
// spacing for x
self.lineSpacing = (self.pageSize.width - (self.itemsInOneRow * self.itemSize.width)) / (self.itemsInOneRow + 1);
// spacing for y
self.interitemSpacing = (self.pageSize.height - (self.itemsInOneCol * self.itemSize.height)) / (self.itemsInOneCol + 1);
}
-(int)pagesInSection:(NSInteger)section {
NSLog(#"v9layout: numberOfItemsInSection is %ld", (long)[self.collectionView numberOfItemsInSection:section]);
return ([self.collectionView numberOfItemsInSection:section] - 1) / (self.itemsInOneRow * self.itemsInOneCol) + 1;
}
-(CGSize)collectionViewContentSize {
// contentSize.width is equal to pages * pageSize.width
NSInteger sections = 1;
if ([self.collectionView respondsToSelector:#selector(numberOfSections)]) {
sections = [self.collectionView numberOfSections];
}
int pages = 0;
for (int section = 0; section < sections; section++) {
pages = pages + [self pagesInSection:section];
}
return CGSizeMake(pages * self.pageSize.width, self.pageSize.height);
}
-(void)prepareLayout {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self calculateLayoutProperties];
});
[self.frames removeAllObjects];
NSInteger sections = 1;
if ([self.collectionView respondsToSelector:#selector(numberOfSections)]) {
sections = [self.collectionView numberOfSections];
}
int pagesOffset = 0; // Pages that are used by prevoius sections
int itemsInPage = self.itemsInOneRow * self.itemsInOneCol;
for (int section = 0; section < sections; section++) {
NSMutableArray *framesInSection = [NSMutableArray array];
int pagesInSection = [self pagesInSection:section];
int itemsInSection = [self.collectionView numberOfItemsInSection:section];
for (int page = 0; page < pagesInSection; page++) {
int itemsToAddToArray = itemsInSection - framesInSection.count;
int itemsInCurrentPage = itemsInPage;
if (itemsToAddToArray < itemsInPage) { // If there are less cells than expected (typically last page of section), we go only through existing cells.
itemsInCurrentPage = itemsToAddToArray;
}
for (int itemInPage = 0; itemInPage < itemsInCurrentPage; itemInPage++) {
CGFloat originX = (pagesOffset + page) * self.pageSize.width + self.lineSpacing + (itemInPage % self.itemsInOneRow) * (self.itemSize.width + self.lineSpacing);
CGFloat originY = self.interitemSpacing + (itemInPage / self.itemsInOneRow) * (self.itemSize.height + self.interitemSpacing);
CGRect itemFrame = CGRectMake(originX, originY, self.itemSize.width, self.itemSize.height);
[framesInSection addObject:NSStringFromCGRect(itemFrame)];
}
}
[self.frames addObject:framesInSection];
pagesOffset += pagesInSection;
}
}
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSMutableArray *attributes = [NSMutableArray array];
NSInteger sections = 1;
if ([self.collectionView respondsToSelector:#selector(numberOfSections)]) {
sections = [self.collectionView numberOfSections];
}
int pagesOffset = 0;
int itemsInPage = self.itemsInOneRow * self.itemsInOneCol;
for (int section = 0; section < sections; section++) {
int pagesInSection = [self pagesInSection:section];
int itemsInSection = [self.collectionView numberOfItemsInSection:section];
for (int page = 0; page < pagesInSection; page++) {
CGRect pageFrame = CGRectMake((pagesOffset + page) * self.pageSize.width, 0, self.pageSize.width, self.pageSize.height);
if (CGRectIntersectsRect(rect, pageFrame)) {
int startItemIndex = page * itemsInPage;
int itemsInCurrentPage = itemsInPage;
if (itemsInSection - startItemIndex < itemsInPage) {
itemsInCurrentPage = itemsInSection - startItemIndex;
}
for (int itemInPage = 0; itemInPage < itemsInCurrentPage; itemInPage++) {
UICollectionViewLayoutAttributes *itemAttributes = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:startItemIndex + itemInPage inSection:section]];
if (CGRectIntersectsRect(itemAttributes.frame, rect)) {
[attributes addObject:itemAttributes];
}
}
}
}
pagesOffset += pagesInSection;
}
return attributes;
}
-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attributes.frame = CGRectFromString(self.frames[indexPath.section][indexPath.item]);
return attributes;
}
#end
This implement the horizontal paging with order of cells one beside each other and not one underneath each other.
I still want to add a drag and drop ability so that the user can change the order of cells. I poset this question about it.
Hopefully, I'll find answer to this and update this answer.

Resources