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
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.
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 .
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)
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
2_Code_H-Scroll_V-Fill.png
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.
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];
Source: Stackoverflow.com