Yossi Tsafar Yossi Tsafar - 6 months ago 39
Swift Question

Create a Paging UICollectionView with Swift

I'm trying to create a UICollectionView with paging and that each item max width is 250 points, I've managed to create it, but I have 2 problems: The first item start not as it should be, but with more space at start and when I try to swipe, there is always something that wont let me swipe smooth.

This is how it looks:

video link

This is my code:

CenterCellCollectionFlowLayout.swift

class CenterCellCollectionViewFlowLayout: UICollectionViewFlowLayout {

override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {

var attributesToReturn:[UICollectionViewLayoutAttributes] = super.layoutAttributesForElementsInRect(rect) as! [UICollectionViewLayoutAttributes]

for var i = 0 ; i < attributesToReturn.count ; i++
{
var currentLayoutAttributes: UICollectionViewLayoutAttributes = attributesToReturn[i]
var maximumSpacing: CGFloat = 50
let origin: CGFloat
if i - 1 >= 0 {
let previousLayoutAttributes = attributesToReturn[i - 1]
origin = previousLayoutAttributes.frame.maxX
} else {
origin = 0
}

if origin + maximumSpacing + currentLayoutAttributes.frame.size.width < self.collectionViewContentSize().width
{
var frame: CGRect = currentLayoutAttributes.frame
frame.origin.x = origin + maximumSpacing
currentLayoutAttributes.frame = frame
}
}

return attributesToReturn

}

override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {

if let cv = self.collectionView {

let cvBounds = cv.bounds
let halfWidth = cvBounds.size.width * 0.5;
let proposedContentOffsetCenterX = proposedContentOffset.x + halfWidth;

if let attributesForVisibleCells = self.layoutAttributesForElementsInRect(cvBounds) as? [UICollectionViewLayoutAttributes] {

var candidateAttributes : UICollectionViewLayoutAttributes?
for attributes in attributesForVisibleCells {

// == Skip comparison with non-cell items (headers and footers) == //
if attributes.representedElementCategory != UICollectionElementCategory.Cell {
continue
}

if let candAttrs = candidateAttributes {

let a = attributes.center.x - proposedContentOffsetCenterX
let b = candAttrs.center.x - proposedContentOffsetCenterX

if fabsf(Float(a)) < fabsf(Float(b)) {
candidateAttributes = attributes;
}

}
else { // == First time in the loop == //

candidateAttributes = attributes;
continue;
}


}

return CGPoint(x : candidateAttributes!.center.x - halfWidth, y : proposedContentOffset.y);

}

}

// Fallback
return super.targetContentOffsetForProposedContentOffset(proposedContentOffset)
}
}


MainViewController.swift

class MainViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {

@IBOutlet weak var collectionView: UICollectionView!
@IBOutlet weak var collectionViewFlowLayout: CenterCellCollectionViewFlowLayout!

var collectionObjects: NSMutableArray?
private let reuseIdentifier = "CollectionViewCell"

override func viewDidLoad() {
super.viewDidLoad()

// Do any additional setup after loading the view.

self.collectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)

}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}

func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath)
{
}

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! UICollectionViewCell
cell.backgroundColor = UIColor.greenColor()
// Configure the cell
return cell
}

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return 10
}

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets
{
return UIEdgeInsetsMake(0, 0, 0, 0)
}

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
return CGSizeMake(250, 250)
}
}


Thanks in advance

Answer

So I figure out how to do it.

First create a custom UICollectionViewFlowLayout and add this override this method:

override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {

    if let cv = self.collectionView {

        let cvBounds = cv.bounds
        let halfWidth = cvBounds.size.width * 0.5;
        let proposedContentOffsetCenterX = proposedContentOffset.x + halfWidth;

        if let attributesForVisibleCells = self.layoutAttributesForElementsInRect(cvBounds) as? [UICollectionViewLayoutAttributes] {

            var candidateAttributes : UICollectionViewLayoutAttributes?
            for attributes in attributesForVisibleCells {

                // == Skip comparison with non-cell items (headers and footers) == //
                if attributes.representedElementCategory != UICollectionElementCategory.Cell {
                    continue
                }

                if let candAttrs = candidateAttributes {

                    let a = attributes.center.x - proposedContentOffsetCenterX
                    let b = candAttrs.center.x - proposedContentOffsetCenterX

                    if fabsf(Float(a)) < fabsf(Float(b)) {
                        candidateAttributes = attributes;
                    }

                }
                else { // == First time in the loop == //

                    candidateAttributes = attributes;
                    continue;
                }


            }

            return CGPoint(x : candidateAttributes!.center.x - halfWidth, y : proposedContentOffset.y);

        }

    }

    // Fallback
    return super.targetContentOffsetForProposedContentOffset(proposedContentOffset)
}

Then on the class that you implement the UICollectionView do like that:

    let collectionViewLayout: CenterCellCollectionViewFlowLayout = CenterCellCollectionViewFlowLayout()
    collectionViewLayout.itemSize = CGSizeMake(self.itemSize, self.itemSize)
    collectionViewLayout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
    collectionViewLayout.minimumInteritemSpacing = 0
    collectionViewLayout.minimumLineSpacing = self.itemSpacing
    collectionViewLayout.scrollDirection = UICollectionViewScrollDirection.Horizontal

    var collectionView: UICollectionView = UICollectionView(frame: self.collectionContainer.bounds, collectionViewLayout: collectionViewLayout)
    collectionView.delegate = self;
    collectionView.dataSource = self;
    collectionView.backgroundColor = UIColor.redColor()

    collectionView.registerClass(LevelsCustomCell.self, forCellWithReuseIdentifier: reuseIdentifier)
    collectionView.registerNib(UINib(nibName: reuseIdentifier, bundle: nil), forCellWithReuseIdentifier: reuseIdentifier)

    self.collectionContainer.addSubview(collectionView)

Thats about it, works like a charm.