I’m trying to create a custom calendar in my app and I added the following code.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout:
UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.size.width/7, height: 36)
}
But in different iPad devices, the weekdays show more than 7 & and some iPad show 5 days of the week. It's changing the cell size depending on the screen size. But I want to add & days for all iPad screen widths. Then I tried the following
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if UIDevice().userInterfaceIdiom == .pad
{
if (UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.pad &&
(UIScreen.main.bounds.size.height == 1366 || UIScreen.main.bounds.size.width == 1366))
{
print("iPad Pro : 12.9 inch")
return CGSize(width: collectionView.frame.width/7 20, height: 36)
}
else if (UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.pad &&
(UIScreen.main.bounds.size.height == 1024 || UIScreen.main.bounds.size.width == 1024))
{
print("iPad 2 || iPad Pro : 9.7 inch || iPad Air/iPad Air 2 || iPad Retina ")
return CGSize(width: collectionView.frame.width/7 - 5, height: 36)
}
else if (UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.pad &&
(UIScreen.main.bounds.size.height == 1180 || UIScreen.main.bounds.size.width == 1180))
{
print("iPad Air 4th gen")
return CGSize(width: collectionView.frame.width/7 5, height: 45)
}
else if (UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.pad &&
(UIScreen.main.bounds.size.height == 1194 || UIScreen.main.bounds.size.width == 1194))
{
print("iPad Pro 11")
return CGSize(width: collectionView.frame.width/7 5, height: 45)
}
else if (UIDevice.current.userInterfaceIdiom == UIUserInterfaceIdiom.pad &&
(UIScreen.main.bounds.size.height == 1112 || UIScreen.main.bounds.size.width == 1112))
{
print("iPad Air 3")
return CGSize(width: collectionView.frame.width/7, height: 45)
}
else
{
print("iPad 3")
return CGSize(width: collectionView.frame.width/7, height: 45)
}
}
return CGSize(width: collectionView.frame.width/7, height: 45)
}
For using this most of the iPad devices are getting exactly 7 days cell. But some devices (iPad 5th, 6th, 7th generation) with 1024 screen width still showing 6 days a week. I tried with the simulator.
CodePudding user response:
Your answer might work for the time being, however I would recommend against using hard-coded values like 173, 20, 7 etc
which might not work if screen dimensions change or even the orientation.
The main thing to figure out is how much available width
you actually have to work with before getting the cell dimensions.
Here is what I would do:
extension CalendarVC: UICollectionViewDelegateFlowLayout
{
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize
{
var availableWidth = collectionView.bounds.size.width
// Remove horizontal insets if any
if let collectionViewLayout
= collectionViewLayout as? UICollectionViewFlowLayout
{
let totalSectionInsets
= collectionViewLayout.sectionInset.left collectionViewLayout.sectionInset.right
availableWidth -= totalSectionInsets
// Remove the horizontal spacing between cells, which is a bit tricky
// The horizontal spacing between cells is the interItemSpacing
// There will always be numberOfColumns - 1 in spaces between cells
// For 7 cells, there will be 6 spaces. For 5, there will be 4 etc
let spaces = numberOfColumns - 1
let totalSpacing = CGFloat(spaces) * collectionViewLayout.minimumInteritemSpacing
availableWidth -= totalSpacing
}
// Remove the vertical content insets if any
let horizontalContentInsets
= collectionView.contentInset.left collectionView.contentInset.right
availableWidth -= horizontalContentInsets
// Now we have the actual available width after all the insets and spacing
// So calculate the cell width
let cellDimension = availableWidth / CGFloat(numberOfColumns)
// Keep the height same as width to get a square or set it as you wish
return CGSize(width: cellDimension, height: cellDimension)
}
}
This will give you the following in phones portrait:
Landscape phones
iPad
In all cases you get 7 columns without using random numbers
Here is the full solution:
// Cell class, not so important for you
fileprivate class DateCell: UICollectionViewCell
{
static let reuseIdentifier = "DateCell"
let date = UILabel()
override init(frame: CGRect)
{
super.init(frame: frame)
contentView.backgroundColor = .yellow
configureLabel()
layoutIfNeeded()
}
required init?(coder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
private func configureLabel()
{
contentView.addSubview(date)
date.backgroundColor = .lightGray
date.textColor = .black
date.textAlignment = .center
date.translatesAutoresizingMaskIntoConstraints = false
addConstraints([
date.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
date.topAnchor.constraint(equalTo: contentView.topAnchor),
date.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
date.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
])
}
}
// View Controller
class CalendarVC: UIViewController
{
var calendarCollectionView: UICollectionView!
// Number of columns per row
let numberOfColumns = 7
// Padding between the cells horizontally
// and vertically
let padding: CGFloat = 10
let sectionInset: CGFloat = 10
var daysInMonth = 0
override func viewDidLoad()
{
super.viewDidLoad()
title = "CV Calendar"
view.backgroundColor = .white
configureDaysInMonth()
configureCollectionView()
}
// I am just getting days of the month, not so important for you
private func configureDaysInMonth()
{
let cal = Calendar(identifier: .gregorian)
// Calculate start and end of the current year (or month with `.month`):
if let range = cal.range(of: .day, in: .month, for: Date())
{
daysInMonth = range.count
}
}
private func configureCollectionView()
{
calendarCollectionView = UICollectionView(frame: CGRect.zero,
collectionViewLayout: createLayout())
calendarCollectionView.register(DateCell.self,
forCellWithReuseIdentifier: DateCell.reuseIdentifier)
calendarCollectionView.dataSource = self
calendarCollectionView.delegate = self
calendarCollectionView.backgroundColor = .white
calendarCollectionView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(calendarCollectionView)
// Collection view auto layout
view.addConstraints([
calendarCollectionView.leadingAnchor
.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor,
constant: 0),
calendarCollectionView.topAnchor
.constraint(equalTo: view.topAnchor,
constant: 0),
calendarCollectionView.trailingAnchor
.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor,
constant: 0),
calendarCollectionView.bottomAnchor
.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor,
constant: 0)
])
}
private func createLayout() -> UICollectionViewFlowLayout
{
let flowLayout = UICollectionViewFlowLayout()
flowLayout.minimumLineSpacing = padding
flowLayout.minimumInteritemSpacing = padding
flowLayout.scrollDirection = .vertical
flowLayout.sectionInset = UIEdgeInsets(top: sectionInset,
left: 0,
bottom: 0,
right: 0)
return flowLayout
}
}
// UICollectionView DataSource, not too important for you
extension CalendarVC: UICollectionViewDataSource
{
func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int
{
daysInMonth
}
func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell
= collectionView.dequeueReusableCell(withReuseIdentifier: DateCell.reuseIdentifier,
for: indexPath) as! DateCell
cell.date.text = "\(indexPath.row 1)"
return cell
}
}
// UICollectionViewDelegateFlowLayout, same as above
extension CalendarVC: UICollectionViewDelegateFlowLayout
{
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize
{
var availableWidth = collectionView.bounds.size.width
// Remove horizontal insets if any
if let collectionViewLayout
= collectionViewLayout as? UICollectionViewFlowLayout
{
let totalSectionInsets
= collectionViewLayout.sectionInset.left collectionViewLayout.sectionInset.right
availableWidth -= totalSectionInsets
// Remove the horizontal spacing between cells, which is a bit tricky
// The horizontal spacing between cells is the interItemSpacing
// There will always be numberOfColumns - 1 in spaces between cells
// For 7 cells, there will be 6 spaces. For 5, there will be 4 etc
let spaces = numberOfColumns - 1
let totalSpacing = CGFloat(spaces) * collectionViewLayout.minimumInteritemSpacing
availableWidth -= totalSpacing
}
// Remove the vertical content insets if any
let horizontalContentInsets
= collectionView.contentInset.left collectionView.contentInset.right
availableWidth -= horizontalContentInsets
// Now we have the actual available width after all the insets and spacing
// So calculate the cell width
let cellDimension = availableWidth / CGFloat(numberOfColumns)
// Keep the height same as width to get a square or set it as you wish
return CGSize(width: cellDimension, height: cellDimension)
}
}
CodePudding user response:
I got the perfect solution with the following code.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return CGSize(width: (self.contentView.frame.width - 173)/7, height: 45) }