README and documentationIGListKit version: 3.0# Please include debug logs using the following lldb command:
po [IGListDebugger dump]
Hello!
This is a question, I think!
First, thanks for a great product ! 🎉
I want to achieve some center paging for a banner view in my app. I want to do something similar to the new iOS 11 AppStore. With the main cell in center and then the right and left are peaking out in the sides.

I hope that some one can help me 😄
// Kim
If you are interested in the snapping to center and not the structure to do horizontal scrolling in a section. Here is how I did ut:
adapter.scrollViewDelegate = self
and then in the horizontal paging wrapping section controller:
extension WrappingSectionController: UIScrollViewDelegate {
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
guard let collectionView = scrollView as? UICollectionView else { return }
collectionView.snapToCell(velocity: velocity, targetOffset: targetContentOffset, spacing: cellSpacing)
contentOffset = targetContentOffset.pointee.x
}
}
The snap to cell is an extension on UICollectionView
extension UICollectionView {
func snapToCell(velocity: CGPoint, targetOffset: UnsafeMutablePointer<CGPoint>, contentInset: CGFloat = 0, spacing: CGFloat = 0) {
// No snap needed as we're at the end of the scrollview
guard (contentOffset.x + frame.size.width) < contentSize.width else { return }
guard let indexPath = indexPathForItem(at: targetOffset.pointee) else { return }
guard let cellLayout = layoutAttributesForItem(at: indexPath) else { return }
var offset = targetOffset.pointee
if velocity.x < 0 {
offset.x = cellLayout.frame.minX - max(contentInset, spacing)
} else {
offset.x = cellLayout.frame.maxX - contentInset + min(contentInset, spacing)
}
targetOffset.pointee = offset
}
}
Thanks @heumn !
I have tried your example, and can't get it working.
I have a custom UICollectionViewFlowlayout I know that works (Used in other projects)
Do you know if it is possible to use that? :)
Could you describe what doesn't work or even better, share some code? :)
Okay I have my ViewController that contains a list of discoverItems
The first item of the list return a DiscoverBannerSectionController
if discoverItem === self.trending {
let sectionController = DiscoverBannerSectionController()
sectionController.inset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
return sectionController
}
The DiscoverBannerSectionController loks like this
final class DiscoverBannerSectionController: ListSectionController, ListAdapterDataSource {
private var item: DiscoverItem?
lazy var adapter: ListAdapter = {
let adapter = ListAdapter(updater: ListAdapterUpdater(),
viewController: self.viewController)
adapter.dataSource = self
return adapter
}()
override func sizeForItem(at index: Int) -> CGSize {
let width = collectionContext?.containerSize.width ?? 0
return CGSize(width: width, height: width * 9 / 16)
}
override func cellForItem(at index: Int) -> UICollectionViewCell {
guard let cell = collectionContext?.dequeueReusableCell(of: EmbeddedBannerCollectionViewCell.self,
for: self,
at: index) as? EmbeddedBannerCollectionViewCell else {
fatalError("Something went wrong in DiscoverBannerSectionController")
}
self.adapter.collectionView = cell.collectionView
self.adapter.scrollViewDelegate = self
return cell
}
override func didUpdate(to object: Any) {
item = object as? DiscoverItem
self.adapter.reloadData(completion: nil)
}
// MARK: ListAdapterDataSource
func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
guard let item = item else { return [] }
return item.items
}
func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
return EmbeddedBannerSectionController()
}
func emptyView(for listAdapter: ListAdapter) -> UIView? {
let spinner = UIActivityIndicatorView()
spinner.activityIndicatorViewStyle = .gray
return spinner
}
}
extension DiscoverBannerSectionController: UIScrollViewDelegate {
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
guard let collectionView = scrollView as? UICollectionView else { return }
collectionView.snapToCell(velocity: velocity, targetOffset: targetContentOffset, spacing: 50)
contentOffset = targetContentOffset.pointee.x
}
}
The EmbeddedBannerCollectionViewCell is a cell containing a UICollectionViewController with a UICollectionViewFlowlayout
The EmbeddedBannerSectionController return a single cell containing an image.
The size of the cell is
override func sizeForItem(at index: Int) -> CGSize {
guard let height = collectionContext?.containerSize.height,
let width = collectionContext?.containerSize.width else {
fatalError("This should not happen")
}
return CGSize(width: width - 50, height: height)
}
Hope this is enough :)
Back at the computer tomorrow. Will take a look then:)
I got it!
I could use my CenterCellCollectionViewFlowLayout
Just forgot to diable paging.... 🤦♂️🤦♂️🤦♂️🤦♂️🤦♂️🤦♂️🤦♂️🤦♂️🤦♂️🤦♂️🤦♂️🤦♂️🤦♂️🤦♂️
// Source: http://blog.karmadust.com/centered-paging-with-preview-cells-on-uicollectionview/
import UIKit
class CenterCellCollectionViewFlowLayout: UICollectionViewFlowLayout {
var mostRecentOffset: CGPoint = CGPoint.zero
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
if velocity.x == 0 {
return self.mostRecentOffset
}
guard let cv = self.collectionView,
let attributesForVisibleCells = self.layoutAttributesForElements(in: cv.bounds) else {
// Fallback
self.mostRecentOffset = super.targetContentOffset(forProposedContentOffset: proposedContentOffset)
return self.mostRecentOffset
}
let halfWidth = cv.bounds.size.width * 0.5
var candidateAttributes: UICollectionViewLayoutAttributes?
for attributes in attributesForVisibleCells {
// Skip comparison with non-cell items (headers and footers)
if attributes.representedElementCategory != UICollectionElementCategory.cell {
continue
}
if (attributes.center.x == 0) || (attributes.center.x > (cv.contentOffset.x + halfWidth) && velocity.x < 0) {
continue
}
candidateAttributes = attributes
}
// Beautification step , I don't know why it works!
if proposedContentOffset.x == -(cv.contentInset.left) {
return proposedContentOffset
}
guard let attributes = candidateAttributes else {
return mostRecentOffset
}
self.mostRecentOffset = CGPoint(x: floor(attributes.center.x - halfWidth), y: proposedContentOffset.y)
return self.mostRecentOffset
}
}
Glad you fixed it :)
Odd question, what is contentOffset you are setting @heumn / @kimdv? I don't see a declaration or where else it is being used.
Thanks!
I still need help on this, anybody has any idea?
Most helpful comment
Odd question, what is contentOffset you are setting @heumn / @kimdv? I don't see a declaration or where else it is being used.
Thanks!