UICollectionView cell not resizing based on cell content with autoresize - ios

I am trying to create UICollectionView with cells that adjust the width based on the content of the cell using layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize but it doesn't update. Bellow is two images of what am trying to achieve and the next is what I have achieved.
Here is what am trying to achieve.
Correct display
Here is what I have been able to achieve
What I have now
And here is my code
import UIKit
struct Model {
let name: String
let color: String
}
class DisplayTagsView: UIView {
// Collection View
var projectTagsCollectionView: UICollectionView!
var projectTagsCollectionViewHeightConstraint: NSLayoutConstraint!
private var tagsObjects = [Any]() // Object to allow add button
private var tags: [Model] = [] // Tags
// MARK: - Init
init() {
super.init(frame: .zero)
initViews()
initTags()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
func initTags() {
self.tags.append(Model(name: "Some tag", color: "color"))
self.tags.append(Model(name: "Twotwo", color: "color"))
self.tags.append(Model(name: "Smartparking", color: "color"))
self.tags.append(Model(name: "Development", color: "color"))
self.tagsObjects = self.tags
self.tagsObjects.append(AddButtonCollectionViewCell.self)
}
// MARK: - Private
private func initViews() {
initTagCollectionView()
}
private func initTagCollectionView() {
let layout = UICollectionViewFlowLayout()
layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
layout.minimumLineSpacing = 5
layout.minimumInteritemSpacing = 5
layout.scrollDirection = .vertical
projectTagsCollectionView = UICollectionView(frame: self.frame, collectionViewLayout: layout)
projectTagsCollectionView.delegate = self
projectTagsCollectionView.dataSource = self
projectTagsCollectionView.register(cell: ProjectCollectionViewCell.self)
projectTagsCollectionView.register(cell: AddButtonCollectionViewCell.self)
projectTagsCollectionView.isScrollEnabled = false
projectTagsCollectionView.backgroundColor = .clear
}
// set up views
func setupView() {
addSubview(projectTagsCollectionView)
setCollectionHeight()
}
// update height
func setCollectionHeight() {
projectTagsCollectionViewHeightConstraint = projectTagsCollectionView.heightAnchor.constraint(equalToConstant: 200)
projectTagsCollectionViewHeightConstraint.isActive = true
}
// Button button
func addButton() -> UIButton {
let addButton = UIButton()
addButton.setImage(#imageLiteral(resourceName: "settings"), for: .normal)
return addButton
}
}
// MARK: - UICollectionViewDelegateFlowLayout
extension DisplayTagsView : UICollectionViewDelegateFlowLayout {
// Auto resizing of the cell tags
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let flowLayout = collectionViewLayout as? UICollectionViewFlowLayout
var newEdgeInsets = UIEdgeInsets(top: flowLayout!.sectionInset.top, left: flowLayout!.sectionInset.left, bottom: flowLayout!.sectionInset.bottom, right: flowLayout!.sectionInset.right)
if collectionView.numberOfItems(inSection: section) == 1 {
let edgeInset = newEdgeInsets.right + (collectionView.frame.width - flowLayout!.itemSize.width)
newEdgeInsets.right = edgeInset
}
return newEdgeInsets
}
}
// MARK: - UICollectionViewDelegate
extension DisplayTagsView: UICollectionViewDelegate {
}
// MARK: - UICollectionViewDataSource
extension DisplayTagsView: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return tagsObjects.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let tagObject = self.tagsObjects[indexPath.item]
if let tag = tagObject as? Model {
let cell: ProjectCollectionViewCell = projectTagsCollectionView.dequeueTypedCell(forIndexPath: indexPath)
cell.configureCell(tagName: tag.name, tagBackground: tag.color)
return cell
} else {
// Adding button to the UICollection
let cell: AddButtonCollectionViewCell = membersCollectionView.dequeueTypedCell(forIndexPath: indexPath)
let addButto = addButton()
cell.contentView.subviews.forEach({$0.removeFromSuperview()})
cell.contentView.addSubview(addButton)
settingButton.snp.makeConstraints { make in
make.size.equalTo(cell.contentView)
}
return cell
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(indexPath)
}
}

Try Following library. TagList view.
https://github.com/ElaWorkshop/TagListView. It surely working ur conditions

Related

How to make horizontal CollectionView size fit its content programatically

I have a collection view which takes up the whole screen and should scroll horizontally.
Problem is I can't adjust the size for item while retaining the carousel effect. If I lower the size for item, the cell stacks vertically just like here:
The pink background is the collectionView. The collectionView fills the viewController's view that is why the cells doesn't align horizontally.
The alignment should be like this which scrolls horizontally much like a carousel but the problem is it's too big and it took up the whole view.
What's expected is for the cell and collection view to be around 100 height or 200 height only and can still scroll horizontally.
This is my existing code:
I am using SnapKit for constraints.
ViewController
import UIKit
import SnapKit
class ViewController: UIViewController {
let viewModel: [SeatViewModel] = [.init(text: "single text"), .init(text: "slightly longer text."), .init(text: "very very very very long long text")]
private lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.delegate = self
cv.backgroundColor = .white
cv.register(SeatCardCell.self, forCellWithReuseIdentifier: "SeatCardCell")
return cv
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
collectionView.snp.makeConstraints { make in
make.center.equalToSuperview()
make.width.equalTo(200)
make.height.equalTo(100)
}
collectionView.dataSource = self
collectionView.backgroundColor = .systemPink
}
func lines(label: UILabel) -> Int {
self.collectionView.layoutIfNeeded()
let textSize = CGSize(width: label.frame.size.width, height: CGFloat(Float.infinity))
let rHeight = lroundf(Float(label.sizeThatFits(textSize).height))
let charSize = lroundf(Float(label.font.lineHeight))
let lineCount = rHeight/charSize
return lineCount
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView.frame = view.bounds
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let collectioCell = collectionView.dequeueReusableCell(withReuseIdentifier: "SeatCardCell", for: indexPath) as? SeatCardCell else { fatalError() }
collectioCell.backgroundColor = [UIColor.green, .red, .black, .brown, .yellow].randomElement()
collectioCell.viewModel = viewModel[indexPath.row]
return collectioCell
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 200, height: self.collectionView.frame.height)
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 1.0
}
func collectionView(_ collectionView: UICollectionView, layout
collectionViewLayout: UICollectionViewLayout,
minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 1.0
}
}
View Model and Cell
import UIKit
import SnapKit
struct SeatViewModel {
let text: String
}
class SeatCardCell: UICollectionViewCell {
var viewModel: SeatViewModel? {
didSet {
configure()
}
}
// MARK: - Properties
let seatImageView = UIImageView(image: nil)
let seatLabel: CustomLabel = {
let label = CustomLabel()
return label
}()
// MARK: - Lifecycle
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func configure() {
guard let viewModel = viewModel else { return }
seatLabel.text = viewModel.text
seatLabel.backgroundColor = .cyan
seatLabel.sizeToFit()
seatImageView.image = .strokedCheckmark
let stackView = UIStackView(arrangedSubviews: [seatImageView, seatLabel])
stackView.spacing = 0
stackView.alignment = .center
addSubview(stackView)
seatImageView.snp.makeConstraints { make in
make.size.equalTo(40)
}
stackView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
}
// MARK: - CUSTOM TITLE LABEL
class CustomLabel: UILabel {
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
self.textColor = .darkGray
self.textAlignment = .left
self.numberOfLines = 4
}
}
The problem of your code is you set again the collection view frame equal to view frame in viewDidLayoutSubviews.
You just need to remove it and your code will work perfectly fine.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// remove it
//collectionView.frame = view.bounds
}

Can't pass data from cell to UIView

I want to change value of temperature in UIView (blue square), when I tapping on cell in collection view (date). I am trying to use delegate for it, but i can't do it. I want to change value of selectedIndex and pass this value to UIView in viewModel.weather[selectedIndex]
ViewController with collectionView
protocol WeekCityWeatherViewControllerDelegate {
func reloadWeatherData()
}
class WeekCityWeatherViewController: UIViewController {
var delegate: WeekCityWeatherViewControllerDelegate?
...
extension WeekCityWeatherViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 7
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "dateCell", for: indexPath) as! DateWeekWeatherScreenCell
cell.layer.cornerRadius = 5
if selectedIndex == indexPath.row {
cell.backgroundColor = UIColor.blue
cell.dateLabel.textColor = UIColor.white
} else {
cell.backgroundColor = UIColor.white
cell.dateLabel.textColor = UIColor.black
}
if let dateInt = viewModel.weather.first?.week.daily[indexPath.item].dt {
let timeInterval = TimeInterval(dateInt)
let myNSDate = Date(timeIntervalSince1970: timeInterval)
let dateFormatter2 = DateFormatter()
dateFormatter2.dateFormat = "dd/MM E"
dateFormatter2.locale = Locale(identifier: "ru_RU")
let dateString = dateFormatter2.string(from: myNSDate)
cell.dateLabel.text = dateString
}
return cell
}
}
extension WeekCityWeatherViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width / 5, height: collectionView.frame.height)
}
}
extension WeekCityWeatherViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedIndex = indexPath.row
self.dateCollectionView.reloadData()
delegate?.reloadWeatherData()
}
}
UIView
class DayView: UIView, WeekCityWeatherViewControllerDelegate {
var viewModel: GeneralViewModel
var selectedIndex: Int
var currentIndex: Int
init(frame: CGRect, viewModel: GeneralViewModel, selectedIndex: Int, currentIndex: Int) {
self.viewModel = viewModel
self.selectedIndex = selectedIndex
self.currentIndex = currentIndex
super.init(frame: frame)
createSubviews()
reloadWeatherData()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func reloadWeatherData() {
self.reloadInputViews()
}
private func createSubviews() {
let weekCityWeatherViewController = WeekCityWeatherViewController(viewModel: viewModel, currentIndex: currentIndex)
weekCityWeatherViewController.delegate = self
let tempLabel = UILabel()
tempLabel.textColor = .black
tempLabel.translatesAutoresizingMaskIntoConstraints = false
tempLabel.font = UIFont(name: "Rubik-Regular", size: 30)
if let maxTemp = viewModel.weather[selectedIndex].week.daily[0].temp.max {
tempLabel.text = String(format: "%.0f", maxTemp) + " " + "°"
}
addSubview(tempLabel)
NSLayoutConstraint.activate([
tempLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
tempLabel.topAnchor.constraint(equalTo: topAnchor, constant: 10),
tempLabel.heightAnchor.constraint(equalToConstant: 50),
tempLabel.widthAnchor.constraint(equalToConstant: 50),
])
}
}
create a protocol and send the target cell as a parameter in the trigger method.
Here is the steps;
// 1. protocol and trigger method
protocol ItemTableViewCellDelegate: AnyObject {
func doneButtonTapped(_ cell: ItemTableViewCell)
}
// 2. implement delegate and send the trigger method in your case
class ItemTableViewCell: UITableViewCell {
weak var delegate: ItemTableViewCellDelegate?
//...
}
// MARK: - Implementation
extension ViewController: ItemTableViewCellDelegate {
func doneButtonTapped(_ cell: ItemTableViewCell) {
// 3. find the index of item you tapped the button, via cell as a parameter
guard let indexPath = tableView.indexPath(for: cell) else { return }
var item = MainScreenModel.dummyModel[indexPath.row]
// 4. reload the cell by indexpath
tableView.reloadRows(at: [indexPath], with: .automatic)
}
}

How to get all UICollectionViewCells to share the same height?

I have a UICollectionView where the Cell's do not fill their vertical space. What adjustment should I make to ensure each cell fills up the entire cell area? Or at least all share the same height for the row they are on?
Here is the Storyboard
UICollectionViewFlowLayout
class AddServiceFlowLayout: UICollectionViewFlowLayout {
let cellsPerRow: Int
init(cellsPerRow: Int, minimumInteritemSpacing: CGFloat = 0, minimumLineSpacing: CGFloat = 0, sectionInset: UIEdgeInsets = .zero) {
self.cellsPerRow = cellsPerRow
super.init()
self.minimumInteritemSpacing = minimumInteritemSpacing
self.minimumLineSpacing = minimumLineSpacing
self.sectionInset = sectionInset
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepare() {
super.prepare()
guard let collectionView = collectionView else { return }
let marginsAndInsets = sectionInset.left + sectionInset.right + collectionView.safeAreaInsets.left + collectionView.safeAreaInsets.right + minimumInteritemSpacing * CGFloat(cellsPerRow - 1)
let itemWidth = ((collectionView.bounds.size.width - marginsAndInsets) / CGFloat(cellsPerRow)).rounded(.down)
itemSize = CGSize(width: itemWidth, height: itemWidth)
}
override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext {
let context = super.invalidationContext(forBoundsChange: newBounds) as! UICollectionViewFlowLayoutInvalidationContext
context.invalidateFlowLayoutDelegateMetrics = newBounds.size != collectionView?.bounds.size
return context
}
}
UICollectionViewCell
class AddServiceViewCell: UICollectionViewCell {
#IBOutlet weak var labelStackView: UIStackView!
#IBOutlet weak var tvServiceName: UILabel!
#IBOutlet weak var tvQuantityNeeded: UILabel!
public var onCellTapped: (() -> ())?
override func awakeFromNib() {
super.awakeFromNib()
self.layer.cornerRadius = 10.0
}
override func prepareForReuse() {
super.prepareForReuse()
self.tvServiceName.text = nil
self.tvQuantityNeeded.show()
}
public static func nib() -> UINib {
return UINib.init(nibName: identifier, bundle: Bundle(for: ServiceLineItemCell.self))
}
public func configure(with service: TowService){
self.tvServiceName.text = service.description
if(service.calculated){
self.tvQuantityNeeded.hide()
}
}
public func eventTriggered()
{
onCellTapped?()
}
}
UIViewController
class AddServiceViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
private let columnLayout = AddServiceFlowLayout(
cellsPerRow: 3,
minimumInteritemSpacing: 10,
minimumLineSpacing: 10,
sectionInset: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
)
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Add Service"
// Register cell classes
self.collectionView!.delegate = self
self.collectionView!.dataSource = self
self.collectionView!.collectionViewLayout = columnLayout
self.collectionView!.contentInsetAdjustmentBehavior = .always
self.collectionView!.register(AddServiceViewCell.nib().self, forCellWithReuseIdentifier: AddServiceViewCell.identifier)
}
// removed for brevity ....
// MARK: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout
extension AddServiceViewController : UICollectionViewDelegateFlowLayout, UICollectionViewDelegate, UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return allServices.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: AddServiceViewCell.identifier, for: indexPath) as! AddServiceViewCell
cell.configure(with: allServices[indexPath.row])
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.onConfirmAdd(allServices[indexPath.row])
}
}
Here is a road map
1- You need to implement sizeForItemAt
2- Consider you have 3 columns per row , create a function that that accepts 3 Items ( introduced with the current indexPath.item ) from your model and manually calculate maximum height for each string in that model
3- Return the maximum height and by this you will set that height for all cells of same row

Cannot select CollectionView item if the item integrates another CollectionView

My NSCollectionView item integrates another NSCollectionView. Everything works great but not when it comes to items selection in the first view.
When I set collectionView1.isSelectable = true, the didSelectItemsAt delegate is called only if I click somewhere else than the second (integrated) collectionView. To be clearer, if I click on a label, an image or a custom view: it works. As soon as I click on the second collection view, the delegate is not called.
Here is what I have tried so far:
Setting collectionView2.isSelectable = true to the second view.
That did not work.
I followed this tip. Curiously that did not work either. I got the same behaviour.
The only thing that works is to add a gesture recogniser on each integrated collection view. But this is so ugly...
I haven't tried on iOS yet with UICollectionView and cell selection, but I tend to think that the problem is the same.
EDIT:
So, if I click on the green label or everywhere on the blue part (which is the first collection view item), the didSelectItemsAt delegate is called correctly.
If I click on the orange NSView (with the second CollectionView inside), it's not called..
Heres is the simplified code of the first CollectionView:
class DashboardVC: NSViewController {
#IBOutlet weak var collectionView1: NSCollectionView!
override func viewDidLoad() {
super.viewDidLoad()
self.initViews()
}
fileprivate func initViews() {
let flowLayout = NSCollectionViewFlowLayout()
flowLayout.itemSize = NSSize(width: 400.0, height: 380.0)
flowLayout.sectionInset = NSEdgeInsets(top: 20.0, left: 20.0, bottom: 20.0, right: 20.0)
flowLayout.minimumInteritemSpacing = 20.0
flowLayout.minimumLineSpacing = 20.0
self.collectionView1.collectionViewLayout = flowLayout
self.collectionView1.isSelectable = true
self.collectionView1.dataSource = self
self.collectionView1.delegate = self
}
}
extension DashboardVC: NSCollectionViewDataSource {
func numberOfSections(in collectionView: NSCollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ itemForRepresentedObjectAtcollectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
let item = self.collectionView1.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "CollectionViewItem"), for: indexPath)
guard let collectionViewItem = item as? CollectionViewItem else { return item }
return collectionViewItem
}
}
extension DashboardVC: NSCollectionViewDelegate {
func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set<IndexPath>) {
print("I can't make this work!")
}
}
And for the second collection view:
class TabularView: NSView {
lazy var collectionView2: NSCollectionView = {
let gridLayout = NSCollectionViewGridLayout()
gridLayout.maximumNumberOfColumns = self.numberOfColums
gridLayout.maximumNumberOfRows = self.numberOfRows
let collectionView = NSCollectionView(frame: .zero)
collectionView.collectionViewLayout = gridLayout
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.isSelectable = true //-> does not work :(
collectionView.dataSource = self
collectionView.delegate = self
return collectionView
}()
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
self.setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.setup()
}
private func setup() {
self.addSubview(self.collectionView)
NSLayoutConstraint.activate([
self.topAnchor.constraint(equalTo: self.collectionView.topAnchor),
self.bottomAnchor.constraint(equalTo: self.collectionView.bottomAnchor),
self.leadingAnchor.constraint(equalTo: self.collectionView.leadingAnchor),
self.trailingAnchor.constraint(equalTo: self.collectionView.trailingAnchor),
])
}
}
extension TabularView: NSCollectionViewDataSource {
func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
return self.numberOfRows * self.numberOfColums
}
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
let item = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "TabularCollectionViewItem"), for: indexPath)
guard let collectionViewItem = item as? TabularCollectionViewItem else { return item }
return collectionViewItem
}
}
If I'm understanding your problem correctly, you're trying to disable selection on a specific item in collectionView1 and instead have the user select the items of collectionView2 embedded in this cell.
Take a look at NSCollectionViewDelegate's collectionView(_:shouldSelectItemsAt:)
From the documentation:
Return Value
The set of NSIndexPath objects corresponding to the items that you want to be selected. If you do not want any items selected, return an empty set.

iOS: How to create a collectionView with different size & clickable cells

I would like to create a planning app, what I have already done, but my problem is that now, I would like to create different cells as following:
I don't know if it is possible...
Today, I have this code:
func numberOfSections(in collectionView: UICollectionView) -> Int { //colonnes: models
if _event != nil && _event!.models.count > 0
{
return _event!.models.count + 1
}
return 0
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int //lignes: slots
{
if _event != nil && _event!.models.count > 0 && _event!.models[0].slots.count > 0
{
if (section == 0) // A vérifier
{
return _event!.models[0].slots.count + 1
}
else
{
return _event!.models[section - 1].slots.count + 1
}
}
return 0
}
With this result:
UICollectionViewLayout is definitely the way to go.
I've made a start on an example of this for you, It gets the number of sections and items and generates the layout you asked for, evenly distributing the items across the height of the UICollectionView.
I've cropped the screenshot above so I don't take up to much space, heres the code
class OrganiserLayout:UICollectionViewLayout {
let cellWidth:CGFloat = 100
var attrDict = Dictionary<IndexPath,UICollectionViewLayoutAttributes>()
var contentSize = CGSize.zero
override var collectionViewContentSize : CGSize {
return self.contentSize
}
override func prepare() {
// Generate the attributes for each cell based on the size of the collection view and our chosen cell width
if let cv = collectionView {
let collectionViewHeight = cv.frame.height
let numberOfSections = cv.numberOfSections
self.contentSize = cv.frame.size
self.contentSize.width = cellWidth*CGFloat(numberOfSections)
for section in 0...numberOfSections-1 {
let numberOfItemsInSection = cv.numberOfItems(inSection: section)
let itemHeight = collectionViewHeight/CGFloat(numberOfItemsInSection)
let itemXPos = cellWidth*CGFloat(section)
for item in 0...numberOfItemsInSection-1 {
let indexPath = IndexPath(item: item, section: section)
let itemYPos = itemHeight*CGFloat(item)
let attr = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attr.frame = CGRect(x: itemXPos, y: itemYPos, width: cellWidth, height: itemHeight)
attrDict[indexPath] = attr
}
}
}
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
// Here we return the layout attributes for cells in the current rectangle
var attributesInRect = [UICollectionViewLayoutAttributes]()
for cellAttributes in attrDict.values {
if rect.intersects(cellAttributes.frame) {
attributesInRect.append(cellAttributes)
}
}
return attributesInRect
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
// Here we return one attribute object for the specified indexPath
return attrDict[indexPath]!
}
}
To test this I made a basic UIViewController and UICollectionViewCell, here is the view controller:
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Use our new OrganiserLayout subclass
let layout = OrganiserLayout()
// Init the collection view with the layout
let collection = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
collection.delegate = self
collection.dataSource = self
collection.backgroundColor = UIColor.white
collection.register(OrganiserCollectionViewCell.self, forCellWithReuseIdentifier: "cell")
self.view.addSubview(collection)
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
switch section {
case 0:
return 8
case 1:
return 6
case 2:
return 4
case 3:
return 2
case 4:
return 4
default:
return 0
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! OrganiserCollectionViewCell
cell.label.text = "\(indexPath.section)/\(indexPath.row)"
switch indexPath.section {
case 1:
cell.backgroundColor = UIColor.blue
case 2:
cell.backgroundColor = UIColor.red
case 3:
cell.backgroundColor = UIColor.green
default:
cell.backgroundColor = UIColor.cyan
}
return cell
}
}
And the UICollectionViewCell looks like this:
class OrganiserCollectionViewCell:UICollectionViewCell {
var label:UILabel!
var seperator:UIView!
override init(frame: CGRect) {
super.init(frame: frame)
label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(label)
seperator = UIView()
seperator.backgroundColor = UIColor.black
seperator.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(seperator)
let views:[String:UIView] = [
"label":label,
"sep":seperator
]
let cons = [
"V:|-20-[label]",
"V:[sep(1)]|",
"H:|[label]|",
"H:|[sep]|"
]
for con in cons {
self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: con, options: [], metrics: nil, views: views))
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Hope some of this helps, It's a very simplified example and you will probably need to modify it in order to achieve the calendar looking result that you require.
I believe you have to resolve to implementing your own custom layout for the collectionView - you will need to implement custom UICollectionViewLayout. This is not a trivial thing, but there are several good tutorials for it, for example this tutorial.

Resources