UICollectionViewCell width greater than UICollectionView error handling - ios

I'm currently using a UICollectionView and a UICollectionViewFlowLayout and the following UICollectionViewCell:
However, a bug was raised recently that locks the UI and upon inspection the console kept spitting out the following continuously to the console.
Make a symbolic breakpoint at
UICollectionViewFlowLayoutBreakForInvalidSizes to catch this in the
debugger.The behavior of the UICollectionViewFlowLayout is not defined
because: the item width must be less than the width of the
UICollectionView minus the section insets left and right values, minus
the content insets left and right values.
Now the error is pretty straight forward, the cell itself has a length greater than that of the collection view. The reason for this is that in an outlier occurrence, there is a piece data that is just very long and causes the cell to go outside the bounds of the collection view.
On the cell itself, I have a line size of 1 and that the tail is truncated. It was my assumption that autolayout would handle this in such a way that if the cell is greater than the collection view width it would just use the width of the collection view on the cell and just truncate the text in the label. However, autolayout does not do this and the error above is produced repetitively in the console.
So now the question is what would be the best / correct way to handle this edge case?
Is there something small that I have missed?
Given this scenario I would have to 'Tweak' the flow layout, so would it be recommended to subclass the flow layout, override layoutAttributesForElementsInRect: and then adjust that cells size frame/size whenever it is greater than the bounds of the UICollectionView? This route seems like over kill the bug?

Add a less-than-or-equal width constraint to your custom UICollectionViewCell.
Create an outlet to that constraint and set it in collectionView(_:cellForItemAt:).

You may just use the following UICollectionViewDelegateFlowLayout method to dictate the size of the item at the given indexPath
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize
For further details have a look at Appleā€™s documentation

Related

Intrinsic content sized UICollectionView inside UITableView

Let me preface by saying that I've spent probably 100 hours over the past few months searching Google and StackOverflow trying to answer this question. I've followed many tutorials and have not been able to solve my problem. Every one is a major disappointment and is disheartening.
Expectation
Reality
Problem
I have a UITableView that contains a UIView. The UIView has auto layout constraints to pin it 15pt off the edges of the cell, and then I give that a shadow and rounded corners. Inside that UIView, I have a few labels, then a UICollectionView, and a stack view.
My UICollectionView keeps having a height of 0 and I can't figure out how to make it have intrinsic size. The only way I can seem to make it display properly is to force a specific height to it via auto layout inside the storyboard (which is what I did to take this screenshot). Unfortunately, forcing a height like this is not realistic for 2 reasons:
The height varies based on the width of the device. Since the images are squares, the height is loosely (but not exactly) half the width of the device.
If there are no images, the height of the collection view should be 0.
It really seems like it should be possible to use the intrinsic content size of the flow layout here.
Storyboard
Constraints
I've got about 15pt of space between each of the labels, collection view, and stack view (so, the vertical space). The collection view prototype cell has an image view in it. The image view has top, bottom, trailing and leading space all set to 0 to the collection view cell, and also has a 1:1 ratio.
Collection View Layout
To achieve the layout of one large image and 4 small images, I'm using SNCollectionViewLayout.
Data Source / Delegate
In my controller, in viewDidLoad, I've set the estimatedRowHeight of the UITableView to 400, and I've set the rowHeight to UITableView.automaticDimension.
In the tableView cellForRowAt, I dequeue a reusable custom UITableViewCell class, set various outlets on the table view cell, and call reloadData() on the collection view for the table view cell. In the storyboard, I've set the table view cell as the collection view's data source and delegate.
In the table view cell's awakeFromNib function, I've got it setting the corner radius, shadow, and using the following code for the flow layout on the collection view (which you can see in their example):
let snCollectionViewLayout = SNCollectionViewLayout()
snCollectionViewLayout.fixedDivisionCount = 4
snCollectionViewLayout.delegate = self
snCollectionViewLayout.itemSpacing = 10
collectionView.collectionViewLayout = snCollectionViewLayout
I've also implemented the optional SNCollectionViewLayoutDelegate protocol by writing the following function:
func scaleForItem(inCollectionView collectionView: UICollectionView, withLayout layout: UICollectionViewLayout, atIndexPath indexPath: IndexPath) -> UInt {
if indexPath.row == 0 || collectionView.numberOfItems(inSection: indexPath.section) <= 2 {
return 2
} else {
return 1
}
}
This function is why the first image is twice the height and width as the rest.
Research
In trying to figure this out, I've checked the collectionView.collectionViewLayout.collectionViewContentSize inside the table view cell's cellForRowAt and willDisplay methods. In either case, it's (0,0).
I've also tried checking it in the collection view cell's cellForItemAt and willDisplay. Those log lines don't even print unless I add a height constraint to the collection view to force it tall, but then I don't have a good way to bring the height back down to what it should be, and any time I've tried, I got conflicting constraint warnings and really wonky stretched views (such as the stack view being totally squished).
In testing, I've forced a height constraint that's large enough for the collection view to fit, and when that happens, it renders correctly and the collection view content size is correct, so I know it's capable of keeping track (which can be seen here).
Does anyone have any insight into this? I'd be happy to provide more code if you have any specifics on things to look into. I tried to give the full picture, while keeping this as short as possible (I know it's long).
I feel like the issue has something to do with the collection view being inside a table view cell.
Thanks in advance.
Keep a height constraint connection in your table view cell.
#IBOutlet weak var collectionViewHeightConstraint: NSLayoutConstraint!
And update the height constraint value programatically as per height calculations
self. collectionViewHeightConstraint.constant = 100 // set it to 0 if nothing to display

Autosizing UICollectionView cell embedded in UITableView not working

I have spent a few day's now trying out things suggested on various SO posts but have not been able to figure this out. I'm probably looking right at the issue!
I am making a switch over from Android to iOS and seem to be having some trouble with autolayout. Specifically, getting the UICollectionView to resize to the height of its content. Each collection view can have a different cell size based on its content but the cell size will be the same for each item on a given row.
The UICollectionView is nested inside a UITableViewCell. The UITableViewCells appear to be resizing to its content height correctly and looking at the Debug View Hierarrchy the issue seems to be the UICollectionView.
I have created a mini demo and put it on this github (link below). I have thrown in a bunch of test data so the bottom part of the ViewController VC is a bit messy. The demo just illistrates how each row can have different content size. There can be several table rows of the same style.
NOTE: I had to add the following line to CategoryRowCell.swift in order for me to get the below screen shots. Without this line the UICollectionView height becomes 0!
...
collectionView.heightAnchor.constraint(equalToConstant: 250)
...
GitHub link
So here is what it looks like right now..
scrolled down
Notice the gaps at the top and bottom of the horizontal lists. Here is what i am expecting..
Here is part of the debug view hierarchy. You will see that the collection view already has top and bottom margins (good), but the red lines show the excess height being applied to the collection view. The green line indicates the height i am expecting. I am wanted the collection view to have a height of its content.
I think it would be better to do it the other way around, having the UICollectionView as the parent and UITableView as the child. Since the first is more flexible and customizable.
Anyway for your case I think you need to take a look at two things:
1- To have a better control of the cell size you can use this func for UICollectionView:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
// The desired size based on the value at that row (the name for example)
if objects[indexPath.row].name == "some condition" {
return CGSize(width: 150, height: 50)
}
else {
return CGSize(width: UIScreen.main.bounds.width, height: 75)
}
}
2- The size inspector values for the UICollectionView and UITableView (you might have some extra spacing in one of them)

Collectionviews move weird when setting constraints

I got three collectionViews in my application. Now I finished it as far as I wanted it to complete. The only problem which I have for several days now are the constraints. I don't know how to explain it in words so I added two pictures of the problem to the question I hope it's understandable.
What I currently have:
What I want:
Use UICollectionViewDelegateFlowLayout delegate method to archive this.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
in this method, you can programmatically set size of cells. Give sizes according to your need of each collectionView.
Based on your screenshots, it looks like the collection view holding the column of white dots expands its width when the superview (the screen size) changes.
It also looks like you are using Flow Layout... when the view gets wider there is enough room for 2 cells on each row.
You should be able to fix that by either:
constraining the width of that collection view so it doesn't expand, or
using a custom Collection View Layout
I'd suggest trying a width constraint first - see if you can get the layout to look the way you want.
As a side note... using collection views may not be the best approach for what you're trying to do. UIStackView might be a better option (although, I don't know what else the interface will be doing, so maybe not).

UICollectionViewCell with auto layout: how to get rid of UIView-Encapsulated-Layout-Height?

I try to have a UICollectionViewCell with a dynamic height determined by auto layout.
On the UICollectionViewFlowLayout I set the estimatedItemSize but all my cells are exactly as big as that estimated size. When I do have a larger cell, I get a Unable to simultaneously satisfy constraints with UIView-Encapsulated-Layout-Height set to exactly the height from the estimated size. How do I get rid of this?
In general I have found Apple's claim that there is such a thing as "a UICollectionViewCell with a dynamic height determined by auto layout" to be a myth. What I do is implement the delegate method sizeForItemAt to do what the runtime ought to be doing, namely, measure the size in accordance with the internal constraints and return that as the desired size.

Calculating sizeForItemAtIndexPath in UICollectionView without using the cells

I am in a loop with myself on from where to get the cell's size in a collection view in case I have a cell with auto layout
I understand that sizeForItemAtIndexPath should get the size from the data and not from the cell itself, because the cell is not drawn yet, but in auto layout I can't calculate this just by looking at the data, unless I put in code the contraints in a way that I can later calculate from them (seems crooked)
On the other hand, UIView does have intrinsicContentSize and systemLayoutSizeFittingSize which gives the size of a view that's already drawn or going to be drawn.
But in sizeForItemAtIndexPath I don't have a view yet, and the data is just data.
the cell nib looks like this
In what way should I get the size of a custom cell (from .nib)?
I could technically hard code sizes for the images and icons, but that feels wrong. It also feels wrong to ask the view what size it is in a function that should tell the controller what size it is
You can use an off screen prototype cell which you configure and then use to determine its size.
This is a common approach for UITableView pre iOS8 to determine cell height. The same approach should work for UICollectionView.
See the accepted answer here: Using Auto Layout in UITableView for dynamic cell layouts & variable row heights

Resources