Swift3 - UICollectionViewCel's intrinsicContentSize is (-1, -1) - ios

When I try to get cell's intrinsicContentSize in function collectionView(_:didSelectItemAt:), the result is (-1, -1).
I am using flow layout with auto layout config of cell's subviews and did not implement collectionView(_:layout:sizeForItemAt:). Does anyone have ideas?
Edit:
the auto layout of cell has fixed width and height. I turned on self-sizing by setting estimatedItemSize and config the collectionView as following:
self.collectionView.delegate = self
self.collectionView.dataSource = self
if let layout = self.collectionView.collectionViewLayout as?
UICollectionViewFlowLayout {
layout.estimatedItemSize = CGSize(width: 100, height: 100)
}
And I try to get the cell's intrinsicContentSize in:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = collectionView.cellForItem(at: indexPath)
let size = cell?.intrinsicContentSize
}
which gives me (-1, -1) for size

A UICollectionViewCell has no intrinsicContentSize. Why do you expect it to have one?
If you want to know what size the cell is at this moment, just ask for its bounds.size.
If you want to know what size the cell would take on if it were sized from inside by the autolayout constraints of its subviews, call systemLayoutSizeFitting(_:).

Related

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.

Prevent sizeForItemAt from using the wrong cell size because of reuse for dynamic collectionView cell

I used auto layout to dynamically calculate the size of the collectionView cell. Some cells are using the dimensions from the reused cells when they first scrolled to view port. As I continue to scroll the collectionView, they will be set to the correct value.
In my sizeForItemAt, I have the following:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if let cachedSize = cachedHeightForIndexPath[indexPath] {
return cachedSize
}
if let cell = collectionView.cellForItem(at: indexPath) {
cell.setNeedsLayout()
cell.layoutIfNeeded()
let size = cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
cachedHeightForIndexPath[indexPath] = size
print("value is \(size) for indexpath: \(indexPath)")
return size
}
return CGSize(width: ScreenSize.width, height: 0.0)
}
I have a three sessions, with the first section all cell's height theoretically equals to 88, and all the other sections all cell's height equals to 104.
Originally, only the first section is visible. From the console, I can see the height of the cell is set to 88.0 as expected. As I scroll to the remaining sections(the first section will be invisible and the cells will be reused), some cells from second section and third section are using the value 88.0 as the height of the cells when first scrolled to view port instead of 104. As I continue to scroll, the wrong sized cell will be using 104 as the dimension. How do we force all the cells to recalculate the height and don't use the height from old cell.
You have the right idea, but when you measure the cell by its internal constraints by calling systemLayoutSizeFitting, instead of calling systemLayoutSizeFitting on an existing cell (collectionView.cellForItem), you need to arm yourself with a model cell that you configure the same as cellForItem would configure it and measure that.
Here's how I do it (remarkably similar to what you have, with that one difference; also, I store the size in the model):
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let memosize = self.sections[indexPath.section].itemData[indexPath.row].size
if memosize != .zero {
return memosize
}
self.configure(self.modelCell, forIndexPath:indexPath) // in common with cellForItem
var sz = self.modelCell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
sz.width = ceil(sz.width); sz.height = ceil(sz.height)
self.sections[indexPath.section].itemData[indexPath.row].size = sz // memoize
return sz
}

TableView cell height not according to collectionview height on first reload

I know this is already asked in many threads but none of the solutions are working for me. Problem scenario is: I have a collectionview in tableview cell. The constraints on collection view are as: Collectionview top, leading, trailing and bottom constraint to superview and heightconstraint (with priority 999). Changing the height of collection view as:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: GalleryCollectionViewCell.identifier, for: indexPath) as? GalleryCollectionViewCell {
collectionViewHeightConstraint.constant = collectionView.contentSize.height
self.layoutIfNeeded()
return cell
}
return UICollectionViewCell()
}
On a button click the tableview is reload and when collectionview height constraint value is printed, it comes true but the tableview cell is not getting exact content size. But when i scroll then the tableview cell gets the exact size.
PS: Also tried using :
collectionViewHeightConstraint.constant = collectionView.collectionViewLayout.collectionViewContentSize.height
In your collectionviewCell, you need to take ContentView and set constant Height
self.contentView.translatesAutoresizingMaskIntoConstraints = false
Heightconstant.constant = 200
try this by setting collection flow layout:-
var flowLayout = UICollectionViewFlowLayout()
flowLayout.itemSize = CGSize(width: 200, height: collectionView.contentSize.height)
flowLayout.minimumInteritemSpacing = 0
flowLayout.scrollDirection = .vertical
CollectionVW.collectionViewLayout = flowLayout

Dynamic Cell Height for Custom Collection View Layout

I have a collection view which makes use of a custom layout. I'm trying to calculate the height dynamically, but the problem is sizeForItemAt:indexPath is called before cellForItemAt:indexPath.
My cells get loaded in cellForItemAt. But since sizeForItemAt is called before cellForItemAt, then I can't use my calculated height.
I know with Apple's FlowLayout I can just set the estimatedItemSize for the layout. I'm not sure how to do it with a custom layout.
Please advise. Thank you!
I had the same problem, my app uses custom layout with dynamic height. I found that with custom layout that doesn't extend UICollectionViewFlowLayout or any other default layout, dynamic height will not work because as per Apple documentation (and like you probably noticed) with a completely custom layout you have to predefine all the cells X, Y, width, height before the cell load and before you even have data.
I changed my custom layout to subclass UICollectionViewFlowLayout and implemented UICollectionViewDelegateFlowLayout. When this method is called the cell did not load yet but the cell's data is available since I know what the cell should look like (assuming you are using cell prototypes) I could calculate the cell width and height using its data and index, something like this:
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
// get the cell's data
if let data = self.fetchedResultsController.fetchedObjects![indexPath.row] as? YourDataType {
// carculate the cell width according to cell position
let cellWidth = carculateCellWidth(indexPath.row)
var cellHeight : CGFloat = 0
// assuming the cell have a label, set the label to have the same attributes as set in the storyboard or set programmatically
let label = UILabel()
label.numberOfLines = 0
label.font = UIFont.preferredFont(forTextStyle: .subheadline)
label.text = data.text
// carculate the height of the cell, assuming here the label width equal the cell width minus 10px left and right padding.
cellHeight += label.systemLayoutSizeFitting(CGSize(width:cellWidth-20, height: CGFloat(Float.greatestFiniteMagnitude))).height
return CGSize(width: cellWidth, height: cellHeight)
}
return .zero
}
This is not a very elegant solution but it works.

Simply using storyboard, constraints to set size of cell in UICollectionView

Here's a UICollectionView, and the cell in purple:
Quite simply, I want the cells to be 1/2 of the collection view width. (So TBC, it will be a two rows arrangement of cells in the collection view.)
(The collection view is simply fullscreen, so each cell is half the screen width.)
How do you do this in storyboard?
If I try to control-drag in the normal way, it basically doesn't work.
These are simple totally static cells (not dynamic).
For anyone googling here, to save your time: Here's exactly (2016) the simplest way to make a two-across UICollectionView layout; no gaps between the cells.
// Two - two-across UICollectionView
// use a completely standard UIViewController on the storyboard,
// likely change scroll direction to vertical.
// name the cell identifier "cellTwo" on the storyboard
import UIKit
class Two:UICollectionViewController
{
override func viewDidLoad()
{
super.viewDidLoad()
let w = collectionView!.bounds.width / 2.0
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.itemSize = CGSize(width:w,height:w)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
collectionView!.collectionViewLayout = layout
// Note!! DO NOT!!! register if using a storyboard cell!!
// do NOT do this:
// self.collectionView!.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
}
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int
{ return 1 }
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{ return 5 }
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
return collectionView.dequeueReusableCellWithReuseIdentifier("cellTwo", forIndexPath: indexPath)
}
}
You can't do it in the storyboard. The collection view width is not known until runtime, and collection view cells are not under autolayout, so you cannot express the notion "1/2 the width" of anything else. (If you did know the collection view width in advance, you could use the flow layout in the storyboard to set the cell size absolutely, by dividing in your head; but you don't know it, because the width differs depending on the device.)

Resources