Skip to content

Instantly share code, notes, and snippets.

@Coder-ACJHP
Created October 20, 2023 14:17
Show Gist options
  • Save Coder-ACJHP/cd5d38aee95ac24506b4bb3a6aeb5ea8 to your computer and use it in GitHub Desktop.
Save Coder-ACJHP/cd5d38aee95ac24506b4bb3a6aeb5ea8 to your computer and use it in GitHub Desktop.
Custom PinterestLayout with UIKit iOS 11 (included example usage)
//
// PhotoCell.swift
// TestApp
//
// Created by Coder ACJHP on 20.10.2023.
//
import UIKit
class PhotoCell: UICollectionViewCell {
private let containerView: UIView = {
let view = UIView(frame: .zero)
view.backgroundColor = .white
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
var item: UIColor = .lightGray {
didSet {
containerView.backgroundColor = item
}
}
var padding: CGFloat = .zero {
didSet {
containerViewConstraints.forEach({
let needsToNegativeValue = ($0.firstAttribute == .bottom || $0.firstAttribute == .trailing)
$0.constant = needsToNegativeValue ? -padding : padding
})
containerView.layoutIfNeeded()
}
}
private var containerViewConstraints = Array<NSLayoutConstraint>()
override init(frame: CGRect) {
super.init(frame: frame)
initCommon()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
initCommon()
}
private final func initCommon() {
backgroundColor = .clear
contentView.backgroundColor = .clear
containerView.layer.cornerRadius = 10
containerView.layer.masksToBounds = true
contentView.addSubview(containerView)
let topConstraint = containerView.topAnchor.constraint(equalTo: contentView.topAnchor)
containerViewConstraints.append(topConstraint)
let leadingConstraint = containerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
containerViewConstraints.append(leadingConstraint)
let bottomConstraint = containerView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
containerViewConstraints.append(bottomConstraint)
let trailingConstraint = containerView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
containerViewConstraints.append(trailingConstraint)
containerViewConstraints.forEach({ $0.isActive = true })
}
override func prepareForReuse() {
super.prepareForReuse()
backgroundColor = .clear
contentView.backgroundColor = .clear
}
}
//
// PhotosGridViewController.swift
// TestApp
//
// Created by Coder ACJHP on 20.10.2023.
//
import UIKit
class PhotosGridViewController: UIViewController {
private let pinterestLayout = PinterestLayout()
private lazy var pinterestCollectionView: UICollectionView = {
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: pinterestLayout)
collectionView.backgroundColor = .clear
collectionView.showsVerticalScrollIndicator = false
collectionView.showsHorizontalScrollIndicator = false
collectionView.register(PhotoCell.self, forCellWithReuseIdentifier: CellIdentifiers.photoCellIdentifier)
collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}()
private enum CellIdentifiers {
static let photoCellIdentifier = String(describing: PhotoCell.self)
}
private var minimumCellHeight = 150.0
private var minimumCellPadding = 2.5
private var photoItems: Array<UIColor> = []
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(pinterestCollectionView)
pinterestCollectionView.fillContainer()
pinterestCollectionView.delegate = self
pinterestCollectionView.dataSource = self
pinterestLayout.delegate = self
pinterestLayout.numberOfColumns = 3
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
prepareDataSource()
}
private final func prepareDataSource() {
(0 ... Int.random(in: 20 ... 40)).forEach { _ in
let hue = CGFloat.random(in: 0...1)
let saturation = CGFloat.random(in: 0.5...1) // from 0.5 to 1.0 to stay away from white
let brightness = CGFloat.random(in: 0.5...1) // from 0.5 to 1.0 to stay away from black
photoItems.append(UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: 1))
}
pinterestCollectionView.reloadData()
}
}
extension PhotosGridViewController: UICollectionViewDelegate, UICollectionViewDataSource, PinterestLayoutDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
photoItems.count
}
func collectionView(collectionView: UICollectionView, heightForItemAtIndexPath indexPath: IndexPath) -> CGFloat {
let randomMultiplier = CGFloat.random(in: 1 ... 4)
return randomMultiplier * minimumCellHeight
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let photoCell = collectionView.dequeueReusableCell(withReuseIdentifier: CellIdentifiers.photoCellIdentifier, for: indexPath) as? PhotoCell else {
return UICollectionViewCell()
}
photoCell.padding = minimumCellPadding
photoCell.item = photoItems[indexPath.item]
return photoCell
}
}
//
// PinterestLayout.swift
// TestApp
//
// Created by Coder ACJHP on 20.10.2023.
//
import UIKit
protocol PinterestLayoutDelegate: AnyObject {
func collectionView(collectionView: UICollectionView, heightForItemAtIndexPath indexPath: IndexPath) -> CGFloat
}
class PinterestLayout: UICollectionViewLayout {
var delegate: PinterestLayoutDelegate!
var numberOfColumns = 1
private var cache = [UICollectionViewLayoutAttributes]()
private var contentHeight: CGFloat = .zero
private var width: CGFloat {
get {
return collectionView!.bounds.width
}
}
override var collectionViewContentSize: CGSize {
CGSize(width: width, height: contentHeight)
}
override func prepare() {
if cache.isEmpty {
let columnWidth = width / CGFloat(numberOfColumns)
var xOffsets = Array<CGFloat>()
for column in 0 ..< numberOfColumns {
xOffsets.append(CGFloat(column) * columnWidth)
}
var yOffsets = Array<CGFloat>(repeating: 0, count: numberOfColumns)
var column = 0
for item in 0 ..< collectionView!.numberOfItems(inSection: .zero) {
let indexPath = IndexPath(row: item, section: .zero)
let height = delegate.collectionView(collectionView: collectionView!, heightForItemAtIndexPath: indexPath)
let frame = CGRect(x: xOffsets[column], y: yOffsets[column], width: columnWidth, height: height)
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.frame = frame
cache.append(attributes)
contentHeight = max(contentHeight, frame.maxY)
yOffsets[column] = yOffsets[column] + height
let nextColumnCount = column + 1
column = (column >= (numberOfColumns - 1)) ? .zero : nextColumnCount
}
}
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var layoutAttributes = [UICollectionViewLayoutAttributes]()
for attributes in cache {
if rect.intersects(attributes.frame) {
layoutAttributes.append(attributes)
}
}
return layoutAttributes
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment