How can I navigate to any cell in a UICollectionView using a custom layout?
I have 40 cells in my horizontal UICollectionView and button.
I can go from cell number 5 to cell number 10 when I click the button.
But as soon as I want to move to another cell (for example 5 to 25),
it doesn't work and it goes to 0 instead.
code:
func setValue(value: Int, animated: Bool) {
self.value = value
if let row = find(values, value) {
let indexPath = NSIndexPath(forRow: row, inSection: 0)
selectedCell = collectionView.cellForItemAtIndexPath(indexPath) as? StepperCell
collectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: .CenteredHorizontally, animated: animated)
}
}
override func prepareLayout() {
let start = Int(collectionView!.bounds.size.width * 0.5) - statics.width / 2
let length = collectionView!.numberOfItemsInSection(0)
if cache.isEmpty && length > 0 {
for item in 0..<length {
let indexPath = NSIndexPath(forItem: item, inSection: 0)
let attributes = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)
attributes.frame = CGRect(
x: start + (statics.width + statics.padding) * indexPath.item,
y: 0,
width: statics.width,
height: statics.height
)
cache.append(attributes)
}
contentWidth = CGRectGetMaxX(cache.last!.frame) + CGFloat(start)
}
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {
var layoutAttributes = [UICollectionViewLayoutAttributes]()
for attributes in cache {
if CGRectIntersectsRect(attributes.frame, rect) {
layoutAttributes.append(attributes)
}
}
return layoutAttributes
}
}
source to share
After some research and some help from another coder. I came up with a simpler and more efficient solution. I use my own layout just for the binding effect (when switching between cells) and then I let the flow layout handle the rest. here is the code:
struct Statics {
static let width = CGFloat(50.0)
static let height = CGFloat(50.0)
static let padding = CGFloat(5.0)
}
func commonInit() {
...
// I had to set layout from the code, didn't work from the Interface Builder
let layout = HorizontalLayout()
layout.scrollDirection = .Horizontal
collectionView.collectionViewLayout = layout
collectionView.dataSource = self
}
override func layoutSubviews() {
super.layoutSubviews()
// Set item size, line spacing and insets and let the flow layout do the rest
let layout = collectionView.collectionViewLayout as! HorizontalLayout
layout.itemSize = CGSize(width: Statics.width, height: Statics.height)
layout.minimumLineSpacing = Statics.padding
let sideMargin = (collectionView.bounds.width - Statics.width) / 2.0
layout.sectionInset = UIEdgeInsetsMake(0.0, sideMargin, 0.0, sideMargin)
setValue(value, animated: true)
}
class HorizontalLayout: UICollectionViewFlowLayout {
// This the only method you need to override
override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
let contentOffset: CGPoint
let itemWidth = itemSize.width
let padding = minimumLineSpacing
// Calculate nearest cell from the proposed content offset
let index = round(proposedContentOffset.x / (itemWidth + padding))
let x = (itemWidth + padding) * index
contentOffset = CGPoint(x: x, y: proposedContentOffset.y)
return contentOffset
}
}
source to share
I think you can get by with string logic. In the end - you just use the passed one Int
. If the sentence if let
doesn't work, it won't scroll.
Also drop the call cellForItemAtIndexPath
- you don't need it (you don't use it either).
I am guessing that perhaps you should be using NSIndexPath(forItem, inSection)
instead NSIndexPath(forRow, inSection)
. (The table view uses a string, the collectionView uses an item.)
collectionView.scrollToItemAtIndexPath(
NSIndexPath(forItem: value, inSection: 0),
atScrollPosition: .CenteredHorizontally, animated: true)
source to share