Left Align UICollectionViewCells only works when cells are not correct height, and stops working when scrolled - swift

I am trying to left align the cells in a UICollectionView, and I have used a GitHub repo to try and obtain this, link here:
https://github.com/mischa-hildebrand/AlignedCollectionViewFlowLayout
I set up the CollectionView and its cells like so:
lazy var filterCollectionView: UICollectionView = {
let layout = AlignedCollectionViewFlowLayout(horizontalAlignment: .left, verticalAlignment: .center)
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.register(filterCell.self, forCellWithReuseIdentifier: "filterCellId")
cv.backgroundColor = .white
cv.isScrollEnabled = true
cv.dataSource = self
cv.delegate = self
cv.showsHorizontalScrollIndicator = false
return cv
}()
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return genrArray.count
}
let genArray = ["Test123", "LongTextTest", "Short", "S", "#LongLongLongLong"]
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "filterCellId", for: indexPath) as! filterCell
cell.genText.text = genArray[indexPath.row]
cell.genText.sizeToFit()
cell.frame = CGRect(x: cell.frame.origin.x, y: cell.frame.origin.y, width: cell.genText.frame.width + 25, height: 24)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let label = UILabel(frame: .zero)
label.text = genArray[indexPath.row]
label.frame = CGRect(x: 0, y: 0, width: label.intrinsicContentSize.width, height: 0)
return CGSize(width: label.frame.width + 25, height: 18)
}
This did nothing to the cells, so I added these lines of code into ViewDidLoad():
let layout = AlignedCollectionViewFlowLayout(horizontalAlignment: .left, verticalAlignment: .center)
layout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
self.filterCollectionView.collectionViewLayout = layout
The line that actually made a change in the CollectionView was layout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize. But this still did not achieve the desired effect. This squished my cells and made them have a height of much smaller than 18 (the size declared in sizeForItem. When loaded up, the collectionView looks like this:
But when I scroll through the collectionView, it reverts back to normal as if the left alignment layout is not even there. It then looks like this:
The cells are the correct shape in that image, but the alignment/layout/spacing is off.
What am I doing wrong?

Related

UICollectionViewCell zero spacing not working

I did create a custom calendar based on UICollectionView.
One UICollectionViewCell - one date in the calendar. I want to delete spacing between cells.
CollectionView layout settings -
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (collectionView.frame.width / 7), height: collectionView.frame.width / 7)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0.0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0.0
}
In different screen sizes, unless iPhone X, we will see the spacing between cells.
How to fix it?
You probably have a rounding error. You are saying
collectionView.frame.width / 7
An iPhone 11 is 414 points wide. 414 / 7 is 59.14285714. There is no way to portray a fractional point on the screen, so now what? We round down, and now there is an extra pixel space between cells.
This is occurring because of the rounding-off of decimal places when your providing collectionView.frame.width / 7 just as matt has said in his answer. Seems you are trying to avoid this because you don't want users to see that bit of gap between the cells. Mostly you would only see this on a simulator an not on a real device due to the pixel density. You can overcome this fault either by replacing the UICollectionViewFlowLayout with a UICollectionViewCompositionalLayout which is available only from iOS 12. A simple fix would be to create a custom background view for the cell that extends the UICollectionViewCell by just 1 pixel. Here is an example of how I tried to achieve this:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.backgroundColor = .purple
let view = UIView()
view.backgroundColor = .brown
cell.addSubview(view) // Comment this line and you can see the gap
view.frame = cell.bounds
view.frame.size = CGSize(width: view.frame.size.width + 1, height: view.frame.size.height + 1)
return cell
}
This was a nice little hack I found a while back. Hope this helps you too.
I found a solution -
1. Delete from storyboard leading and trailing constraints from calendar view.
2. Set center horizontally constraint
3. Set width constraint to calendar view.
4. Create #IBOutlet calendarWidthConstraint
5. In viewDidLoad -
let layout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
let width = self.view.frame.width
let cellSize = CGFloat(width / 7).rounded()
let newWidth = cellSize * 7
layout.itemSize = CGSize(width: cellSize, height: cellSize)
calendarWidth.constant = newWidth
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
collectionView.collectionViewLayout = layout

Swift: Set cell left alignement for UICollectionView horizontally

I have a collectionView where the cells have a dynamic width. The problem is that the cells are aligned in the center, and I want it to be aligned to the left, look at the picture, I drew a red line where I want the alignment:
Here my collection view configuration:
var collectionView: UICollectionView = {
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: 100, height: 40)
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
let collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
collectionView.setCollectionViewLayout(layout, animated: true)
collectionView.register(SportsCell.self, forCellWithReuseIdentifier: "MyCell")
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.backgroundColor = .white
collectionView.showsHorizontalScrollIndicator = false
return collectionView
}()
Then the method to make the size dynamic:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let text = items[indexPath.row].message
let padding: CGFloat = 5
return CGSize(width: textWidth(text: text, font: .systemFont(ofSize: 22)) + padding, height: 40)
}
func textWidth(text: String, font: UIFont?) -> CGFloat {
let attributes = font != nil ? [NSAttributedString.Key.font: font!] : [:]
return text.size(withAttributes: attributes).width
}

NSCollectionView viewForSupplementaryElementOfKind not being called

In my NSCollectionViewItem I set up the code below.
func collectionView(_ collectionView: NSCollectionView, viewForSupplementaryElementOfKind kind: NSCollectionView.SupplementaryElementKind, at indexPath: IndexPath) -> NSView {
let view = collectionView.makeSupplementaryView(ofKind: .sectionHeader, withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "sectionHeader"), for: indexPath)
view.wantsLayer = true
view.layer?.backgroundColor = NSColor.red.cgColor
return view
}
func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> NSSize {
return NSSize(width: self.frame.size.width, height: 60)
}
However, when I place a print() in the viewForSupplementaryElementOfKind function it never is executed. Also, no red header appears.
This is how I set up the NSCollectionView
public let collectionView: NSCollectionView = {
let v = NSCollectionView(frame: NSRect.zero)
v.wantsLayer = true
v.layer!.backgroundColor = NSColor.clear.cgColor
v.isSelectable = true
v.backgroundColors = [NSColor.clear]
v.allowsEmptySelection = false
v.collectionViewLayout = {
let l = NSCollectionViewFlowLayout()
l.minimumInteritemSpacing = 1
l.minimumLineSpacing = 1
l.scrollDirection = NSCollectionView.ScrollDirection.vertical
l.sectionInset = NSEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
return l
}()
return v
}()
What am I doing wrong?
The supplemental views (header and footer) are not collection view items. You need to implement those two methods on the NSCollectionViewDelgateFlowLayout object, not on your NSCollectionViewItem class.
See https://developer.apple.com/documentation/appkit/nscollectionviewdelegateflowlayout
You can also set a default for the entire collection view:
l.headerReferenceSize = NSSize(width: 0, height: 50)
l.footerReferenceSize = NSSize(width: 0, height: 50)
When using NSCollectionViewFlowLayout, the default size of the header and footer is NSSize.zero, so the method to make the supplemental view is not called unless one of these techniques is used.

UICollectionView equal cell spacing

I am displaying several images in a collection view. They are all the same orientation (landscape) except for one, which is portrait (see image below):
I am trying to make the portrait image more centered, so that everything is evenly spaced. Any ideas on how to do this?
PhotoGalleryViewController.swift:
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return images.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! PhotoGalleryCell
let image = UIImage(named: images[indexPath.row])
cell.imageView.image = image
cell.captionLabel.text = captions[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
var size = CGSize()
if (indexPath.row == 3)
{
size = CGSize(width: 450, height: 600)
}
else {
size = CGSize(width: 940, height: 600)
}
return size
}
My suggestion would be not altering the cell size based on the fact that a portrait image will be in item 3.
Instead update the layout of your cell so that whatever image it gets assigned to it's image view, it will centre the image in itself and centralise the label underneath the image.
In ViewDidLoad method put this code :
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
collectionview.collectionViewLayout = layout
Also add this into cellForRow Methods:
cell.imageview.contentmode = .aspectfit
Hope it helps to resolve your issue.

CollectionViewCell out of screen on iphone 5

I have this custom UICollectionViewCell and it works nice on iphone 7, but when I run it on Iphone 5 simulator, things are acting weird.
My collection view has many sections and one item per section. It looks like a table view, meaning each cell is full width and they are all above each other like in a table view.
For some reason in Iphone 5 the frame is: (-27.5, 50.0, 320.0, 45.7143)
meaning it is in the correct width and height, but it starts 27.5 points to the left of the screen. I update the cell like this:
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> ProfileCollectionCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! ProfileCollectionCell
let width = CGFloat((self.collectionView?.frame.width)!)
let height = width/7
cell.frame.size = CGSize(width: width, height: height)
return cell
}
how can i have the frame positioned correctly?
You shouldn't set the cell's frame in cellForItemAtIndexPath
Instead, implement the UICollectionViewDelegateFlowLayout method…
func collectionView(UICollectionView, layout: UICollectionViewLayout, sizeForItemAt: IndexPath) {
let width = collectionView?.frame.width ?? 0
let height = width / 7
return CGSize(width: width, height: height)
}
first add protocol into your class UICollectionViewDelegateFlowLayout and add below code:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
{
let width = CGFloat((self.collectionView?.frame.width)!)
let height = width/7
return CGSize(width: width, height: height)
}