iOS : Working with collection view layouts
In this blog we are going to discuss about “Collection View Custom Layouts” and “Switching between two collection view layouts” in iOS development.
First of all we will know about Collection View Layouts.
What Is a Layout?
UICollectionViewLayout is an abstract class that should not be created itself; its only purpose is to be subclassed. Each collection view has a layout associated with it whose job it is to lay content out. Layouts are not concerned with the data contained in the views they lay out. they are only interested in their layout to the user. We can use UICollectionViewFlowLayout and UICollectionViewLayout to create our own layouts. To use layout we need to create subclass of layout class.
How it works?
First, the collection view interrogates its data source for information about the contents to be displayed to the user. This includes the number of sections and the number of items and supplementary views in each individual section.
Next, the collection view gathers information from its layout object about how to display the cells, supplementary views, and decoration views. This information is stored in instances of a class called UICollectionViewLayoutAttributes.
Finally, the collection view forwards information about the layout to the cells, supplementary views, and decoration views. Each of these classes is responsible for using the information it has been given to apply those layout attributes to itself. Deferring to the superclass’s implementation, or omitting an implementation entirely, will ensure that the layout attributes already handled by the collection view, like frame, are applied. Your implementations should concentrate on any custom attributes that you’ve added.
These steps occur whenever the existing layout is invalidated, which you can force by calling invalidateLayout on the layout object.
Now we are aware of the different classes used in laying content out:
UICollectionView: which is the view that presents content to the user.
UICollectionViewCell: which is responsible for displaying one unit of content to a user at a time.
UICollectionViewLayout: which determines the attributes of items and returns that information to the collection view.
UICollectionViewLayoutAttributes: which is a class in which the layout stores information to be marshalled to the cells, supplementary views, and decoration views. The class contains the following properties, which are applied to items at runtime:
1. Frame (convenience property for centre and size)
2. Center
3. Size
4. 3D Transform
5. Alpha (opacity)
6. Z-index
7. Hidden
8. Element category (cell, supplementary view, or decoration view)
9. Element kind (nil for cells)
Now we will create two layouts of collection view and switching between them.
Steps :
1. Create a project
————————————————————————————————————————————
2. Create Controller which will extends UICollectionViewController
ViewController code includes following code
[code language=”objc”]
#import "ViewController.h"
#import "CollectionViewFlowLayout.h"
#import "CollectionViewCircleLayout.h"
#import "CollectionViewCell.h"
static NSString *CellIdentifier = @"CellIdentifier";
@interface ViewController ()
@property (nonatomic,assign) NSInteger cellCount;
@property (nonatomic, strong) CollectionViewCircleLayout *circleLayout;
@property (nonatomic, strong) CollectionViewFlowLayout *flowLayout;
@property (nonatomic, strong) UISegmentedControl *layoutChangeSegmentedControl;
@end
@implementation ViewController
– (void)viewDidLoad {
[super viewDidLoad];
self.layoutChangeSegmentedControl = [[UISegmentedControl alloc] initWithItems:@[@"Circle Layout", @"Flow Layout"]];
self.layoutChangeSegmentedControl.selectedSegmentIndex = 0;
[self.layoutChangeSegmentedControl addTarget:self action:@selector(layoutChangeSegmentedControlDidChangeValue:) forControlEvents:UIControlEventValueChanged]; self.navigationItem.titleView = self.layoutChangeSegmentedControl;
self.circleLayout = [[CollectionViewCircleLayout alloc] init];
self.flowLayout = [[CollectionViewFlowLayout alloc] init];
self.collectionView.collectionViewLayout = self.circleLayout;
// Register our classes so we can use our custom subclassed // cell and header
[self.collectionView registerClass:[CollectionViewCell class]
forCellWithReuseIdentifier:CellIdentifier];
self.cellCount = 20;
}
// Do any additional setup after loading the view, typically from a nib.
– (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void)layoutChangeSegmentedControlDidChangeValue:(id)sender
{
// We need to explicitly tell the collection view layout
// that we want the change animated.
if (self.collectionView.collectionViewLayout == self.circleLayout)
{
[self.flowLayout invalidateLayout];
[self.collectionView setCollectionViewLayout:self.flowLayout animated:YES];
}else
{
[self.circleLayout invalidateLayout];
[self.collectionView setCollectionViewLayout:self.circleLayout
animated:YES];
}
}
– (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section;
{
return self.cellCount;
}
-(UICollectionViewCell *)collectionView: (UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath;
{
CollectionViewCell *cell = (CollectionViewCell *)
[collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
[cell setLabelString:[NSString stringWithFormat:@"%ld", (long)indexPath.row]];
return cell;
}
@end
[/code]
————————————————————————————————————————————
3. Create Simple Flow Layout : Will extend “UICollectionViewFlowLayout”
[code language=”objc”]
#import "CollectionViewFlowLayout.h"
@implementation CollectionViewFlowLayout
-(id)init
{
self = [super init];
if(self)
{
self.itemSize = CGSizeMake(60, 60);
self.sectionInset = UIEdgeInsetsMake(13.0f, 13.0f, 13.0f, 13.0f); self.minimumInteritemSpacing = 13.0f;
self.minimumLineSpacing = 13.0f;
}
return self;
}
@end
[/code]
———————————————————————————————————————————————————————————————————————
4. Create Circle Layout : Will extend “UICollectionViewLayout”
[code language=”objc”]
#import "CollectionViewCircleLayout.h"
#define kItemDimension 60
@interface CollectionViewCircleLayout ()
@property(nonatomic,assign)NSInteger cellCount;
@property(nonatomic,assign)CGPoint center;
@property(nonatomic,assign)NSInteger radius;
@end
@implementation CollectionViewCircleLayout
// optional method called every time collection view reload data or collection view invalidate layout.
// Here we will do some useful tasks which are needed before loading collection view cells.
// 1st method which is called before loading of collection view.
-(void)prepareLayout
{
[super prepareLayout];
CGSize size = self.collectionView.bounds.size;
self.cellCount = [[self collectionView] numberOfItemsInSection:0];
self.center = CGPointMake(size.width / 2.0, size.height / 2.0);
self.radius = MIN(size.width, size.height) / 2.5;
}
// 2nd method which is called after prepareLayout
-(CGSize)collectionViewContentSize
{
// calculate here for collection view content size. Try to avoid it.
CGRect bounds = [[self collectionView] bounds];
return bounds.size;
}
// If the layout supports any supplementary or decoration view types, it should also implement the respective atIndexPath: methods for those types.
// 3rd method which is called after collectionViewContentSize
//- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
//{
//
//}
– (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path
{
UICollectionViewLayoutAttributes* attributes =
[UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:path];
attributes.size = CGSizeMake(kItemDimension, kItemDimension);
attributes.center = CGPointMake(self.center.x + self.radius * cosf(2 * path.item * M_PI / self.cellCount – M_PI_2), self.center.y + self.radius * sinf(2 * path.item * M_PI / self.cellCount – M_PI_2));
attributes.transform3D = CATransform3DMakeRotation((2 * M_PI * path.item / self.cellCount), 0, 0, 1);
return attributes;
}
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray* attributes = [NSMutableArray array];
for (NSInteger i = 0 ; i < self.cellCount; i++)
{
NSIndexPath* indexPath = [NSIndexPath
indexPathForItem:i inSection:0];
[attributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]];
}
return attributes;
}
@end
[/code]
Below is the flow of layout :
1. prepareLayout is called on the layout so it has an opportunity to perform any up- front computations.
2. collectionViewContentSize is called on the layout to determine the collection view’s content size.
3. layoutAttributesForElementsInRect: is called.
Then, the layout becomes live and continues to call layoutAttributesForElementsInRect: and layoutAttributesForItemAtIndexPath: until the layout becomes invalidated. Then, the process is repeated again.
————————————————————————————————————————
5. Create collection view cell class
[code language=”objc”]
#import "CollectionViewCell.h"
@interface CollectionViewCell ()
@property (nonatomic, strong) UILabel *label;
@end
@implementation CollectionViewCell
– (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
self.backgroundColor = [UIColor orangeColor];
self.label = [[UILabel alloc] initWithFrame:self.contentView.bounds];
self.label.backgroundColor = [UIColor clearColor]; self.label.textAlignment = NSTextAlignmentCenter; self.label.textColor = [UIColor whiteColor]; self.label.font = [UIFont boldSystemFontOfSize:24];
[self.contentView addSubview:self.label];
}
return self;
}
-(void)prepareForReuse
{
[super prepareForReuse];
[self setLabelString:@""];
}
-(void)setLabelString:(NSString *)labelString
{
self.label.text = labelString;
}
//***********************************************************************
#pragma mark –
#pragma mark – Cell layout related methods
#pragma mark –
//***********************************************************************
/**
* Called when layout changed of a cell. This can be used to change cell layout
* according to current layout.
* @author Harish
*
* @param layoutAttributes : attributes of cell in case using custom layouts
* @return nil
*/
-(void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
[super applyLayoutAttributes:layoutAttributes];
self.label.center = CGPointMake( CGRectGetWidth(self.contentView.bounds) / 2.0f, CGRectGetHeight(self.contentView.bounds) / 2.0f);
}
@end
[/code]
Now run project and You will get something like :