GBA002/Delta/Components/Collection View/GridCollectionViewLayout.swift
2018-11-12 14:51:29 -08:00

155 lines
5.3 KiB
Swift

//
// GridCollectionViewLayout.swift
// Delta
//
// Created by Riley Testut on 10/24/15.
// Copyright © 2015 Riley Testut. All rights reserved.
//
import UIKit
class GridCollectionViewLayout: UICollectionViewFlowLayout
{
var itemWidth: CGFloat = 150 {
didSet {
// Only invalidate if needed, otherwise could potentially cause endless loop
if oldValue != self.itemWidth
{
self.invalidateLayout()
}
}
}
// If only one row, distribute the items equally horizontally
var usesEqualHorizontalSpacingDistributionForSingleRow = false
private var contentInset: UIEdgeInsets {
guard let collectionView = self.collectionView else { return .zero }
var contentInset = collectionView.contentInset
contentInset.left += collectionView.safeAreaInsets.left
contentInset.right += collectionView.safeAreaInsets.right
return contentInset
}
private var contentWidth: CGFloat {
guard let collectionView = self.collectionView else { return 0.0 }
let contentWidth = collectionView.bounds.width - (self.contentInset.left + self.contentInset.right)
return contentWidth
}
private var maximumItemsPerRow: Int {
let maximumItemsPerRow = Int(floor((self.contentWidth - self.minimumInteritemSpacing) / (self.itemWidth + self.minimumInteritemSpacing)))
return maximumItemsPerRow
}
private var interitemSpacing: CGFloat {
let interitemSpacing = (self.contentWidth - CGFloat(self.maximumItemsPerRow) * self.itemWidth) / CGFloat(self.maximumItemsPerRow + 1)
return interitemSpacing
}
override var estimatedItemSize: CGSize {
didSet {
fatalError("GridCollectionViewLayout does not support self-sizing cells.")
}
}
override func prepare()
{
super.prepare()
self.sectionInset.left = self.interitemSpacing + self.contentInset.left
self.sectionInset.right = self.interitemSpacing + self.contentInset.right
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?
{
let layoutAttributes = super.layoutAttributesForElements(in: rect)?.map({ $0.copy() }) as! [UICollectionViewLayoutAttributes]
var minimumY: CGFloat? = nil
var maximumY: CGFloat? = nil
var tempLayoutAttributes: [UICollectionViewLayoutAttributes] = []
var isSingleRow = true
for (index, attributes) in layoutAttributes.enumerated()
{
guard attributes.representedElementCategory == .cell else { continue }
// Ensure equal spacing between items (that also match the section insets)
if index > 0
{
let previousLayoutAttributes = layoutAttributes[index - 1]
if abs(attributes.frame.minX - self.sectionInset.left) > 1
{
attributes.frame.origin.x = previousLayoutAttributes.frame.maxX + self.interitemSpacing
}
}
if let maxY = maximumY, let minY = minimumY
{
// If attributes.frame.minY is greater than maximumY, then it is a new row
// In this case, we need to align all the previous tempLayoutAttributes to the same Y-value
if attributes.frame.minY > maxY
{
isSingleRow = false
self.align(tempLayoutAttributes, toMinimumY: minY)
// Reset tempLayoutAttributes
tempLayoutAttributes.removeAll()
minimumY = nil
maximumY = nil
}
}
// Update minimumY value if needed
if minimumY == nil || attributes.frame.minY < minimumY!
{
minimumY = attributes.frame.minY
}
// Update maximumY value if needed
if maximumY == nil || attributes.frame.maxY > maximumY!
{
maximumY = attributes.frame.maxY
}
tempLayoutAttributes.append(attributes)
}
// Handle the remaining tempLayoutAttributes
if let minimumY = minimumY
{
self.align(tempLayoutAttributes, toMinimumY: minimumY)
if isSingleRow && self.usesEqualHorizontalSpacingDistributionForSingleRow
{
let spacing = (self.contentWidth - (self.itemWidth * CGFloat(tempLayoutAttributes.count))) / (CGFloat(tempLayoutAttributes.count) + 1.0)
for (index, layoutAttributes) in tempLayoutAttributes.enumerated()
{
layoutAttributes.frame.origin.x = spacing + (spacing + self.itemWidth) * CGFloat(index) + self.contentInset.left
}
}
}
return layoutAttributes
}
}
private extension GridCollectionViewLayout
{
func align(_ layoutAttributes: [UICollectionViewLayoutAttributes], toMinimumY minimumY: CGFloat)
{
for attributes in layoutAttributes
{
attributes.frame.origin.y = minimumY
}
}
}