I have to build a UICollectionView scrollable horizontal and vertical, I know that the grid layout scrolls along one axis only, either horizontally or vertically, so I have read some posts and I have tried different solutions but the most simple is to put the UICollectionview inside a UIScrollView. In this way the CollectionView scroll vertically and the UIScrollView horizontally. The problem is that the vertical scroll is difficult, not fluid and often is stop until you tap again and drag again. Can you suggest a solution? Thanks
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
UIScrollView *backgroundScroll = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)];
backgroundScroll.scrollEnabled = YES;
[self.view addSubview:backgroundScroll];
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(10, 15, 1020, [UIScreen mainScreen].bounds.size.height - 35) collectionViewLayout:layout];
[backgroundScroll addSubview:_collectionView];
_collectionView.contentInset = UIEdgeInsetsMake(0, 0, 50, 0);
_collectionView.scrollEnabled = YES;
And I have implemented the method:
- (void)viewDidLayoutSubviews {
backgroundScroll.contentSize = self.collectionView.frame.size;
}
The way to do this is to create a custom UICollectionViewLayout subclass.
I had to do this recently.
Let me go get the files... One sec...
First of all, you can't use a subclass of UICollectionViewFlowLayout easily for this. Flow layout is designed to fit the content in one direction and scroll in the other direction. This isn't what you want.
It isn't very difficult though to create a custom layout to do this for you.
Header File
@interface GridCollectionViewLayout : UICollectionViewLayout
// properties to configure the size and spacing of the grid
@property (nonatomic) CGSize itemSize;
@property (nonatomic) CGFloat itemSpacing;
// this method was used because I was switching between layouts
- (void)configureCollectionViewForLayout:(UICollectionView *)collectionView;
@end
Implementation
#import "GridCollectionViewLayout.h"
@interface GridCollectionViewLayout ()
@property (nonatomic, strong) NSDictionary *layoutInfo;
@end
@implementation GridCollectionViewLayout
Create inits for code and interface builder...
- (id)init
{
self = [super init];
if (self) {
[self setup];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
[self setup];
}
return self;
}
Setup defaults property values...
- (void)setup
{
self.itemSize = CGSizeMake(50.0, 50.0);
self.itemSpacing = 10.0;
}
This was used because I was changing between different layouts but it shows what is needed to set the layout..
- (void)configureCollectionViewForLayout:(UICollectionView *)collectionView
{
collectionView.alwaysBounceHorizontal = YES;
[collectionView setCollectionViewLayout:self animated:NO];
}
Required method. This iterates the items and creates frames CGRect for each one. Saving them into a dictionary.
- (void)prepareLayout
{
NSMutableDictionary *cellLayoutInfo = [NSMutableDictionary dictionary];
NSInteger sectionCount = [self.collectionView numberOfSections];
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
for (NSInteger section = 0; section < sectionCount; section++) {
NSInteger itemCount = [self.collectionView numberOfItemsInSection:section];
for (NSInteger item = 0; item < itemCount; item++) {
indexPath = [NSIndexPath indexPathForItem:item inSection:section];
UICollectionViewLayoutAttributes *itemAttributes =
[UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
itemAttributes.frame = [self frameForAssessmentAtIndexPath:indexPath];
cellLayoutInfo[indexPath] = itemAttributes;
}
}
self.layoutInfo = cellLayoutInfo;
}
This is a convenience method for quickly getting a frame at a given index.
- (CGRect)frameForIndexPath:(NSIndexPath *)indexPath
{
NSInteger column = indexPath.section;
NSInteger row = indexPath.item;
CGFloat originX = column * (self.itemSize.width + self.itemSpacing);
CGFloat originY = row * (self.itemSize.height + self.itemSpacing);
return CGRectMake(originX, originY, self.itemSize.width, self.itemSize.height);
}
Required method to calculate the content size. This just multiplies the number of sections or items by the size and spacing properties. This is what allows scrolling in both directions because the content size can be bigger than the collection view's width AND height.
- (CGSize)collectionViewContentSize
{
NSInteger sectionCount = [self.collectionView numberOfSections];
if (sectionCount == 0) {
return CGSizeZero;
}
NSInteger itemCount = [self.collectionView numberOfItemsInSection:0];
CGFloat width = (self.itemSize.width + self.itemSpacing) * sectionCount - self.itemSpacing;
CGFloat height = (self.itemSize.height + self.itemSpacing) * itemCount - self.itemSpacing;
return CGSizeMake(width, height);
}
Required methods. These tell the collection view where each item needs to be placed.
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
return self.layoutInfo[indexPath];
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray *allAttributes = [NSMutableArray array];
[self.layoutInfo enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *indexPath, UICollectionViewLayoutAttributes *attributes, BOOL *stop) {
if (CGRectIntersectsRect(attributes.frame, rect)) {
[allAttributes addObject:attributes];
}
}];
return allAttributes;
}
@end
Of course, the layout in this case is specific to my individual problem.
The layout worked by having each section be a column and the items in each section were the rows. So something like this...
xy = item y in section x
00 10 20 30 ...
01 11 21 31 ...
02 12 22 32 ...
. . . .
. . . .
. . . .
Obviously there can be an unlimited number of sections or items in sections so I had to have scrolling in both directions.
Once you have created your layout class you just need to set it as the layout for your collection view. You can do this in code collectionView.collectionViewLayout = myLayout or you can do it in Interface Builder with the "layout" property on the collection view.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With