UICollectionView equal cell spacing - swift

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.

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

UICollectionView sizeForItemAt Called But Not Sized Correctly

I would like to have the first section's cells be the full width of the collection view and the second section be half of the width. On startup, the cells don't respect the given width although the height seems to be respected. After a couple of seconds, the collection view is reloaded and most of the time it sets the cells to the correct sizes. When the phone's orientation changes to landscape, the cells are drawn in the incorrect sizes again.
I've tried using UICollectionViewDelegateFlowLayout and the sizeForItemAt delegate function to set the cell sizes but they don't follow the given sizes even if the function is being called.
Currently, I am setting section insets and then calculating the remaining available width for the cells in the collection view's bounds, afterward I divide the width by the number of cells per row.
class HomeCollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
let sectionInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView?.contentInsetAdjustmentBehavior = .always
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: collectionView.frame.width, height: 44)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return self.sectionInsets
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return self.sectionInsets.left
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
var numberOfItemsPerRow: CGFloat = 1
var height: CGFloat = 0
switch indexPath.section {
case 0:
numberOfItemsPerRow = 1
height = 97
case 1:
numberOfItemsPerRow = 2
height = 126
default:
break
}
let padding = self.sectionInsets.left * (numberOfItemsPerRow + 1)
let availableWidth = collectionView.bounds.size.width - padding
let width = availableWidth / numberOfItemsPerRow
return CGSize(width: width, height: height)
}
}
This results in the cells to be sized incorrectly as in the following image. Incorrect Example Image
The only time they are sized correctly is after the collection view is reloaded. Correct Example Image
Also, when the phone turns to landscape it displays it incorrectly. Incorrect Landscape Example Image
To conclude my issue, the first section's cells should always take 100% of the available width. If possible it would be very nice if they would have an automatic height. I've tried using UICollectionViewFlowLayout.automaticSize.height but I don't think that would work. For the second section, it needs to always take 50% of the available width.
If you're using Storyboard to create the CollectionView.
I thought I had to invalidateLayout(). It turns out there's a weird bug with iOS 13.
you have to create your own UICollectionViewFlowLayout. Call something like this in your viewDidLoad()
collectionView.collectionViewLayout = {
let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .horizontal
return flowLayout
}()
You must invalidate layout on viewDidLayoutSubviews to re-calculate sizes for cells.
I have attached code snippet. Try this one
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView.collectionViewLayout.invalidateLayout()
}

Set The CollectionView As Banner

I have to Collection View in my view Controller. One is Showing the image and the other is showing the ticket details. Here is the sample image.
But I can't set the proper constraint. This is what I Do with code.
let numberOfCells = 9
let kCellHeight : CGFloat = 104
let kLineSpacing : CGFloat = 2
let kInset : CGFloat = 10
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if collectionView == clnView {
return CGSize.init(width: width, height: height)
}else {
return CGSize(width: (UIScreen.main.bounds.width - 2*kInset - kLineSpacing), height: kCellHeight)
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
var size = CGFloat()
if collectionView == clnView {
size = kLineSpacing
}
return size
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
var Insect = UIEdgeInsets()
if collectionView == clnView {
Insect = UIEdgeInsets(top: kInset, left: kInset, bottom: kInset, right: kInset)
}
return Insect
}
But It's not showing properly. Look
iphone 5s
iPhone XR
Please Explain the Correct way. Thanks
Your code simply defines how items are laid out inside the collection view.
Constraints should tell you where in your parent view collection view is placed, and how wide + high.
You must learn autolayout and do it step by step in your storyboard or xib for the view. With proper Y constraint, it should work as expected. If you are putting collectionview + other things inside scrollview, follow this answer.

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

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?

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)
}