collectionview cell dynamic width breaks layout - ios

When i try to give a collection view's cell a dynamic with, based on the label it will contain, something strange is happening to the spacing between the cell:
If i space the items giving a static width, the cell are being layed out as expected:
The code which gives the item a width is:
func collectionView(collectionView: UICollectionView!, layout collectionViewLayout: UICollectionViewLayout!,
sizeForItemAtIndexPath indexPath: NSIndexPath!) -> CGSize
{
let element = self.default_categories[indexPath.row] as NSString
let stringSize = element.sizeWithAttributes([NSFontAttributeName: UIFont.systemFontOfSize(18.0)])
// return CGSize(width: stringSize.width, height: self.categoriesView.frame.height)
return CGSize(width: 100, height: self.categoriesView.frame.height)
}
So, basically, i don't really know where that space is coming from, when i
try to give the cells, a dynamic width.
The sectionInset is (0, 0, 0, 0) and the minimumLineSpacing is also 0.

The issues was with "minimumInteritemSpacing" property of the layout. Setting it to 0 does remove the margins between the cells, when adding dynamic size.

Related

How to make dynamic height for UICollectionViewCell with dynamic type and fixed width?

I am working on a project where we use a UICollectionView with a custom cell. The cell looks like this. All of my elements are contained in a UIView.
Of course, the text is much shorter.
Anyway, I decide the width of the CollectionView in the sizeForItemAt function:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width - 50, height: (collectionView.frame.width * 0.7))
}
I need to find a way to keep this fixed width but to make the height of the cell resizable when dynamic type is enabled. The labels at the bottom of the cell must be able to grow in size and therefore resize the cell at the bottom as much as they need without affecting the ImageView's size at the top. However, this seems harder than it looks since my ImageView has a size of 0.7 * the width of my CollectionView (so it has a proper height in any screen).
My labels are contained in a VerticalStackView and my VerticalStackView and the "1" label are contained in a HorizontalStackView.
I have tried to use dynamic type on this kind of view hierarchy in a sample project and the view grows in height without affecting the ImageView but I cannot seem to do the same in a CollectionViewCell.
I tried using auto-layout with Automatic Size enabled for my CollectionViewCell but that just ruins both my width and height and also clips the labels that I cannot see them anymore.
I hope you can help me!
Thank you so much.
You can find your height of the label using text as:
extension String {
func height(withWidth width: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(
with: constraintRect,
options: .usesLineFragmentOrigin,
attributes: [NSAttributedString.Key.font: font],
context: nil)
return ceil(boundingBox.height)
}
}
Modify your collection view's sizeForItemAt as:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.frame.width - 50.0
let labelHeight = "your text".height(withWidth: width, font: .systemFont(ofSize: 17.0))
let imageHeight = width * 0.7
return CGSize(width: width, height: labelHeight+imageHeight)
}

UICollectionViewCell sizeForItemAt values being reverted to fitItemSize somehow?

I have set up 2 collectionView in a viewcontroller, both get their data from an endpoint and then reloadData().
One collectionView act like an header tab and have its cell size depend on its intritic size and rely on insetForSection to position/align the cell in the center of the collectionView.
Another have "sort-of" fixed size for themselves where the first cell will be almost the entire width of the collectionView and then the cells after the first one will occupy semi-half the collectionView width.
I have setted-up delegate and extension methods, however for some reason the sizeforItem that focus on the second CollectionView doesn't "stick", they get reverted. When i do :
self.statusOptionCollectionView.reloadData()
self.statusOptionCollectionView.performBatchUpdates({
self.statusOptionCollectionView.layoutIfNeeded()
}) { (complete) in
debugPrint("Batch Update complete")
}
I saw a brief frame of my desired outcome but then the collectionView suddenly undo my sizeForItem code and change the cell to something akin to "size-to-fit". (Pics: Below).
Question is how do i fix this? What is causing this? Is it because i have 2 collectionView in one viewcontroller? I've tried to invalidatingLayout in viewdidlayoutsubviews but it doesn't work. "I did use storyboard but i already delete the collectionView and re-add it, didn't fix it)
I want Something Like This (Focus one the second viewcontroller layout) :
My CollectionView Layout Code is Like This (kindTabCollectionView is the "header", with center alignment) :
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
if collectionView.isEqual(self.kindTabCollectionView){
let layout = collectionViewLayout as! UICollectionViewFlowLayout
let totalCellWidth = layout.itemSize.width * CGFloat(self.kindArray.count)
let totalSpacingWidth = CGFloat(8 * (self.kindArray.count - 1))
let leftInset = (collectionView.bounds.size.width - CGFloat(totalCellWidth + totalSpacingWidth)) / 2
let rightInset = leftInset
return UIEdgeInsets(top: 0, left: leftInset, bottom: 0, right: rightInset)
}else{
return UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if collectionView.isEqual(self.kindTabCollectionView){
let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
return flowLayout.itemSize
}else{
let height = CGFloat(40.0)
let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
let widthMargin = (flowLayout.sectionInset.left + flowLayout.sectionInset.left + flowLayout.minimumInteritemSpacing)
if indexPath.item == 0 && indexPath.section == 0{
let width = floor(collectionView.frame.size.width - widthMargin)
return CGSize(width: width, height: height)
}else{
let width = floor((collectionView.frame.size.width / 2) - widthMargin)
return CGSize(width: width, height: height)
}
}
}
However, the result that come out is this :
(Sorry, it was just a few frame, i tried my best to screen shot it, but it did tried to change to correct frame size, but then it just revert to the small "fitToSize" pic)
Check collectionView's "Estimated Size" attribute in the Size Inspector (Storyboard). It should be set to "None" when using an extension of UICollectionViewDelegateFlowLayout to set cell's size.
As stated in the Xcode 11 Release Notes:
Cells in a UICollectionView can now self size with Auto Layout
constrained views in the canvas. To opt into the behavior for existing
collection views, enable “Automatic” for the collection view’s
estimated size, and “Automatic” for cell’s size from the Size
inspector. If deploying before iOS 13, you can activate self sizing
collection view cells by calling performBatchUpdates(_:completion:)
during viewDidLoad(). (45617083)
So, newly created collectionViews have the attribute "Estimated Size" set as "Automatic" and the cell's size is computed considering its subview dimensions, thus ignoring the UICollectionViewDelegateFlowLayout extension methods, even though they are called.

UICollectionView cell ignores UICollectionViewDelegateFlowLayout - sizeForItemAt

I am trying to layout the size of my collectionViewCell so that in each row there are 7 cells (easy right?).
The cell is very simple, it only has a UILabel with top, left, bottom and right constraints of 0.
I have setup the sizeForItemAt method as below, made sure my controller is a collectionViewDelegateFlowLayout, collectionViewDelegate and collectionViewDataSource, and that collectionview.delegate and .datasource = self.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let safeScreenWidth = self.view.frame.size.width - 30
let width: CGFloat = (safeScreenWidth / 7)
let height: CGFloat = 40.0
return CGSize(width: width, height: height)
}
The method is called correctly (checked with break points and print()) however the cell decides to totally ignore the size given and comes out with a size just enough to contain its label's text (if the label's text is empty the app crashes for "Invalid parameter not satisfying: !CGSizeEqualToSize(size, CGSizeZero)").
I tried setting different sizes such width = 200, height = 200; but nothing changes the cell is always very small, as I said, just enough to fit its label's text.
A quick solve is setting height and width constraints for the label in cellForItemAt, but I want to understand why sizeForItemAt does not work.
For some reason, automatic estimated size has bigger priority, than calculated in sizeForItemAt method.
Try to set collectionViews Estimate Size to None

Autoresizing of cells in UICollectionViewCell in accordance with the frame size

How can I make the cells in collection view auto resize so that minimum of four cells can at least fit in a single row, and if the frame size is that of iPad, then more cells could fit in row. Four cells should be the minimum number of cells in a row, please see the pictures below for further clarification:
I have a collection view which allows me to add images by using the picker view controller, at first the collection view looks like this:
I can further add images to this collection view and after adding several images, it would like:
Right now if there are four images, the fourth one goes to the next row, I want system to autoresize the cells based on the frame size so that minimum four cells are shown in one row. I'm a newbie and quite new to collection view, can someone please help me on this?
you can decide size of collectionview's cell using following method:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize{
// Return different cells size
let screenBounds = UIScreen.main.bounds
var width = screenBounds.width
let totalLeftRightInsect = 16
let minimumsSpaceBetweenCell = 5
switch (Condition for detecting device ) {
case "iPhone":
let numberOfCell = 4
let totalSpace = CGFloat(( numberOfCell * spaceBetweenCell) + totalLeftRightInsect)
width = (width - totalSpace) / CGFloat(numberOfCell)
return CGSize(width: width, height: width)
case "ipad":
let numberOfCell = 6
let totalSpace = CGFloat(( numberOfCell * spaceBetweenCell) + totalLeftRightInsect)
width = (width - totalSpace) / CGFloat(numberOfCell)
return CGSize(width: width, height: width)
default:
return CGSize(width: 0.0, height: 0.0)
}}
Answer for Swift3, Xcode 8 with horizantal spacing fixed:
The issue with all earlier answer is that cell size is given CGSizeMake(cellWidth, cellWidth) actually takes all screen leaving no room for margin lines due to which collectionView tries to adjust the row/column spacing by taking one element less in each row and unwanted extra spacing.
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let linespacing = 5 //spacing you want horizantally and vertically
let numberOfCell: CGFloat = 3 //you need to give a type as CGFloat
let cellWidth = UIScreen.mainScreen().bounds.size.width / numberOfCell
return CGSizeMake(cellWidth - linespacing, cellWidth - linespacing)
}

Getting height of collection view is wrong after auto layout have done its job

How do I get the height of the collection view in the delegate method so that the cells height are resized? The collection view is not fixed height in my layout so auto layout changes its height, but the cells height is not correct with the current code.
ThiscollectionView.bounds.size.height returns the wrong height it seems. It seems like it is the value before it was resized.
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
if indexPath.row % 4 == 0 {
return CGSize(width: 100, height: collectionView.bounds.size.height)
} else {
return CGSize(width: 100, height: collectionView.bounds.size.height)
}
}
You need to wait for ViewDidLayoutSubviews, to get actual size. The problem is -sizeForItemAtIndexPath get calling before that. It's not a great solution, but try to refresh CollectionView cells size after ViewDidLayoutSubviews.

Resources