Search code examples
iosswiftuicollectionviewuicollectionviewcelluicollectionviewlayout

Collection cell that fills entire width of screen and scrolls only one at a time in swift?


I have a UICollectionView that has 20 cells and 1 section. I made each cell 320 in width and 304 in height.

I scroll the collection view programmatically using two buttons at the bottom of the collection view using scrollToItemAtIndexPath(currentIndex + 1). I only scroll them 1 by 1.

This works fine in iPhone 4s and iPhone 5/5s. The problem appears when using an iPhone 6/6 Plus.

When I scrollToItemAtIndexPath it scrolls 2 cells at a time. How can I prevent this from happening? I tried to make the cell fit the width of the screen but just one cell appeared, and the rest of the UICollectionView was black.

EDIT:

Here is the datasource code:

extension ViewController: UICollectionViewDataSource {

    func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
    return 1
    }

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

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier("collectionCell", forIndexPath: indexPath) as! CollectionCell
    cell.backgroundColor = UIColor.whiteColor()

    self.current = indexPath

    self.configureCell(cell, atIndexPath: indexPath) as! CollectionCell
    return cell
    }

    func configureCell(cell: CollectionCell, atIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let string = textArray[indexPath.item % 20] as String
    cell.textView.text = string
    cell.textView.backgroundColor = UIColor.cloudsColor()
    return cell
    }
}

And here is how I scroll them:

func buttonSelected(sender: UIButton) {
    switch sender.tag {
      case 0:
      let previousItem: NSIndexPath = NSIndexPath(forItem: self.current.item - 1, inSection: self.current.section)
      self.collectionView?.scrollToItemAtIndexPath(previousItem, atScrollPosition:UICollectionViewScrollPosition.CenteredHorizontally, animated:true)
      case 1:
      let nextItem: NSIndexPath = NSIndexPath(forItem: self.current.item + 1, inSection: self.current.section)
      self.collectionView?.scrollToItemAtIndexPath(nextItem, atScrollPosition:UICollectionViewScrollPosition.CenteredHorizontally, animated:true)
      default: break
      }
}

Solution

  • I've solved this using a custom FlowLayout in the collection view. Here it is:

        class CenterFlowLayout: UICollectionViewFlowLayout {
    
        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)
        }
      }