Skip to content

Instantly share code, notes, and snippets.

@wleii
Last active January 16, 2018 07:42
Show Gist options
  • Save wleii/a39a9324b4a87cf52a9610154bfbca3d to your computer and use it in GitHub Desktop.
Save wleii/a39a9324b4a87cf52a9610154bfbca3d to your computer and use it in GitHub Desktop.
// 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