How to create a centered UICollectionView like in Spotify's Player
As you have said in the comment you want that in the Objective-c code, there is a very famous library called iCarousel which can be helpful in completing your requirement.Link: https://github.com/nicklockwood/iCarousel
You may use 'Rotary' or 'Linear' or some other style with little or no modification to implement the custom view
To implement it you have implement only some delegate methods of it and it's working for ex:
//specify the type you want to use in viewDidLoad
_carousel.type = iCarouselTypeRotary;
//Set the following delegate methods
- (NSInteger)numberOfItemsInCarousel:(iCarousel *)carousel
{
//return the total number of items in the carousel
return [_items count];
}
- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSInteger)index reusingView:(UIView *)view
{
UILabel *label = nil;
//create new view if no view is available for recycling
if (view == nil)
{
//don't do anything specific to the index within
//this `if (view == nil) {...}` statement because the view will be
//recycled and used with other index values later
view = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200.0f, 200.0f)];
((UIImageView *)view).image = [UIImage imageNamed:@"page.png"];
view.contentMode = UIViewContentModeCenter;
label = [[UILabel alloc] initWithFrame:view.bounds];
label.backgroundColor = [UIColor clearColor];
label.textAlignment = NSTextAlignmentCenter;
label.font = [label.font fontWithSize:50];
label.tag = 1;
[view addSubview:label];
}
else
{
//get a reference to the label in the recycled view
label = (UILabel *)[view viewWithTag:1];
}
//set item label
label.text = [_items[index] stringValue];
return view;
}
- (CGFloat)carousel:(iCarousel *)carousel valueForOption:(iCarouselOption)option withDefault:(CGFloat)value
{
if (option == iCarouselOptionSpacing)
{
return value * 1.1;
}
return value;
}
You can check the full working demo from 'Examples/Basic iOS Example' which is included with the Github repository link
As it is old and popular you can find some related tutorials for it and it will also be much stable than the custom code implementation
Snap to center of a cell when scrolling UICollectionView horizontally
While originally I was using Objective-C, I since switched so Swift and the original accepted answer did not suffice.
I ended up creating a UICollectionViewLayout
subclass which provides the best (imo) experience as opposed to the other functions which alter content offset or something similar when the user has stopped scrolling.
class SnappingCollectionViewLayout: UICollectionViewFlowLayout {
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = collectionView else { return super.targetContentOffset(forProposedContentOffset: proposedContentOffset, withScrollingVelocity: velocity) }
var offsetAdjustment = CGFloat.greatestFiniteMagnitude
let horizontalOffset = proposedContentOffset.x + collectionView.contentInset.left
let targetRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView.bounds.size.width, height: collectionView.bounds.size.height)
let layoutAttributesArray = super.layoutAttributesForElements(in: targetRect)
layoutAttributesArray?.forEach({ (layoutAttributes) in
let itemOffset = layoutAttributes.frame.origin.x
if fabsf(Float(itemOffset - horizontalOffset)) < fabsf(Float(offsetAdjustment)) {
offsetAdjustment = itemOffset - horizontalOffset
}
})
return CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y)
}
}
For the most native feeling deceleration with the current layout subclass, make sure to set the following:
collectionView?.decelerationRate = UIScrollViewDecelerationRateFast
UICollectionViewFlowLayout `targetContentOffset` - how to keep cells centered after orientation change?
As far as I can see, the method is getting called not when you just rotate the device, but when the layout changes. Meaning, if you change from Landscape Left
to Landscape Right
the delegate method is not called — however if you rotating from any Landscape
to Portrait
or other way around, it is working fine.
Important!
For maintaining the centered collection view cell, add the targetContentOffset(forProposedContentOffset:)
method. This is called after a rotation, and is not the same as your current targetContentOffset(forProposedContentOffset:withScrollingVelocity:)
method. In your final code, you should use both of these methods. To summarize:
targetContentOffset(forProposedContentOffset:)
is called after a layout changetargetContentOffset(forProposedContentOffset:withScrollingVelocity:)
is called when the user lifts their finger
So, in your PagingFlowLayout
class, paste the following code:
private var focusedIndexPath: IndexPath?
override func prepare(forAnimatedBoundsChange oldBounds: CGRect) {
super.prepare(forAnimatedBoundsChange: oldBounds)
focusedIndexPath = collectionView?.indexPathsForVisibleItems.first
}
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
guard
let indexPath = focusedIndexPath,
let attributes = layoutAttributesForItem(at: indexPath),
let collectionView = collectionView
else {
return super.targetContentOffset(forProposedContentOffset: proposedContentOffset)
}
return CGPoint(
x: attributes.frame.origin.x - collectionView.contentInset.left,
y: attributes.frame.origin.y - collectionView.contentInset.top
)
}
override func finalizeAnimatedBoundsChange() {
super.finalizeAnimatedBoundsChange()
focusedIndexPath = nil
}
The code was from: https://stackoverflow.com/a/54868999/11332605.
UICollectionView Horizontal Paging not centered
Remove spaces between items. For horizontal scrolling collection view set minimum line spacing to 0. You can do this with interface builder or with method of UICollectionViewDelegateFlowLayout
protocol:
- (CGFloat)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
minimumLineSpacingForSectionAtIndex:(NSInteger)section {
return 0;
}
Another way is making your cell's width less than collectionView's width for a value of horizontal space between items. Then add section insets with left and right insets that equal a half of horizontal space between items. For example, your minimum line spacing is 10:
- (CGFloat)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
minimumLineSpacingForSectionAtIndex:(NSInteger)section {
return 10;
}
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
return CGSizeMake(collectionView.frame.size.width - 10, collectionView.frame.size.height);
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(0, 5, 0, 5);
}
And third way: manipulate collectionView scroll in scrollViewDidEndDecelerating:
method:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
if (scrollView == self.collectionView) {
CGPoint currentCellOffset = self.collectionView.contentOffset;
currentCellOffset.x += self.collectionView.frame.size.width / 2;
NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:currentCellOffset];
[self.collectionView scrollToItemAtIndexPath:indexPath
atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally
animated:YES];
}
}
Center First UICollectionViewCell in UICollectionView
To make collection view cell center, your controller must confirm UICollectionViewDelegateFlowLayout protocol. Use below code to make cells center:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width * 0.7, height: 300)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let width = collectionView.frame.width
let margin = width * 0.3
return UIEdgeInsets(top: 10, left: margin / 2, bottom: 10, right: margin / 2)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return collectionView.frame.width * 0.3 / 2
}
If you return 1 or 2 in numberOfItemsInSection method, It works fine.
UICollectionView Bring Item to center position
SOL 1:
collectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: .centeredHorizontally, animated: true)
SOL 2:
I suggest you add empty items at the beginning and end. So the user can scroll the first and last visible item to the centre.
Related Topics
Custom Installed Font Not Displayed Correctly in Uilabel
Using Multiple Storyboards in iOS
iOS 7 - Difference Between Viewdidload and Viewdidappear
Change Tab Bar Item Selected Color in a Storyboard
Iphone: Hide Uitableview Search Bar by Default
Invalid Update: Invalid Number of Rows in Section 0
How to Make an Uipickerview Component Wrap Around
Method Overloading in Objective-C
Swift Programmatically Navigate to Another View Controller/Scene
iOS - Uiimageview - How to Handle Uiimage Image Orientation
How to Detect the Touch Event of an Uiimageview
How to Get Audio Volume Level, and Volume Changed Notifications on iOS
Using Iskindofclass with Swift
How to Initialize/Instantiate a Custom Uiview Class with a Xib File in Swift