vendoo_v1.0/Pods/BSGridCollectionViewLayout/Pod/Classes/GridCollectionViewLayout.swift

285 lines
9.4 KiB
Swift

// The MIT License (MIT)
//
// Copyright (c) 2015 Joakim Gyllström
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
/**
Provides a grid collection view layout
*/
@objc(BSGridCollectionViewLayout)
public final class GridCollectionViewLayout: UICollectionViewLayout {
/**
Spacing between items (horizontal and vertical)
*/
public var itemSpacing: CGFloat = 0 {
didSet {
_itemSize = estimatedItemSize()
}
}
/**
Number of items per row
*/
public var itemsPerRow = 3 {
didSet {
_itemSize = estimatedItemSize()
}
}
/**
Item height ratio relative to it's width
*/
public var itemHeightRatio: CGFloat = 1 {
didSet {
_itemSize = estimatedItemSize()
}
}
/**
Size for each item
*/
public var itemSize: CGSize {
get {
return _itemSize
}
}
var items = 0
var rows = 0
var _itemSize = CGSizeZero
public override func prepareLayout() {
// Set total number of items and rows
items = estimatedNumberOfItems()
rows = items / itemsPerRow + ((items % itemsPerRow > 0) ? 1 : 0)
// Set item size
_itemSize = estimatedItemSize()
}
/**
See UICollectionViewLayout documentation
*/
public override func collectionViewContentSize() -> CGSize {
guard let collectionView = collectionView where rows > 0 else {
return CGSizeZero
}
let height = estimatedRowHeight() * CGFloat(rows)
return CGSize(width: collectionView.bounds.width, height: height)
}
/**
See UICollectionViewLayout documentation
*/
public override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
return indexPathsInRect(rect).map { (indexPath) -> UICollectionViewLayoutAttributes in
return self.layoutAttributesForItemAtIndexPath(indexPath)! // TODO: Fix forcefull unwrap
}
}
/**
See UICollectionViewLayout documentation
*/
public override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
let itemIndex = flatIndex(indexPath) // index among total number of items
let rowIndex = itemIndex % itemsPerRow // index within it's row
let row = itemIndex / itemsPerRow // which row for that item
let x = (CGFloat(rowIndex) * itemSpacing) + (CGFloat(rowIndex) * itemSize.width)
let y = (CGFloat(row) * itemSpacing) + (CGFloat(row) * itemSize.height)
let width = _itemSize.width
let height = _itemSize.height
let attribute = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)
attribute.frame = CGRect(x: x, y: y, width: width, height: height)
return attribute
}
/**
See UICollectionViewLayout documentation
*/
public override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
return true
}
// No decoration or supplementary views
/**
See UICollectionViewLayout documentation
*/
public override func layoutAttributesForDecorationViewOfKind(elementKind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? { return nil }
/**
See UICollectionViewLayout documentation
*/
public override func layoutAttributesForSupplementaryViewOfKind(elementKind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? { return nil }
}
extension GridCollectionViewLayout {
/**
Calculates which index paths are within a given rect
- parameter rect: The rect which we want index paths for
- returns: An array of indexPaths for that rect
*/
func indexPathsInRect(rect: CGRect) -> [NSIndexPath] {
// Make sure we have items/rows
guard items > 0 && rows > 0 else { return [] }
let rowHeight = estimatedRowHeight()
let startRow = GridCollectionViewLayout.firstRowInRect(rect, withRowHeight: rowHeight)
let endRow = GridCollectionViewLayout.lastRowInRect(rect, withRowHeight: rowHeight, max: rows)
let startIndex = GridCollectionViewLayout.firstIndexInRow(startRow, withItemsPerRow: itemsPerRow)
let endIndex = GridCollectionViewLayout.lastIndexInRow(endRow, withItemsPerRow: itemsPerRow, numberOfItems: items)
let indexPaths = (startIndex...endIndex).map { indexPathFromFlatIndex($0) }
return indexPaths
}
/**
Calculates which row index would be first for a given rect.
- parameter rect: The rect to check
- parameter rowHeight: Height for a row
- returns: First row index
*/
static func firstRowInRect(rect: CGRect, withRowHeight rowHeight: CGFloat) -> Int {
if rect.origin.y / rowHeight < 0 {
return 0
} else {
return Int(rect.origin.y / rowHeight)
}
}
/**
Calculates which row index would be last for a given rect.
- parameter rect: The rect to check
- parameter rowHeight: Height for a row
- returns: Last row index
*/
static func lastRowInRect(rect: CGRect, withRowHeight rowHeight: CGFloat, max: Int) -> Int {
guard rect.size.height >= rowHeight else { return 0 }
if (rect.origin.y + rect.height) / rowHeight > CGFloat(max) {
return max - 1
} else {
return Int(ceil((rect.origin.y + rect.height) / rowHeight)) - 1
}
}
/**
Calculates which index would be the first for a given row.
- parameter row: Row index
- parameter itemsPerRow: How many items there can be in a row
- returns: First index
*/
static func firstIndexInRow(row: Int, withItemsPerRow itemsPerRow: Int) -> Int {
return row * itemsPerRow
}
/**
Calculates which index would be the last for a given row.
- parameter row: Row index
- parameter itemsPerRow: How many items there can be in a row
- parameter numberOfItems: The total number of items.
- returns: Last index
*/
static func lastIndexInRow(row: Int, withItemsPerRow itemsPerRow: Int, numberOfItems: Int) -> Int {
let maxIndex = (row + 1) * itemsPerRow - 1
let bounds = numberOfItems - 1
if maxIndex > bounds {
return bounds
} else {
return maxIndex
}
}
/**
Takes an index path (which are 2 dimensional) and turns it into a 1 dimensional index
- parameter indexPath: The index path we want to flatten
- returns: A flat index
*/
func flatIndex(indexPath: NSIndexPath) -> Int {
guard let collectionView = collectionView else {
return 0
}
return (0..<indexPath.section).reduce(indexPath.row) { $0 + collectionView.numberOfItemsInSection($1)}
}
/**
Converts a flat index into an index path
- parameter index: The flat index
- returns: An index path
*/
func indexPathFromFlatIndex(index: Int) -> NSIndexPath {
guard let collectionView = collectionView else {
return NSIndexPath(forItem: 0, inSection: 0)
}
var item = index
var section = 0
while(item >= collectionView.numberOfItemsInSection(section)) {
item -= collectionView.numberOfItemsInSection(section)
section += 1
}
return NSIndexPath(forItem: item, inSection: section)
}
/**
Estimated the size of the items
- returns: Estimated item size
*/
func estimatedItemSize() -> CGSize {
guard let collectionView = collectionView else {
return CGSizeZero
}
let itemWidth = (collectionView.bounds.width - CGFloat(itemsPerRow - 1) * itemSpacing) / CGFloat(itemsPerRow)
return CGSize(width: itemWidth, height: itemWidth * itemHeightRatio)
}
/**
Estimated total number of items
- returns: Total number of items
*/
func estimatedNumberOfItems() -> Int {
guard let collectionView = collectionView else {
return 0
}
return (0..<collectionView.numberOfSections()).reduce(0, combine: {$0 + collectionView.numberOfItemsInSection($1)})
}
/**
Height for each row
- returns: Row height
*/
func estimatedRowHeight() -> CGFloat {
return _itemSize.height+itemSpacing
}
}