CollectionView Constraints breaking - ios

I have a collectionView and each cell contains a tableView inside it. The gray portion is supposed to be the collectionView.
But when I run the app, I get something like this
As you can clearly see that the table view present doesn't stick to the constraints set. I get the following errors from the console.
The behavior of the UICollectionViewFlowLayout is not defined because:
the item height must be less than the height of the UICollectionView minus the section insets top and
bottom values, minus the content insets top and bottom values.
The relevant UICollectionViewFlowLayout instance is <UICollectionViewFlowLayout: 0x7f80fd28e420>, and it
is attached to <UICollectionView: 0x7f80fc08f800; frame = (0 114; 375 664); clipsToBounds = YES;
autoresize = RM+BM; gestureRecognizers = <NSArray: 0x600000d6c4b0>; layer = <CALayer: 0x6000003beee0>;
contentOffset: {0, 0}; contentSize: {0, 0}; adjustedContentInset: {0, 0, 0, 0};
layout: <UICollectionViewFlowLayout: 0x7f80fd28e420>; dataSource: <BizTiz.EventDetailController: 0x7f80fa0b7800>>.
Make a symbolic breakpoint at UICollectionViewFlowLayoutBreakForInvalidSizes to catch this in the debugger.
How do I fix this? Also when I push this collectionView onto the stack, there is a small lag. I am guessing this might be due to the previous error.

Please set layout in the UICollectionViewDelegateFlowLayout.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: <cell_width>, height: <cell_height>)
}

Realized my mistake....
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: view.frame.height - 114)
}
I was setting the height as view.frame.height. I had forgotten to subtract the extra height at top.

Related

UICollectionView flow layout not defined

I have a horizontally scrolling UICollectionView using the flow layout that has its delegate and datasource set. It has a height of 50 and is pinned to my view using autolayout. Unfortunately, it never displays and always gives the following error:
2020-10-09 21:30:08.687110+0100 MyApp[8679:1601772] The behavior of the UICollectionViewFlowLayout is not defined because:
2020-10-09 21:30:08.687428+0100 MyApp[8679:1601772] the item height must be less than the height of the UICollectionView minus the section insets top and bottom values, minus the content insets top and bottom values.
2020-10-09 21:30:08.687743+0100 MyApp[8679:1601772] Please check the values returned by the delegate.
2020-10-09 21:30:08.688641+0100 MyApp[8679:1601772] The relevant UICollectionViewFlowLayout instance is <UICollectionViewFlowLayout: 0x105dc6350>, and it is attached to <UICollectionView: 0x10611d000; frame = (0 0; 375 90); clipsToBounds = YES; autoresize = RM+BM; gestureRecognizers = <NSArray: 0x28372ccf0>; layer = <CALayer: 0x2839465c0>; contentOffset: {0, 0}; contentSize: {1081.6666666666667, 90}; adjustedContentInset: {0, 0, 0, 0}> collection view layout: <UICollectionViewFlowLayout: 0x105dc6350>.
2020-10-09 21:30:08.688943+0100 MyApp[8679:1601772] Make a symbolic breakpoint at UICollectionViewFlowLayoutBreakForInvalidSizes to catch this in the debugger.
I've noticed that it is saying the contentSize is {1081.6666666666667, 90} but it actually only calls the numberOfSections and numberOfItemsInSection delegate methods. The others aren't called, and neither is the UICollectionViewDelegateFlowLayout sizing method. Why is this and how is it getting a contentSize without actually calling the datasource/delegate methods? It makes no difference if I set the autolayout height from 50 to 90.
My collectionView is set up in the storyboard. I have registered my cell and set the estimated flow layout size like so:
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(
UINib(nibName: "VideoClipCell", bundle: nil),
forCellWithReuseIdentifier: "VideoClipCell"
)
if let flowLayout = self.collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.estimatedItemSize = CGSize(width: 30, height: 50)
}
}
These are the datasource/flow delegate methods:
extension EditingViewController : UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: "VideoClipCell", for: indexPath) as? VideoClipCell else {
return UICollectionViewCell()
}
return cell
}
}
extension EditingViewController: UICollectionViewDelegateFlowLayout {
func collectionView(
_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 100, height: 50)
}
}
Do you have an UIImageView in your cell? That's probably why you're getting 1081.6666666666667 for the content size.
You probably don't want self-sizing cells, so turn off "Estimate Size" in the storyboard. Otherwise, your collection view won't read what's inside sizeForItemAt.
Can you show your UICollectionView`s init func,i guess you did not set the UICollectionView delegate to self,and the layout itemsize height is larger than 90

UICollectionView layout behave weirdly on scrolling

I'm setting a collection View in order to display content as on instagram. instagram's grid
In order to set the layout I'm using some delegate methods of UIcollectionviewDelegateFlowLayout :
extension ProfileViewController : UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat{
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat{
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (view.frame.width)/3, height: (view.frame.width)/3)
}
}
When I open the project, the collectionView works well.
The problem occurs when you scroll down very fast the collectionView so that you make momentaneously disappear all the cells.
In that exact moment the cell layout I've set before doesn't hold and the images become ultra zoomed and one on top of each other, like this: messed Up collectionView
I don't know how to fix this issue.
Also in the compiler I get this error :
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.
Please check the values returned by the delegate.
The relevant UICollectionViewFlowLayout instance is <UICollectionViewFlowLayout: 0x7fbbe972d1f0>, and it is attached to <UICollectionView: 0x7fbbeb056400; frame = (0 0; 414 896); clipsToBounds = YES; autoresize = RM+BM;
gestureRecognizers = <NSArray: 0x60000203c4e0>; layer = <CALayer: 0x600002ef19c0>; contentOffset: {0, 424}; contentSize: {414, 16048}; adjustedContentInset: {88, 0, 83, 0}; layout: <UICollectionViewFlowLayout: 0x7fbbe972d1f0>; dataSource: <InstagramClone.ProfileViewController: 0x7fbbe9708890>>.
I think your problem could be the same as the problem in this post.
Try fixing your problem by setting the UICollectionView's estimatedSize to none.
This is because in Xcode 11 Cells in a CollectionView can now self-size with Auto Layout constrained views in the canvas.

Collection View Cells are resizing unintentionally when scrolling to bottom of CollectionView

I am building a collection view (gallery) of images.
The layout is 3 cells per row.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width: CGFloat = (view.frame.width / 3) - 8
let height: CGFloat = width
return CGSize(width: width, height: height)
}
Everything looks great until I scroll to the bottom of the collection view.
it goes from:
3 per row, looks good
to:
super blown up picture, larger than the screen
I also get this error message:
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.
Please check the values returned by the delegate.
The relevant UICollectionViewFlowLayout instance is <UICollectionViewFlowLayout: 0x7fa97f708c70>, and it is attached to <UICollectionView: 0x7fa981022000; frame = (0 0; 414 896); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x6000019986f0>; layer = <CALayer: 0x6000017a8820>; contentOffset: {0, 498.33333333333331}; contentSize: {414, 1250}; adjustedContentInset: {88, 0, 34, 0}; layout: <UICollectionViewFlowLayout: 0x7fa97f708c70>; dataSource: <MarsRover.GalleryCollectionVC: 0x7fa97f50b610>>.
any insight would be great, I want to turn this into infinite scrolling (api prefetching) too down the road, just fyi if that means I can ignore this.
The error you're getting is pointing at the item's width as the problem, it is saying, basically that the item(s) width and spacing cannot be more than the collection view's width. This is because your collection view has a vertical scrolling.
So to achieve a correct behavior for your collection view you must set your controller as the delegate for the collection view and also adopt the UICollectionViewDelegateFlowLayout. In your case I can see you've already implemented the collectionView(_:, layout:, sizeForItemAt: method.
In your implementation there is a clear intention to divide the collectionView's width in three equal parts. The calculation is considering a third part of the self.view.width minus eight. If I assume correctly you're intending to left an inter-item spacing of 8. If that's the case you should specify it into another method:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 8
}
That will specify the inter-item spacing to 8 points.
Continuing with the cell's width and height, you must then divide the collectionView.frame.width but before that you must subtract the inter-spacing value from this quantity, because that is the remaining space for your cells.
So your implementation would be
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// We subtract 16 because there are 2 inter item spaces like:
// [CELL] [spacing:8] [CELL] [spacing:8] [CELL]
let usableWidth = collectionView.frame.width - (2 * 8)
let cellWidth: CGFloat = usableWidth / 3
let cellHeight: CGFloat = cellWidth
return CGSize(width: cellWidth, height: cellHeight)
}
This should do the layout for you.
Since all of your items are they same size you should not be setting them in the delegate method. Instead set a static size on your flowLayout (you can drag an outlet from the storyboard) in viewDidLayoutSubviews and use the layout insets to do it so you can't possibly get the math wrong:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let remainingSpace = collectionView.bounds.width
- flowLayout.sectionInset.left
- flowLayout.sectionInset.right
- flowLayout.minimumInteritemSpacing * (Constant.numberOfItemsAcross - 1)
let dimension = remainingSpace / Constant.numberOfItemsAcross
flowLayout.itemSize = CGSize(width: dimension, height: dimension)
}
First of all, why not use bounds instead of frame?
Second, the reason this is happening is most likely because you are adjusting the layout elsewhere while the collectionView is loading cells (scrolling will make it load cells).
Are you using UIScrollViewDelegate methods to do anything with layout?

CollectionViewCell's container view doesn't fill cell

I have a collectionView who's height and width I set with constraints using autolayout.
In my
func collectionView(_ collectionView: ...., sizeForItemAt indexPath: IndexPath) I return CGSize(width: self.collectionView.frame.width-20, height: self.collectionView.frame.height) and as seen on the image linked below it lines up perfectly fine.
The problem is that the container view (white space) of that cell doesn't fill up the green space (that is the cell's background) at all even though I have set constraints of the containerView to hug left/right/bottom/up with 0 margin.
What I have tried so far:
Make a height constraint for the container view that I assign the collection view's height
updateConstraints() in cellForItemAt and inside the cell's class in ```layoutSubview()````
Set the container view's frame to the collectionView's frame
cell.layoutIfNeeded()in cellForItemAt
None of these have solved my problem.
https://i.stack.imgur.com/mD2iC.jpg
Here issue related to inset I think. Please use collection delegate method
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section{
return UIEdgeInsetsMake(top, left, bottom, right);
}
or
UICollectionViewFlowLayout *aFlowLayout = [[UICollectionViewFlowLayout alloc] init];
[aFlowLayout setSectionInset:UIEdgeInsetsMake(top, left, bottom, right)];
Swift 5.1
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 15.0
}

UICollection View causes "UICollectionViewFlowLayoutBreakForInvalidSizes" on smaller devices

On an iPhone 6 Plus, the collection view cells are fine, but when testing on another device size like the iPhone 5, i'm bombarded with "
017-06-15 01:59:25.744 HyperTest[3865:8825385] The behavior of the UICollectionViewFlowLayout is not defined because:
2017-06-15 01:59:25.744 HyperTest[3865:8825385] 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.
2017-06-15 01:59:25.745 HyperTest[3865:8825385] The relevant UICollectionViewFlowLayout instance is , and it is attached to ; layer = ; contentOffset: {0, 0}; contentSize: {414, 219}> collection view layout: .
2017-06-15 01:59:25.745 HyperTest[3865:8825385] Make a symbolic breakpoint at UICollectionViewFlowLayoutBreakForInvalidSizes to catch this in the debugger.
especially when I do this:
let layout = billFeedCollectionView.collectionViewLayout as? UICollectionViewFlowLayout // casting is required because UICollectionViewLayout doesn't offer header pin. It's feature of UICollectionViewFlowLayout
layout?.sectionHeadersPinToVisibleBounds = true
layout?.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
anyway, I can resolve this problem? I need to ensure that the cell scales and fits all devices.
By confirming UICollectionViewDelegate from your class. The delegate method sizeForItemAtIndexPath will call from UICollectionViewDelegateFlowLayout and this will call in your class now you can return the size of UICollectionViewCell.
This works for me
method:
collectionView.delegate = self
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSizeMake(collectionView.frame.size.width, collectionView.frame.size.height)
}
It's also possible that when you have a collectionView in a UITableViewCell this cell actually resizes the collectionView height to 0.
In my case, the collectionView is showing an array of UIImageView to display some image gallery in each cell.
If the cell is not supposed to show any images (text without image for example), the collectionView is hidden.
The collectionView, if put within a StackView might need to have a height constraint and to avoid conflicts, you might need to set it to priority 999.
Then, when you hide the collectionView, it will actually try to set the height to 0.
The collectionView can still contain images in it and that will trigger the error.
SOLUTION: empty your datasource and reload the collectionView when you configure your cell.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize {
let cellsize = CGSize(width: (CollectionView.bounds.size.width/2) - 12, height:(CollectionView.bounds.size.height/3) - 20)
return cellsize
}
here CollectionView is outlet of UICollectionView

Resources