Collection View building blocks
UICollectionView is very similar to UITableView. The UICollectionView has a delegate and dataSource protocols and a UICollectionViewLayout object that provides essential information for the collection's construction and workflow.1. Class Structure |
Flow Layout
What is UICollectionFlowLayout ?
Flow layout object provides a customizable way to organize the cells in the collection view. Layout objects in general are designed to calculate and provide specific attributes to cells and decoration views.2. FlowLayout's arrangement of the view elements in the collectionView Courtesy of Apple.com |
3. notice the count |
When to use UICollectionViewFlowLayout ?
Flow layout is useful if you want to display your objects in a grid, single file/line anything that has a linear flow to it could be implemented using the flow layout.Configuring flow layout
Flow layout allows you to configure:
- scrollDirection: horizontal or vertical
- line spacings, which you can see in the example above that a minimum spacing is maintained.
- item size
- header and footer reference sizes
4. header and footers for each section |
FlowLayoutExample.h
@interface FlowLayoutExample : UIViewController <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
@property (retain, nonatomic) IBOutlet UICollectionView *collectionView;@endStraight forward as you can see, my view controller implements the UICollectionViewDataSource and the UICollectionViewDelegateFlowLayout for the layout and datasource. And we have an outlet for our UICollectionView.
lets configure our collection view
FlowLayoutExample.m
- (void)viewDidLoad{[super viewDidLoad];self.collectionView.dataSource = self;//1self.collectionView.delegate = self;//1//register cell[self.collectionView registerClass:[SimpleCell class] forCellWithReuseIdentifier:@"SIMPLE_CELL_ID"];//register headerUINib *headerNib = [UINib nibWithNibName:@"HeaderView" bundle:nil];[self.collectionView registerNib:headerNib forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"HEADER_ID"];//register footerUINib *footerNib = [UINib nibWithNibName:@"FooterView" bundle:nil];[self.collectionView registerNib:footerNib forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"FOOTER_ID"];//adjust layoutUICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout*)self.collectionView.collectionViewLayout;layout.minimumLineSpacing = 20.0;layout.itemSize = CGSizeMake(100, 100);layout.sectionInset = UIEdgeInsetsMake(20, 20, 40, 20);layout.headerReferenceSize = CGSizeMake(50, 65);layout.footerReferenceSize = CGSizeMake(50, 75);}Lets go through this:1 - set the collectionView's delegate and dataSource to be my viewController's responsibilities. we declared in the header that we'll be implementing these protocols.2- registering a class for the cell, in this step we tell the UICollectionView which class will be our cell made of and assign it a reuse identifier. UICollectionView will later instantiate cells of that class when we need them.3- register header, just to demonstrate the different ways I registered the header using a Nib and not a class. UICollectionView allows you either. You notice that we registered this header for: SupplementaryViewOfKind:UICollectionElementKindSectionHeader. UICollectionView enables you to display some meta data on your cells and FlowLayout specifically comes equipped with header and footer. As you build your own layouts you can define more supplementary views of your own.4- register footer, same as the header.5- adjust layout. here we tweak our layout objectUICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout*)self.collectionView.collectionViewLayout;Since we never assigned the layout object our selfs, it was assigned for us in the interface builder:
5. layout config in Xib |
Layout: Flow layout
Scroll Direction: Vertical
Also you could setup the Header & Footer from Xib to some degree here.
you can see that we configured the layout such that it keeps a minimum 20 px between the lines. We set each item to be of size 100x100, which later you can change on a per item basis in a method called: collectionView:layout:sizeForItemAtIndexPath: where you can give a specific size to each item.
you can also set the insets of each section using: sectionInset property.
Important note about the header/footer reference size, when the flow layout scrolls vertically the layout will use the height value to keep the header/footer in that height and stretch the footer to the collection widths. when scrolling horizontally it will do exactly the opposite, it will take the width and stretch on the height.
DataSource implementation:
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 50;
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return 3;
}
As you can see we have 3 sections with 50 items in each section.
Now we'll need to implement a method for the cells:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
SimpleCell *cell = (SimpleCell *)[collectionView dequeueReusableCellWithReuseIdentifier:@"SIMPLE_CELL_ID" forIndexPath:indexPath];
cell.label = [NSString stringWithFormat:@"%d,%d", indexPath.section, indexPath.row];
cell.transform = CGAffineTransformMakeRotation(arc4random()%1*M_2_PI);
return cell;
}
So we dequeue a reusable cell with identifier SIMPLE_CELL_ID, same as the cell class we registered before. The cool thing about the UICollectionView is that you dont need to check the if the dequeued cell is null , the collection will instantiate one for you in that case automatically.
Lets provide some final implementation for the supplementary elements:
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
HeaderView *header = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"HEADER_ID" forIndexPath:indexPath];
header.label.text = [NSString stringWithFormat:@"Header for section: %d", indexPath.section];
return header;
}
else if ([kind isEqualToString:UICollectionElementKindSectionFooter]){
UICollectionReusableView *footer = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"FOOTER_ID" forIndexPath:indexPath];
return footer;
}
return nil;
}
same logic as with the cells, we dequeue a supplementary element based on the kind of element and configure it as needed.
The result will look like image 4 above.
Customizing your own layout
- (void)prepareLayout
This method is called before the layout is drawn on screen to allow you to prepare any calculations ahead of times.
prepareLayout will also be called when ever the layout is invalidated. Invalidating the layout will throw away all the existing layout information and forces the layout object to start over again. the layout can call this on itself or can trigger this automatically when the bounds change, see shouldInvalidateLayoutForBoundsChange for more details.
- (CGSize)collectionViewContentSize
the second step is to figure out our content size area. here you will tell the UICollectionView what is the size of the content area you will be needing to display all your cells and decorations.
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
the last step is supplying an array of layout attributed for the displayed cells in the provided rect.
Since UICollectionView is a subclass of a UIScrollView displays only portion of the items and allows you to scroll to see the rest of the elements. so at any time there is only a certain area provided by the rect that is visible and needing the layout attributes.
6. layout process Curtesy of Apple.com |
UICollectionViewLayoutAttributes
The layout uses objects of class UICollectionViewLayoutAttributes (for short layout attributes) to describe the specific layout attributes for a given item in a collection view. the layout object will create layout attributes as needed by the collection view. Layout attributes include attributes such as frame, center, size, transform3D, alpha, zIndex and hidden.
zIndex is especially useful when cells overlap and you need to provide some order on the z-axis, deciding which cell is displayed on top.
Circular layout Example
One of the cool demonstrations at Apple's WWDC 2012 was a circular layout.
Its a layout where the cells are displayed in a circle in the middle of the collection view.
for this layout to work a few things are required from our new custom layout object.
we need to know the number of items displayed so we can calculate the angle increment between each cell. which should be done in the prepareLayout method:
- (void)prepareLayout {
[super prepareLayout];
//calculate the degree increment
//360 / items_count
if (self.numItems) {
angle = (CGFloat)2*M_PI/self.numItems;
}
else{
angle = 0;
}
}
Next, we need to provide layout attributes for each cell in the visible rect area. Since we want to display all the cells at once for this demo, we'll provide layout attributes for all of them.
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
if (self.numItems == 0) {
return nil;
}
CGRect collectionFrame = self.collectionView.frame;
CGPoint center = CGPointMake(collectionFrame.size.width/2, collectionFrame.size.height/2);
CGFloat R = fminf(collectionFrame.size.width, collectionFrame.size.height)/2.0 - 100;
NSMutableArray *array = [NSMutableArray new];
CGFloat x,y,alpha;
UICollectionViewLayoutAttributes *attrs;
int z=0;
for (int i=0; i<self.numItems; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
alpha = i * angle;
x = floorf(R * cosf(alpha) + center.x);
y = floorf(R * cosf(M_PI_2-alpha) + center.y);
attrs.center = CGPointMake(x, y);
attrs.size = CGSizeMake(100, 100);
if ((alpha > 0 && alpha <= M_PI_2) || (alpha > 1.5*M_PI && alpha <= 2*M_PI)) {
attrs.zIndex = z++;
}
else if (alpha > M_PI_2 && alpha <= 1.5*M_PI)
{
attrs.zIndex = z--;
}
[array addObject:attrs];
}
NSArray *result = [NSArray arrayWithArray:array];
[array release];
return result;
}
So above you will see that I iterate over all the items and calculate the (x,y) coordinates of that item on the circle plus some normalization for the items to be positioned in the collection view and then some zIndex calculation to make sure that the cells overlap in a nice way.
Feel free to download the demo project from GitHub at:
https://github.com/edwardIshaq/CollectionViewDemo
No comments:
Post a Comment