Page and center UICollectionView like App Store

The name of the pictureThe name of the pictureThe name of the pictureClash 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:



enter image description here



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?







share|improve this question

















  • 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

















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:



enter image description here



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?







share|improve this question

















  • 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













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:



enter image description here



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?







share|improve this question













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:



enter image description here



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?









share|improve this question












share|improve this question




share|improve this question








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













  • 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
















active

oldest

votes











Your Answer




StackExchange.ifUsing("editor", function ()
return StackExchange.using("mathjaxEditing", function ()
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
);
);
, "mathjax-editing");

StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: false,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);








 

draft saved


draft discarded


















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



































active

oldest

votes













active

oldest

votes









active

oldest

votes






active

oldest

votes










 

draft saved


draft discarded


























 


draft saved


draft discarded














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













































































Popular posts from this blog

Greedy Best First Search implementation in Rust

Function to Return a JSON Like Objects Using VBA Collections and Arrays

C++11 CLH Lock Implementation