Last active
January 16, 2018 07:42
-
-
Save wleii/a39a9324b4a87cf52a9610154bfbca3d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MARK: - FlowLayout | |
private class FlowLayout: UICollectionViewFlowLayout, WaterfullFlowLayout { | |
var preparedLayoutAttributes: [UICollectionViewLayoutAttributes] = [] | |
override init() { | |
super.init() | |
callInit() | |
} | |
required init?(coder aDecoder: NSCoder) { | |
super.init(coder: aDecoder) | |
callInit() | |
} | |
override func prepare() { | |
callPrepare() | |
} | |
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { | |
return callLayoutAttributesForItem(at: indexPath) | |
} | |
override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { | |
return calllayoutAttributesForSupplementaryView(ofKind: elementKind, at: indexPath) | |
} | |
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { | |
return callLayoutAttributesForElement(in: rect) | |
} | |
override var collectionViewContentSize: CGSize { | |
return contentSize | |
} | |
} | |
protocol WaterfullFlowLayout: class { | |
var preparedLayoutAttributes: [UICollectionViewLayoutAttributes] { set get } | |
} | |
extension WaterfullFlowLayout where Self: UICollectionViewFlowLayout { | |
private var sectionInsetDecorationViewKind: String { | |
return "SectionInsetDecorationViewKind" | |
} | |
func callInit() { | |
register(UICollectionReusableView.self, forDecorationViewOfKind: sectionInsetDecorationViewKind) | |
} | |
var contentSize: CGSize { | |
guard let collectionView = collectionView else { return .zero } | |
let inset = contentInset | |
return CGSize(width: collectionView.bounds.width - (inset.left + inset.right), | |
height: preparedLayoutAttributes.last?.frame.maxY ?? 0 + (inset.top + inset.bottom)) | |
} | |
func callPrepare() { | |
guard let collectionView = collectionView else { return } | |
guard let dataSource = collectionView.dataSource else { return } | |
preparedLayoutAttributes.removeAll() | |
let numberOfSections = collectionView.dataSource?.numberOfSections?(in: collectionView) ?? 1 | |
let contentInset = self.contentInset | |
for section in 0..<numberOfSections { | |
if let headerAttributes = self.headerAttributes(inSection: section) { | |
preparedLayoutAttributes.append(headerAttributes) | |
} | |
let sectionInset = self.sectionInset(at: section) | |
let itemSpacing: CGFloat = flowlayoutDelegate? | |
.collectionView?(collectionView, layout: self, | |
minimumInteritemSpacingForSectionAt: section) ?? minimumInteritemSpacing | |
let lineSpacing: CGFloat = flowlayoutDelegate? | |
.collectionView?(collectionView, layout: self, | |
minimumLineSpacingForSectionAt: section) ?? minimumLineSpacing | |
var previousFrame = CGRect.zero | |
let totalLeftInset = contentInset.left + sectionInset.left | |
let contentWidth = collectionView.bounds.width - | |
(contentInset.left + contentInset.right) - | |
(sectionInset.left + sectionInset.right) | |
if sectionInset.top > 0 { | |
let decorationView = UICollectionViewLayoutAttributes | |
.init(forDecorationViewOfKind: sectionInsetDecorationViewKind, | |
with: IndexPath(item: 0, section: section)) | |
decorationView.frame = CGRect(x: totalLeftInset, | |
y: (preparedLayoutAttributes.last?.frame.maxY ?? 0), | |
width: contentWidth, | |
height: sectionInset.top) | |
preparedLayoutAttributes.append(decorationView) | |
} | |
let numberOfItems = 0..<dataSource.collectionView(collectionView, numberOfItemsInSection: section) | |
for (currentItemIndex, item) in numberOfItems.enumerated() { | |
let indexPath = IndexPath(item: item, section: section) | |
let currentItemSize = flowlayoutDelegate? | |
.collectionView?(collectionView, layout: self, | |
sizeForItemAt: indexPath) ?? self.itemSize | |
let layoutAttributes = UICollectionViewLayoutAttributes.init(forCellWith: indexPath) | |
var finalFrame = CGRect(origin: previousFrame.origin, size: currentItemSize) | |
if currentItemIndex == 0 { | |
let originX = totalLeftInset | |
finalFrame.origin = CGPoint(x: originX, | |
y: preparedLayoutAttributes.last?.frame.maxY ?? 0) | |
previousFrame = finalFrame | |
}else { | |
if previousFrame.maxX + currentItemSize.width + itemSpacing > contentWidth + totalLeftInset { | |
finalFrame.origin = CGPoint(x: totalLeftInset, | |
y: (preparedLayoutAttributes.last?.frame.maxY ?? 0) + lineSpacing) | |
}else { | |
let originX = previousFrame.maxX + itemSpacing | |
finalFrame.origin = CGPoint(x: originX, y: previousFrame.origin.y) | |
} | |
} | |
layoutAttributes.frame = finalFrame | |
preparedLayoutAttributes.append(layoutAttributes) | |
previousFrame = finalFrame | |
} | |
if sectionInset.bottom > 0 { | |
let decorationView = UICollectionViewLayoutAttributes | |
.init(forDecorationViewOfKind: sectionInsetDecorationViewKind, | |
with: IndexPath(item: 0, section: section)) | |
decorationView.frame = CGRect(x: totalLeftInset, | |
y: (preparedLayoutAttributes.last?.frame.maxY ?? 0), | |
width: contentWidth, | |
height: sectionInset.bottom) | |
preparedLayoutAttributes.append(decorationView) | |
} | |
if let footerAttributes = footerAttributes(inSection: section) { | |
preparedLayoutAttributes.append(footerAttributes) | |
} | |
} | |
} | |
func callLayoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { | |
return preparedLayoutAttributes.filter({ $0.representedElementCategory == .cell && $0.indexPath == indexPath}).first | |
} | |
func calllayoutAttributesForSupplementaryView(ofKind elementKind: String, | |
at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { | |
return preparedLayoutAttributes.filter({ $0.representedElementKind == elementKind | |
&& $0.representedElementCategory == .supplementaryView | |
&& $0.indexPath == indexPath}).first | |
} | |
func callLayoutAttributesForElement(in rect: CGRect) -> [UICollectionViewLayoutAttributes] { | |
var layoutAttributes = [UICollectionViewLayoutAttributes]() | |
for attributes in preparedLayoutAttributes { | |
if attributes.frame.intersects(rect) { | |
layoutAttributes.append(attributes) | |
} | |
} | |
return layoutAttributes | |
} | |
// MARK: Private | |
private var contentInset: UIEdgeInsets { | |
guard let collectionView = collectionView else { | |
return .zero | |
} | |
return collectionView.contentInset | |
} | |
private func sectionInset(at section: Int) -> UIEdgeInsets { | |
guard let collectionView = collectionView else { | |
return .zero | |
} | |
return flowlayoutDelegate?.collectionView?(collectionView, layout: self, insetForSectionAt: section) ?? sectionInset | |
} | |
private func headerAttributes(inSection section: Int) -> UICollectionViewLayoutAttributes? { | |
guard let collectionView = collectionView else { | |
fatalError() | |
} | |
let size: CGSize = flowlayoutDelegate? | |
.collectionView?(collectionView, | |
layout: self, | |
referenceSizeForHeaderInSection: section) ?? headerReferenceSize | |
if size.equalTo(.zero) { | |
return nil | |
} | |
let headerAttributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, with: IndexPath(item: 0, section: section)) | |
let previousFrame = preparedLayoutAttributes.last?.frame ?? .zero | |
var maxY = previousFrame.maxY | |
if section == 0 { | |
maxY += contentInset.top | |
} | |
headerAttributes.frame = CGRect(origin: CGPoint(x: 0, y: previousFrame.maxY), size: size) | |
return headerAttributes | |
} | |
private func footerAttributes(inSection section: Int) -> UICollectionViewLayoutAttributes? { | |
guard let collectionView = collectionView else { | |
fatalError() | |
} | |
let size: CGSize = flowlayoutDelegate? | |
.collectionView?(collectionView, | |
layout: self, | |
referenceSizeForFooterInSection: section) ?? footerReferenceSize | |
if size.equalTo(.zero) { | |
return nil | |
} | |
let footerAttributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, with: IndexPath(item: 0, section: section)) | |
let previousFrame = preparedLayoutAttributes.last?.frame ?? .zero | |
footerAttributes.frame = CGRect(x: 0, y: previousFrame.maxY , width: size.width, height: size.height) | |
return footerAttributes | |
} | |
private var flowlayoutDelegate: UICollectionViewDelegateFlowLayout? { | |
return collectionView?.delegate as? UICollectionViewDelegateFlowLayout | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment