Skip to content

Instantly share code, notes, and snippets.

@tomaskraina
Last active July 20, 2021 14:04
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tomaskraina/1eb291e4717f14ad6e0f8e60ffe9b7d3 to your computer and use it in GitHub Desktop.
Save tomaskraina/1eb291e4717f14ad6e0f8e60ffe9b7d3 to your computer and use it in GitHub Desktop.
Show Separators in Collection View Between Cells - make spaces between cells have different color than the background
//
// CollectionSeparatorView.swift
// tomkraina.com
//
// Created by Tom Kraina on 25.7.2017.
// Copyright © 2017 Tom Kraina. All rights reserved.
//
import UIKit
class CollectionSeparatorView: UICollectionReusableView {
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .black
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
frame = layoutAttributes.frame
}
}
//
// SeparatorCollectionViewFlowLayout.swift
// tomkraina.com
//
// Created by Tom Kraina on 1.8.2017.
// Copyright © 2017 Tom Kraina. All rights reserved.
//
import Foundation
// More info: https://www.raizlabs.com/dev/2014/02/animating-items-in-a-uicollectionview/
class SeparatorCollectionViewFlowLayout: UICollectionViewFlowLayout {
private var indexPathsToInsert: [IndexPath] = []
private var indexPathsToDelete: [IndexPath] = []
// MARK: - Lifecycle
override init() {
super.init()
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override func prepare(forCollectionViewUpdates updateItems: [UICollectionViewUpdateItem]) {
super.prepare(forCollectionViewUpdates: updateItems)
for item in updateItems {
switch item.updateAction {
case .delete:
if let indexPath = item.indexPathBeforeUpdate {
indexPathsToDelete.append(indexPath)
}
case .insert:
if let indexPath = item.indexPathAfterUpdate {
indexPathsToInsert.append(indexPath)
}
default:
break
}
}
}
override func finalizeCollectionViewUpdates() {
super.finalizeCollectionViewUpdates()
indexPathsToDelete.removeAll()
indexPathsToInsert.removeAll()
}
override func indexPathsToDeleteForDecorationView(ofKind elementKind: String) -> [IndexPath] {
return indexPathsToDelete
}
override func indexPathsToInsertForDecorationView(ofKind elementKind: String) -> [IndexPath] {
return indexPathsToInsert
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let layoutAttributesArray = super.layoutAttributesForElements(in: rect) else { return nil }
var decorationAttributes: [UICollectionViewLayoutAttributes] = []
for layoutAttributes in layoutAttributesArray {
let indexPath = layoutAttributes.indexPath
if let separatorAttributes = layoutAttributesForDecorationView(ofKind: CollectionSeparatorView.reusableIdentifier, at: indexPath) {
if rect.intersects(separatorAttributes.frame) {
decorationAttributes.append(separatorAttributes)
}
}
}
let allAttributes = layoutAttributesArray + decorationAttributes
return allAttributes
}
override func layoutAttributesForDecorationView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let cellAttributes = layoutAttributesForItem(at: indexPath) else {
return createAttributesForMyDecoration(at: indexPath)
}
return layoutAttributesForMyDecoratinoView(at: indexPath, for: cellAttributes.frame, state: .normal)
}
override func initialLayoutAttributesForAppearingDecorationElement(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let cellAttributes = initialLayoutAttributesForAppearingItem(at: indexPath) else {
return createAttributesForMyDecoration(at: indexPath)
}
return layoutAttributesForMyDecoratinoView(at: indexPath, for: cellAttributes.frame, state: .initial)
}
override func finalLayoutAttributesForDisappearingDecorationElement(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let cellAttributes = finalLayoutAttributesForDisappearingItem(at: indexPath) else {
return createAttributesForMyDecoration(at: indexPath)
}
return layoutAttributesForMyDecoratinoView(at: indexPath, for: cellAttributes.frame, state: .final)
}
// MARK: - privates
private enum State {
case initial
case normal
case final
}
private func setup() {
register(CollectionSeparatorView.self, forDecorationViewOfKind: CollectionSeparatorView.reusableIdentifier)
}
private func createAttributesForMyDecoration(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes {
return UICollectionViewLayoutAttributes(forDecorationViewOfKind: CollectionSeparatorView.reusableIdentifier, with: indexPath)
}
private func layoutAttributesForMyDecoratinoView(at indexPath: IndexPath, for cellFrame: CGRect, state: State) -> UICollectionViewLayoutAttributes? {
guard let rect = collectionView?.bounds else {
return nil
}
//Add separator for every row except the first
guard indexPath.item > 0 else {
return nil
}
let separatorAttributes = createAttributesForMyDecoration(at: indexPath)
separatorAttributes.alpha = 1.0
separatorAttributes.isHidden = false
let firstCellInRow = cellFrame.origin.x < cellFrame.width
if firstCellInRow {
// horizontal line
separatorAttributes.frame = CGRect(x: rect.minX, y: cellFrame.origin.y - minimumLineSpacing, width: rect.width, height: minimumLineSpacing)
separatorAttributes.zIndex = 1000
} else {
// vertical line
separatorAttributes.frame = CGRect(x: cellFrame.origin.x - minimumInteritemSpacing, y: cellFrame.origin.y, width: minimumInteritemSpacing, height: cellFrame.height)
separatorAttributes.zIndex = 1000
}
// Sync the decorator animation with the cell animation in order to avoid blinkining
switch state {
case .normal:
separatorAttributes.alpha = 1
default:
separatorAttributes.alpha = 0.1
}
return separatorAttributes
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment