UICollectionView - Horizontal scroll, horizontal layout?

IosLayoutUicollectionviewUicollectionviewlayout

Ios Problem Overview


I have a UIScrollView that lays out a grid of icons. If you were to imagine the layout for the iOS Springboard, you'd be pretty close to correct. It has a horizontal, paged scroll (just like Springboard). However, it appears that the layout is not quite right. It appears as though it is laying out the items from top to bottom. As a result, my last column only has 2 rows in it, due to the number of items to be displayed. I'd rather have my last row on the last page have 2 items, like you would see in the Springboard.

How can this be accomplished with UICollectionView and its related classes? Do I have to write a custom UICollectionViewFlowLayout?

Ios Solutions


Solution 1 - Ios

Have you tried setting the scroll direction of your UICollectionViewFlowLayout to horizontal?

[yourFlowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];

And if you want it to page like springboard does, you'll need to enable paging on your collection view like so:

[yourCollectionView setPagingEnabled:YES];

Solution 2 - Ios

1st approach

What about using UIPageViewController with an array of UICollectionViewControllers? You'd have to fetch proper number of items in each UICollectionViewController, but it shouldn't be hard. You'd get exactly the same look as the Springboard has.

2nd approach

I've thought about this and in my opinion you have to set:

self.collectionView.pagingEnabled = YES;

and create your own collection view layout by subclassing UICollectionViewLayout. From the custom layout object you can access self.collectionView, so you'll know what is the size of the collection view's frame, numberOfSections and numberOfItemsInSection:. With that information you can calculate cells' frames (in prepareLayout) and collectionViewContentSize. Here're some articles about creating custom layouts:

3rd approach

You can do this (or an approximation of it) without creating the custom layout. Add UIScrollView in the blank view, set paging enabled in it. In the scroll view add the a collection view. Then add to it a width constraint, check in code how many items you have and set its constant to the correct value, e.g. (self.view.frame.size.width * numOfScreens). Here's how it looks (numbers on cells show the indexPath.row): https://www.dropbox.com/s/ss4jdbvr511azxz/collection_view.mov If you're not satisfied with the way cells are ordered, then I'm afraid you'd have to go with 1. or 2.

Solution 3 - Ios

You need to reduce the height of UICollectionView to its cell / item height and select "Horizontal" from the "Scroll Direction" as seen in the screenshot below. Then it will scroll horizontally depending on the numberOfItems you have returned in its datasource implementation.

enter image description here

Solution 4 - Ios

If you are defining UICollectionViewFlowLayout in code, it will override Interface Builder configs. Hence you need to re-define the scrollDirection again.

let layout = UICollectionViewFlowLayout()
...
layout.scrollDirection = .Horizontal
self.awesomeCollectionView.collectionViewLayout = layout

Solution 5 - Ios

This code works well in Swift 3.1 and Xcode 8.3.2

override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .horizontal
        self.collectionView.collectionViewLayout = layout
        self.collectionView!.contentInset = UIEdgeInsets(top: -10, left: 0, bottom:0, right: 0)
        
        if let layout = self.collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
            layout.minimumInteritemSpacing = 0
            layout.minimumLineSpacing = 0
            layout.itemSize = CGSize(width: self.view.frame.size.width-40, height: self.collectionView.frame.size.height-10)
            layout.invalidateLayout()
        }
        
    }

Solution 6 - Ios

From @Erik Hunter, I post full code for make horizontal UICollectionView

UICollectionViewFlowLayout *collectionViewFlowLayout = [[UICollectionViewFlowLayout alloc] init];
[collectionViewFlowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
self.myCollectionView.collectionViewLayout = collectionViewFlowLayout;

In Swift

let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .Horizontal
self.myCollectionView.collectionViewLayout = layout

In Swift 3.0

let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
self.myCollectionView.collectionViewLayout = layout

Hope this help

Solution 7 - Ios

Just for fun, another approach would be to just leave the paging and horizontal scrolling set, add a method that changes the order of the array items to convert from 'top to bottom, left to right' to visually 'left to right, top to bottom' and fill the in-between cells with empty hidden cells to make the spacing right. In case of 7 items in a grid of 9, this would go like this:

[1][4][7]
[2][5][ ]
[3][6][ ]

should become

[1][2][3]
[4][5][6]
[7][ ][ ]

so 1=1, 2=4, 3=7 etc. and 6=empty. You can reorder them by calculating the total number of rows and columns, then calculate the row and column number for each cell, change the row for the column and vice versa and then you have the new indexes. When the cell doesn't have a value corresponding to the image you can return an empty cell and set cell.hidden = YES; to it.

It works quite well in a soundboard app I built, so if anyone would like working code I'll add it. Only little code is required to make this trick work, it sounds harder than it is!

Update

I doubt this is the best solution, but by request here's working code:

- (void)viewDidLoad {
    // Fill an `NSArray` with items in normal order
    items = [NSMutableArray arrayWithObjects:
             [NSDictionary dictionaryWithObjectsAndKeys:@"Some label 1", @"label", @"Some value 1", @"value", nil],
             [NSDictionary dictionaryWithObjectsAndKeys:@"Some label 2", @"label", @"Some value 2", @"value", nil],
             [NSDictionary dictionaryWithObjectsAndKeys:@"Some label 3", @"label", @"Some value 3", @"value", nil],
             [NSDictionary dictionaryWithObjectsAndKeys:@"Some label 4", @"label", @"Some value 4", @"value", nil],
             [NSDictionary dictionaryWithObjectsAndKeys:@"Some label 5", @"label", @"Some value 5", @"value", nil],
              nil
              ];

    // Calculate number of rows and columns based on width and height of the `UICollectionView` and individual cells (you might have to add margins to the equation based on your setup!)
    CGFloat w = myCollectionView.frame.size.width;
    CGFloat h = myCollectionView.frame.size.height;
    rows = floor(h / cellHeight);
    columns = floor(w / cellWidth);
}

// Calculate number of sections
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return ceil((float)items.count / (float)(rows * columns));
}

// Every section has to have every cell filled, as we need to add empty cells as well to correct the spacing
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return rows*columns;
}

// And now the most important one
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier@"myIdentifier" forIndexPath:indexPath];
    
    // Convert rows and columns
    int row = indexPath.row % rows;
    int col = floor(indexPath.row / rows);
    // Calculate the new index in the `NSArray`
    int newIndex = ((int)indexPath.section * rows * columns) + col + row * columns;
    
    // If the newIndex is within the range of the items array we show the cell, if not we hide it
    if(newIndex < items.count) {
        NSDictionary *item = [items objectAtIndex:newIndex];
        cell.label.text = [item objectForKey:@"label"];
        cell.hidden = NO;
    } else {
        cell.hidden = YES;
    }
    
    return cell;
}

If you'd like to use the didSelectItemAtIndexPath method you have to use the same conversion that is used in cellForItemAtIndexPath to get the corresponding item. If you have cell margins you need to add them to the rows and columns calculation, as those have to be correct in order for this to work.

Solution 8 - Ios

We can do same Springboard behavior using UICollectionView and for that we need to write code for custom layout.

I have achieved it with my custom layout class implementation with "SMCollectionViewFillLayout"

Code repository:

https://github.com/smindia1988/SMCollectionViewFillLayout

Output as below:

1.png

First.png

2_Code_H-Scroll_V-Fill.png

enter image description here

3_Output_H-Scroll_V-Fill.png enter image description here

4_Code_H-Scroll_H-Fill.png enter image description here

5_Output_H-Scroll_H-Fill.png enter image description here

Solution 9 - Ios

for xcode 8 i did this and it worked:

Solution 10 - Ios

I am working on Xcode 6.2 and for horizontal scrolling I have changed scroll direction in attribute inspector.

click on collectionView->attribute inspector->scroll Direction->change to horizontal

enter image description here I hope it helps someone.

Solution 11 - Ios

You can write a custom UICollectionView layout to achieve this, here is demo image of my implementation:

![demo image][1]

Here's code repository: [KSTCollectionViewPageHorizontalLayout][2]

@iPhoneDev (this maybe help you too)

[1]: https://github.com/kelystor/KSTCollectionViewPageHorizontalLayout/raw/master/KSTCollectionViewPageHorizontalLayout/ScreenShots/demo.gif "demo image" [2]: https://github.com/kelystor/KSTCollectionViewPageHorizontalLayout "KSTCollectionViewPageHorizontalLayout"

Solution 12 - Ios

If you need to set the UICollectionView scrolling Direction Horizental and you need to set cell width and height static. Please set the collectionview estimate size Automatic into None .

View The ScreenShot

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionShadowmanView Question on Stackoverflow
Solution 1 - IosErik HunterView Answer on Stackoverflow
Solution 2 - IosArek HolkoView Answer on Stackoverflow
Solution 3 - IosDeepak ThakurView Answer on Stackoverflow
Solution 4 - IosDazChongView Answer on Stackoverflow
Solution 5 - IosPrashant GaikwadView Answer on Stackoverflow
Solution 6 - IosLinhView Answer on Stackoverflow
Solution 7 - IosTumView Answer on Stackoverflow
Solution 8 - IosSandip Patel - SMView Answer on Stackoverflow
Solution 9 - IosGulzView Answer on Stackoverflow
Solution 10 - IosNarasimha NallamsettyView Answer on Stackoverflow
Solution 11 - IosZengView Answer on Stackoverflow
Solution 12 - IosRahul JambaView Answer on Stackoverflow