Kudit
Kudit

Reputation: 4379

Can I have a UICollectionViewFlowLayout with scrollDirection different from layout direction?

The UICollectionViewFlowLayout is great, however, I want it tweaked slightly so that the scrollDirection is different from the layout direction. Examples of what I'd like is the springboard home screen or the emoji keyboards where you can swipe left/right, but the cells are laid out left to right, top to bottom (instead of top to bottom, left to right as they are with the UICollectionViewFlowLayout with scrollDirection set to horizontal). Anyone know how I can easily subclass the FlowLayout to make this change? I'm disappointed they don't have a layoutDirection in addition to scrollDirection on the UICollectionViewFlowLayout object :(

@rdelmar I implemented something similar to your solution, and that works (not as elegant as I'd like), but I noticed that occasionally items disappear from the collection unless re-drawn, which is why I didn't accept the answer. Here is what I ended up with:

@interface UICollectionViewPagedFlowLayout : UICollectionViewFlowLayout
@property int width;
@property int height;
@end
@implementation UICollectionViewPagedFlowLayout
#warning MINOR BUG: sometimes symbols disappear
- (CGSize)collectionViewContentSize {
    CGSize size = [super collectionViewContentSize];
    // make sure it's wide enough to cover all the objects
    size.width = self.collectionView.bounds.size.width * [self.collectionView numberOfSections];
    return size;
}
- (NSArray*) layoutAttributesForElementsInRect:(CGRect)rect {
    NSArray *array = [super layoutAttributesForElementsInRect:rect];
    CGRect visibleRect;
    visibleRect.origin = self.collectionView.contentOffset;
    visibleRect.size = self.collectionView.bounds.size;

    for (UICollectionViewLayoutAttributes* attributes in array) {
        if (CGRectIntersectsRect(attributes.frame, rect)) {
            // see which section this is in
            CGRect configurableRect = UIEdgeInsetsInsetRect(visibleRect, self.sectionInset);
            int horizontalIndex = attributes.indexPath.row % self.width;
            int verticalIndex = attributes.indexPath.row / self.width;
            double xspace = (configurableRect.size.width - self.width * self.itemSize.width) / self.width;
            double yspace = (configurableRect.size.height - self.height * self.itemSize.height) / self.height;
            attributes.center = CGPointMake(attributes.indexPath.section * visibleRect.size.width + self.sectionInset.left + (self.itemSize.width + xspace) * horizontalIndex + self.itemSize.width / 2, self.sectionInset.top + (self.itemSize.height + yspace) * verticalIndex + self.itemSize.height / 2);
        }
    }
    return array;
}
@end

Upvotes: 1

Views: 2748

Answers (3)

Kudit
Kudit

Reputation: 4379

Until Apple adds a simple flag to change the layout orientation on flow layouts, I ended up sub-classing. Some of the stuff is specific to my implementation, but it might help someone else:

@interface UICollectionViewPagedFlowLayout : UICollectionViewFlowLayout
@property int width;
@property int height;
@end
@implementation UICollectionViewPagedFlowLayout
#warning MINOR BUG: sometimes items disappear
- (CGSize)collectionViewContentSize {
    CGSize size = [super collectionViewContentSize];
    size.width = self.collectionView.bounds.size.width * [self.collectionView numberOfSections];
    return size;
}
- (NSArray*) layoutAttributesForElementsInRect:(CGRect)rect {
    NSArray *array = [super layoutAttributesForElementsInRect:rect];
    CGRect visibleRect;
    visibleRect.origin = self.collectionView.contentOffset;
    visibleRect.size = self.collectionView.bounds.size;

    for (UICollectionViewLayoutAttributes* attributes in array) {
        if (CGRectIntersectsRect(attributes.frame, rect)) {
            // see which section this is in
            CGRect configurableRect = UIEdgeInsetsInsetRect(visibleRect, self.sectionInset);
            int horizontalIndex = attributes.indexPath.row % self.width;
            int verticalIndex = attributes.indexPath.row / self.width;
            double xspace = (configurableRect.size.width - self.width * self.itemSize.width) / self.width;
            double yspace = (configurableRect.size.height - self.height * self.itemSize.height) / self.height;
            attributes.center = CGPointMake(attributes.indexPath.section * visibleRect.size.width + self.sectionInset.left + (self.itemSize.width + xspace) * horizontalIndex + self.itemSize.width / 2, self.sectionInset.top + (self.itemSize.height + yspace) * verticalIndex + self.itemSize.height / 2);
        }
    }
    return array;
}
@end

Upvotes: 0

rdelmar
rdelmar

Reputation: 104092

I don't know about easily, depends on your definition I guess. I made a project where I had the layout that's close to what you're looking for. This lays out the rows horizontally, left to right, top to bottom. My data source was an array of arrays, where each subarray provided the data for one line.

#import "SimpleHorizontalLayout.h" // subclass of UICollectionViewFlowLayout
#define kItemSize 60

@implementation SimpleHorizontalLayout 


-(CGSize)collectionViewContentSize {
    NSInteger xSize = [self.collectionView numberOfItemsInSection:0] * (kItemSize + 20); // the 20 is for spacing between cells.
    NSInteger ySize = [self.collectionView numberOfSections] * (kItemSize + 20);
    return CGSizeMake(xSize, ySize);
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path {
    UICollectionViewLayoutAttributes* attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:path];
    attributes.size = CGSizeMake(kItemSize,kItemSize);
    NSInteger xValue = kItemSize/2 + path.row * (kItemSize +20) ;
    NSInteger yValue = kItemSize + path.section * (kItemSize +20);
    attributes.center = CGPointMake(xValue, yValue);
    return attributes;
}


-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect {
    NSInteger minRow =  (rect.origin.x > 0)?  rect.origin.x/(kItemSize +20) : 0; // need to check because bounce gives negative values for x.
    NSInteger maxRow = rect.size.width/(kItemSize +20) + 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;
}

Upvotes: 1

rickster
rickster

Reputation: 126167

IIRC, exactly this is demonstrated in the WWDC 2012 sessions on UICollectionView.

Upvotes: 1

Related Questions