Page and center UICollectionView like App Store
Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
5
down vote
favorite
I need a collection view to page through cells and center it like the App Store, where a portion of the previous and next cells look like this:
The native isPagingEnabled
flag would be great if it had an option to center on the cell. However, making the cell span the full width of the collection view doesn't show previous/next cells, and making the cell width smaller doesn't snap to center when paging.
There are tons of hacks, articles, and suggestions out there that I've tried. Many were overcomplicated or a horrible UX. I finally find a good balance with this example and adjusted the code.
This is the code and scroll delegate event to make this work using a flow layout:
class HomeViewController: UIViewController
@IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad()
super.viewDidLoad()
collectionView.collectionViewLayout = SnapPagingLayout(
centerPosition: true,
peekWidth: 40,
spacing: 20,
inset: 16
)
...
extension HomeViewController: UICollectionViewDelegate
func scrollViewWillBeginDragging(_ scrollView: UIScrollView)
guard let layout = collectionView.collectionViewLayout as? SnapPagingLayout else return
layout.willBeginDragging()
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>)
guard let layout = collectionView.collectionViewLayout as? SnapPagingLayout else return
layout.willEndDragging(withVelocity: velocity, targetContentOffset: targetContentOffset)
//////////
class SnapPagingLayout: UICollectionViewFlowLayout
private var centerPosition = true
private var peekWidth: CGFloat = 0
private var indexOfCellBeforeDragging = 0
convenience init(centerPosition: Bool = true, peekWidth: CGFloat = 40, spacing: CGFloat? = nil, inset: CGFloat? = nil)
self.init()
self.scrollDirection = .horizontal
self.centerPosition = centerPosition
self.peekWidth = peekWidth
if let spacing = spacing
self.minimumLineSpacing = spacing
if let inset = inset
self.sectionInset = UIEdgeInsets(top: 0, left: inset, bottom: 0, right: inset)
override func prepare()
super.prepare()
guard let collectionView = collectionView else return
self.itemSize = calculateItemSize(from: collectionView.bounds.size)
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool
guard let collectionView = collectionView,
!newBounds.size.equalTo(collectionView.bounds.size) else
return false
itemSize = calculateItemSize(from: collectionView.bounds.size)
return true
private extension SnapPagingLayout
func calculateItemSize(from bounds: CGSize) -> CGSize
return CGSize(
width: bounds.width - peekWidth * 2,
height: bounds.height
)
func indexOfMajorCell() -> Int
guard let collectionView = collectionView else return 0
let proportionalOffset = collectionView.contentOffset.x
/ (itemSize.width + minimumLineSpacing)
return Int(round(proportionalOffset))
extension SnapPagingLayout
func willBeginDragging()
indexOfCellBeforeDragging = indexOfMajorCell()
func willEndDragging(withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>)
It doesnâÂÂt feel as smooth as the App Store (especially when scrolling fast), but itâÂÂs almost there. Do you have any suggestions or advice on how to simplify this or improve it?
swift ios user-interface
add a comment |Â
up vote
5
down vote
favorite
I need a collection view to page through cells and center it like the App Store, where a portion of the previous and next cells look like this:
The native isPagingEnabled
flag would be great if it had an option to center on the cell. However, making the cell span the full width of the collection view doesn't show previous/next cells, and making the cell width smaller doesn't snap to center when paging.
There are tons of hacks, articles, and suggestions out there that I've tried. Many were overcomplicated or a horrible UX. I finally find a good balance with this example and adjusted the code.
This is the code and scroll delegate event to make this work using a flow layout:
class HomeViewController: UIViewController
@IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad()
super.viewDidLoad()
collectionView.collectionViewLayout = SnapPagingLayout(
centerPosition: true,
peekWidth: 40,
spacing: 20,
inset: 16
)
...
extension HomeViewController: UICollectionViewDelegate
func scrollViewWillBeginDragging(_ scrollView: UIScrollView)
guard let layout = collectionView.collectionViewLayout as? SnapPagingLayout else return
layout.willBeginDragging()
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>)
guard let layout = collectionView.collectionViewLayout as? SnapPagingLayout else return
layout.willEndDragging(withVelocity: velocity, targetContentOffset: targetContentOffset)
//////////
class SnapPagingLayout: UICollectionViewFlowLayout
private var centerPosition = true
private var peekWidth: CGFloat = 0
private var indexOfCellBeforeDragging = 0
convenience init(centerPosition: Bool = true, peekWidth: CGFloat = 40, spacing: CGFloat? = nil, inset: CGFloat? = nil)
self.init()
self.scrollDirection = .horizontal
self.centerPosition = centerPosition
self.peekWidth = peekWidth
if let spacing = spacing
self.minimumLineSpacing = spacing
if let inset = inset
self.sectionInset = UIEdgeInsets(top: 0, left: inset, bottom: 0, right: inset)
override func prepare()
super.prepare()
guard let collectionView = collectionView else return
self.itemSize = calculateItemSize(from: collectionView.bounds.size)
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool
guard let collectionView = collectionView,
!newBounds.size.equalTo(collectionView.bounds.size) else
return false
itemSize = calculateItemSize(from: collectionView.bounds.size)
return true
private extension SnapPagingLayout
func calculateItemSize(from bounds: CGSize) -> CGSize
return CGSize(
width: bounds.width - peekWidth * 2,
height: bounds.height
)
func indexOfMajorCell() -> Int
guard let collectionView = collectionView else return 0
let proportionalOffset = collectionView.contentOffset.x
/ (itemSize.width + minimumLineSpacing)
return Int(round(proportionalOffset))
extension SnapPagingLayout
func willBeginDragging()
indexOfCellBeforeDragging = indexOfMajorCell()
func willEndDragging(withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>)
It doesnâÂÂt feel as smooth as the App Store (especially when scrolling fast), but itâÂÂs almost there. Do you have any suggestions or advice on how to simplify this or improve it?
swift ios user-interface
1
Is there any chance that you can provide a complete downloadable project, similar to the one that you referenced?
â Martin R
Jun 27 at 6:37
Yes there's a refactor_layout branch: bit.ly/2Ixjqqi
â TruMan1
Jun 27 at 15:01
add a comment |Â
up vote
5
down vote
favorite
up vote
5
down vote
favorite
I need a collection view to page through cells and center it like the App Store, where a portion of the previous and next cells look like this:
The native isPagingEnabled
flag would be great if it had an option to center on the cell. However, making the cell span the full width of the collection view doesn't show previous/next cells, and making the cell width smaller doesn't snap to center when paging.
There are tons of hacks, articles, and suggestions out there that I've tried. Many were overcomplicated or a horrible UX. I finally find a good balance with this example and adjusted the code.
This is the code and scroll delegate event to make this work using a flow layout:
class HomeViewController: UIViewController
@IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad()
super.viewDidLoad()
collectionView.collectionViewLayout = SnapPagingLayout(
centerPosition: true,
peekWidth: 40,
spacing: 20,
inset: 16
)
...
extension HomeViewController: UICollectionViewDelegate
func scrollViewWillBeginDragging(_ scrollView: UIScrollView)
guard let layout = collectionView.collectionViewLayout as? SnapPagingLayout else return
layout.willBeginDragging()
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>)
guard let layout = collectionView.collectionViewLayout as? SnapPagingLayout else return
layout.willEndDragging(withVelocity: velocity, targetContentOffset: targetContentOffset)
//////////
class SnapPagingLayout: UICollectionViewFlowLayout
private var centerPosition = true
private var peekWidth: CGFloat = 0
private var indexOfCellBeforeDragging = 0
convenience init(centerPosition: Bool = true, peekWidth: CGFloat = 40, spacing: CGFloat? = nil, inset: CGFloat? = nil)
self.init()
self.scrollDirection = .horizontal
self.centerPosition = centerPosition
self.peekWidth = peekWidth
if let spacing = spacing
self.minimumLineSpacing = spacing
if let inset = inset
self.sectionInset = UIEdgeInsets(top: 0, left: inset, bottom: 0, right: inset)
override func prepare()
super.prepare()
guard let collectionView = collectionView else return
self.itemSize = calculateItemSize(from: collectionView.bounds.size)
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool
guard let collectionView = collectionView,
!newBounds.size.equalTo(collectionView.bounds.size) else
return false
itemSize = calculateItemSize(from: collectionView.bounds.size)
return true
private extension SnapPagingLayout
func calculateItemSize(from bounds: CGSize) -> CGSize
return CGSize(
width: bounds.width - peekWidth * 2,
height: bounds.height
)
func indexOfMajorCell() -> Int
guard let collectionView = collectionView else return 0
let proportionalOffset = collectionView.contentOffset.x
/ (itemSize.width + minimumLineSpacing)
return Int(round(proportionalOffset))
extension SnapPagingLayout
func willBeginDragging()
indexOfCellBeforeDragging = indexOfMajorCell()
func willEndDragging(withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>)
It doesnâÂÂt feel as smooth as the App Store (especially when scrolling fast), but itâÂÂs almost there. Do you have any suggestions or advice on how to simplify this or improve it?
swift ios user-interface
I need a collection view to page through cells and center it like the App Store, where a portion of the previous and next cells look like this:
The native isPagingEnabled
flag would be great if it had an option to center on the cell. However, making the cell span the full width of the collection view doesn't show previous/next cells, and making the cell width smaller doesn't snap to center when paging.
There are tons of hacks, articles, and suggestions out there that I've tried. Many were overcomplicated or a horrible UX. I finally find a good balance with this example and adjusted the code.
This is the code and scroll delegate event to make this work using a flow layout:
class HomeViewController: UIViewController
@IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad()
super.viewDidLoad()
collectionView.collectionViewLayout = SnapPagingLayout(
centerPosition: true,
peekWidth: 40,
spacing: 20,
inset: 16
)
...
extension HomeViewController: UICollectionViewDelegate
func scrollViewWillBeginDragging(_ scrollView: UIScrollView)
guard let layout = collectionView.collectionViewLayout as? SnapPagingLayout else return
layout.willBeginDragging()
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>)
guard let layout = collectionView.collectionViewLayout as? SnapPagingLayout else return
layout.willEndDragging(withVelocity: velocity, targetContentOffset: targetContentOffset)
//////////
class SnapPagingLayout: UICollectionViewFlowLayout
private var centerPosition = true
private var peekWidth: CGFloat = 0
private var indexOfCellBeforeDragging = 0
convenience init(centerPosition: Bool = true, peekWidth: CGFloat = 40, spacing: CGFloat? = nil, inset: CGFloat? = nil)
self.init()
self.scrollDirection = .horizontal
self.centerPosition = centerPosition
self.peekWidth = peekWidth
if let spacing = spacing
self.minimumLineSpacing = spacing
if let inset = inset
self.sectionInset = UIEdgeInsets(top: 0, left: inset, bottom: 0, right: inset)
override func prepare()
super.prepare()
guard let collectionView = collectionView else return
self.itemSize = calculateItemSize(from: collectionView.bounds.size)
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool
guard let collectionView = collectionView,
!newBounds.size.equalTo(collectionView.bounds.size) else
return false
itemSize = calculateItemSize(from: collectionView.bounds.size)
return true
private extension SnapPagingLayout
func calculateItemSize(from bounds: CGSize) -> CGSize
return CGSize(
width: bounds.width - peekWidth * 2,
height: bounds.height
)
func indexOfMajorCell() -> Int
guard let collectionView = collectionView else return 0
let proportionalOffset = collectionView.contentOffset.x
/ (itemSize.width + minimumLineSpacing)
return Int(round(proportionalOffset))
extension SnapPagingLayout
func willBeginDragging()
indexOfCellBeforeDragging = indexOfMajorCell()
func willEndDragging(withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>)
It doesnâÂÂt feel as smooth as the App Store (especially when scrolling fast), but itâÂÂs almost there. Do you have any suggestions or advice on how to simplify this or improve it?
swift ios user-interface
edited Jun 26 at 14:14
asked Jun 21 at 22:20
TruMan1
763
763
1
Is there any chance that you can provide a complete downloadable project, similar to the one that you referenced?
â Martin R
Jun 27 at 6:37
Yes there's a refactor_layout branch: bit.ly/2Ixjqqi
â TruMan1
Jun 27 at 15:01
add a comment |Â
1
Is there any chance that you can provide a complete downloadable project, similar to the one that you referenced?
â Martin R
Jun 27 at 6:37
Yes there's a refactor_layout branch: bit.ly/2Ixjqqi
â TruMan1
Jun 27 at 15:01
1
1
Is there any chance that you can provide a complete downloadable project, similar to the one that you referenced?
â Martin R
Jun 27 at 6:37
Is there any chance that you can provide a complete downloadable project, similar to the one that you referenced?
â Martin R
Jun 27 at 6:37
Yes there's a refactor_layout branch: bit.ly/2Ixjqqi
â TruMan1
Jun 27 at 15:01
Yes there's a refactor_layout branch: bit.ly/2Ixjqqi
â TruMan1
Jun 27 at 15:01
add a comment |Â
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f197017%2fpage-and-center-uicollectionview-like-app-store%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
1
Is there any chance that you can provide a complete downloadable project, similar to the one that you referenced?
â Martin R
Jun 27 at 6:37
Yes there's a refactor_layout branch: bit.ly/2Ixjqqi
â TruMan1
Jun 27 at 15:01