-->

UICollectionView - Horizontal scroll, horizontal l

2019-01-08 04:58发布

问题:

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?

回答1:

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:

  • https://developer.apple.com/library/content/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/CreatingCustomLayouts/CreatingCustomLayouts.html
  • http://www.objc.io/issue-3/collection-view-layouts.html

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.



回答2:

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


回答3:

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.



回答4:

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


回答5:

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.



回答6:

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

    }


回答7:

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

I hope it helps someone.



回答8:

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

2_Code_H-Scroll_V-Fill.png

3_Output_H-Scroll_V-Fill.png

4_Code_H-Scroll_H-Fill.png

5_Output_H-Scroll_H-Fill.png



回答9:

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



回答10:

for xcode 8 i did this and it worked:



回答11:

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

Here's code repository: KSTCollectionViewPageHorizontalLayout

@iPhoneDev (this maybe help you too)