Created
October 20, 2023 14:17
-
-
Save Coder-ACJHP/cd5d38aee95ac24506b4bb3a6aeb5ea8 to your computer and use it in GitHub Desktop.
Custom PinterestLayout with UIKit iOS 11 (included example usage)
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
// | |
// 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 | |
} | |
} |
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
// | |
// 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 | |
} | |
} |
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
// | |
// 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