125 lines
5.6 KiB
Swift
125 lines
5.6 KiB
Swift
//
|
|
// InfiniteCollectionView.swift
|
|
// Pods
|
|
//
|
|
// Created by hryk224 on 2015/10/17.
|
|
//
|
|
//
|
|
|
|
import UIKit
|
|
|
|
public protocol InfiniteCollectionViewDataSource: class {
|
|
func cellForItemAtIndexPath(collectionView: UICollectionView, dequeueIndexPath: NSIndexPath, indexPath: NSIndexPath) -> UICollectionViewCell
|
|
func numberOfItems(collectionView: UICollectionView) -> Int
|
|
}
|
|
|
|
@objc public protocol InfiniteCollectionViewDelegate: class {
|
|
optional func didSelectCellAtIndexPath(collectionView: UICollectionView, indexPath: NSIndexPath)
|
|
optional func didUpdatePageIndex(index: Int)
|
|
}
|
|
|
|
public class InfiniteCollectionView: UICollectionView {
|
|
private typealias Me = InfiniteCollectionView
|
|
private static let dummyCount: Int = 3
|
|
private static let defaultIdentifier = "Cell"
|
|
public weak var infiniteDataSource: InfiniteCollectionViewDataSource?
|
|
public weak var infiniteDelegate: InfiniteCollectionViewDelegate?
|
|
public var cellWidth: CGFloat = UIScreen.mainScreen().bounds.width
|
|
private var indexOffset: Int = 0
|
|
private var currentIndex: Int = 0
|
|
required public init?(coder aDecoder: NSCoder) {
|
|
super.init(coder: aDecoder)
|
|
configure()
|
|
}
|
|
override public init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
|
|
super.init(frame: frame, collectionViewLayout: layout)
|
|
configure()
|
|
}
|
|
}
|
|
|
|
// MARK: - private
|
|
private extension InfiniteCollectionView {
|
|
func configure() {
|
|
delegate = self
|
|
dataSource = self
|
|
registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: Me.defaultIdentifier)
|
|
}
|
|
|
|
func centerIfNeeded(scrollView: UIScrollView) {
|
|
let currentOffset = contentOffset
|
|
let contentWidth = totalContentWidth()
|
|
// Calculate the centre of content X position offset and the current distance from that centre point
|
|
let centerOffsetX: CGFloat = (CGFloat(Me.dummyCount) * contentWidth - bounds.size.width) / 2
|
|
let distFromCentre = centerOffsetX - currentOffset.x
|
|
if fabs(distFromCentre) > (contentWidth / 4) {
|
|
// Total cells (including partial cells) from centre
|
|
let cellcount = distFromCentre / cellWidth
|
|
// Amount of cells to shift (whole number) - conditional statement due to nature of +ve or -ve cellcount
|
|
let shiftCells = Int((cellcount > 0) ? floor(cellcount) : ceil(cellcount))
|
|
// Amount left over to correct for
|
|
let offsetCorrection = (abs(cellcount) % 1) * cellWidth
|
|
// Scroll back to the centre of the view, offset by the correction to ensure it's not noticable
|
|
if centerOffsetX > contentOffset.x {
|
|
//left scrolling
|
|
contentOffset = CGPoint(x: centerOffsetX - offsetCorrection, y: currentOffset.y)
|
|
} else if contentOffset.x > centerOffsetX {
|
|
//right scrolling
|
|
contentOffset = CGPoint(x: centerOffsetX + offsetCorrection, y: currentOffset.y)
|
|
}
|
|
// Make content shift as per shiftCells
|
|
shiftContentArray(correctedIndex(shiftCells))
|
|
reloadData()
|
|
}
|
|
let centerPoint = CGPoint(x: scrollView.frame.size.width / 2 + scrollView.contentOffset.x, y: scrollView.frame.size.height / 2 + scrollView.contentOffset.y)
|
|
guard let indexPath = indexPathForItemAtPoint(centerPoint) else { return }
|
|
infiniteDelegate?.didUpdatePageIndex?(correctedIndex(indexPath.item - indexOffset))
|
|
}
|
|
func shiftContentArray(offset: Int) {
|
|
indexOffset += offset
|
|
}
|
|
func totalContentWidth() -> CGFloat {
|
|
let numberOfCells = infiniteDataSource?.numberOfItems(self) ?? 0
|
|
return CGFloat(numberOfCells) * cellWidth
|
|
}
|
|
func correctedIndex(indexToCorrect: Int) -> Int {
|
|
if let numberOfItems = infiniteDataSource?.numberOfItems(self) {
|
|
if numberOfItems > indexToCorrect && indexToCorrect >= 0 {
|
|
return indexToCorrect
|
|
} else {
|
|
let countInIndex = Float(indexToCorrect) / Float(numberOfItems)
|
|
let flooredValue = Int(floor(countInIndex))
|
|
let offset = numberOfItems * flooredValue
|
|
return indexToCorrect - offset
|
|
}
|
|
} else {
|
|
return 0
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - UICollectionViewDataSource
|
|
extension InfiniteCollectionView: UICollectionViewDataSource {
|
|
public func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
|
let numberOfItems = infiniteDataSource?.numberOfItems(self) ?? 0
|
|
return Me.dummyCount * numberOfItems
|
|
}
|
|
public func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
|
|
var maybeCell: UICollectionViewCell!
|
|
maybeCell = infiniteDataSource?.cellForItemAtIndexPath(self, dequeueIndexPath: indexPath, indexPath: NSIndexPath(forRow: correctedIndex(indexPath.item - indexOffset), inSection: 0))
|
|
if maybeCell == nil {
|
|
maybeCell = collectionView.dequeueReusableCellWithReuseIdentifier(Me.defaultIdentifier, forIndexPath: indexPath)
|
|
}
|
|
return maybeCell
|
|
}
|
|
}
|
|
|
|
// MARK: - UICollectionViewDelegate
|
|
extension InfiniteCollectionView: UICollectionViewDelegate {
|
|
public func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
|
|
infiniteDelegate?.didSelectCellAtIndexPath?(self, indexPath: NSIndexPath(forRow: correctedIndex(indexPath.item - indexOffset), inSection: 0))
|
|
}
|
|
public func scrollViewDidScroll(scrollView: UIScrollView) {
|
|
centerIfNeeded(scrollView)
|
|
}
|
|
}
|