Files
InfiniteCollectionView/InfiniteCollectionView/InfiniteCollectionView.swift
T
2016-05-13 17:38:47 +09:00

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)
}
}