[ios] UICollectionView - Horizontal scroll, horizontal layout?

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?

This question is related to ios layout uicollectionview uicollectionviewlayout

The answer is


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.


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


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


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

demo image

Here's code repository: KSTCollectionViewPageHorizontalLayout

@iPhoneDev (this maybe help you too)


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()
        }

    }

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


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


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.


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

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];

for xcode 8 i did this and it worked:


Examples related to ios

Adding a UISegmentedControl to UITableView Crop image to specified size and picture location Undefined Symbols error when integrating Apptentive iOS SDK via Cocoapods Keep placeholder text in UITextField on input in IOS Accessing AppDelegate from framework? Autoresize View When SubViews are Added Warp \ bend effect on a UIView? Speech input for visually impaired users without the need to tap the screen make UITableViewCell selectable only while editing Xcode 12, building for iOS Simulator, but linking in object file built for iOS, for architecture arm64

Examples related to layout

This view is not constrained What's the difference between align-content and align-items? CSS Flex Box Layout: full-width row and columns Fill remaining vertical space with CSS using display:flex What is setContentView(R.layout.main)? How to change line color in EditText Scrolling a flexbox with overflowing content UICollectionView - Horizontal scroll, horizontal layout? How to style a div to be a responsive square? 100% width Twitter Bootstrap 3 template

Examples related to uicollectionview

How to set UICollectionViewCell Width and Height programmatically How to make a simple collection view with Swift How to set cell spacing and UICollectionView - UICollectionViewFlowLayout size ratio? UICollectionView - dynamic cell height? UICollectionView Self Sizing Cells with Auto Layout Fatal error: unexpectedly found nil while unwrapping an Optional values Paging UICollectionView by cells, not screen UICollectionView - Horizontal scroll, horizontal layout? UICollectionView current visible cell index Creating a UICollectionView programmatically

Examples related to uicollectionviewlayout

How to set cell spacing and UICollectionView - UICollectionViewFlowLayout size ratio? Paging UICollectionView by cells, not screen UICollectionView - Horizontal scroll, horizontal layout?