3

I have a Collection View with cells of dynamic height (based on a possibly multiline label and a textview inside), which adapts in height perfectly when I span it over multiple lines. However, when the text is just one word or it doesn't cover the entire screen width, the cell is just as wide as needed to be, resulting in cells next to each other instead of underneath each other.

Take a look at the result with different lengths of text inside the textview.

However, I want the cells to be underneath each other, think Instagram or Twitter kind of view. I obviously need to set a width constraint for the cell so it is equal to the entire width of the screen, but I can't seem to find how to do this.

I use FlowLayout with an estimatedItemSize in my AppDelegate:

let layout = UICollectionViewFlowLayout()
window?.rootViewController = UINavigationController(rootViewController: HomeController(collectionViewLayout: layout))

layout.estimatedItemSize = CGSize(width: 414, height: 150)

I also set constraints for the collectionView in viewDidLoad, so the collectionView takes on the entire width and height of the screen:

collectionView?.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
collectionView?.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
collectionView?.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
collectionView?.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true

My constraints for the label and the textview look like this, also allowing the width to take on the size required (so it's not fixed):

// vertical constraints usernameLabel & commentTextView
addConstraintsWithFormat(format: "V:|-16-[v0]-2-[v1]-16-|", views: usernameLabel, commentTextView)

// horizontal constraints usernameLabel
addConstraintsWithFormat(format: "H:|-16-[v0]-16-|", views: usernameLabel)

// horizontal constraints commentTextView
addConstraintsWithFormat(format: "H:|-16-[v0]-16-|", views: commentTextView)

All this is, according to me, essential to make this work. However, it seems to me that I have yet to set a width constraint to make this work. I have tried to add a widthAnchor on the collectionView (equal to view.widthAnchor), this didn't work:

collectionView?.widthAnchor.constraint(equalTo: self.view.widthAnchor).isActive = true

I have also tried to set the width in the sizeForItemAt method, but this also didn't work:

return CGSize(width: view.frame.width, height: 100)

Now, I am checking the width to see where it goes wrong, but I get the result I would need, I think:

print("collectionView width: ", collectionView?.frame.size.width)
print("collectionView height: ", collectionView?.frame.size.height)

print("screen width: ", UIScreen.main.bounds.width)
print("screen height: ", UIScreen.main.bounds.height)

Results in:

collectionView width:  Optional(414.0)
collectionView height:  Optional(736.0)
screen width:  414.0
screen height:  736.0

I am out of ideas on how to move forward. All articles or posts I have gone through related to this issue either explicitly calculate the height or use the two solutions I have mentioned above.

Any ideas on what I am doing wrong?

PennyWise
  • 543
  • 6
  • 23
  • Check that you've set the collectionView delegate to the controller where you're overriding the `sizeForItemAt` function, otherwise it won't be called. – JonJ Sep 18 '18 at 09:58
  • Actually you are trying to imitate `UITableViewCell` behavior with `UICollectionViewCell`. The UI you're trying to accomplish can easily be done with `UITableViewCell` which works out of the box with automatic (variable) height. But if you are required to do the job with `UICollectionViewCell` you should take a look at [this answer](https://stackoverflow.com/a/26349770/3687801). Also check the [other answer](https://stackoverflow.com/a/38088914/3687801) as well. – nayem Sep 19 '18 at 02:36

2 Answers2

0

Make sure add this protocol in app

UICollectionViewDelegate
UICollectionViewDataSource
UICollectionViewDelegateFlowLayout

Please use below method

    extension YourViewController: UICollectionViewDelegate,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {

        func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
             return CGSize(width: screenWidth, height: screenWidth)
        }

    }
  • When I add the extension, I get the error Redundant conformance of 'HomeController' to protocol 'UICollectionViewDataSource' (I get the same error for UICollectionViewDelegateFlowLayout and UICollectionViewDelegate as well). When I try to add those protocols to my class HomeController, in which I then call for the sizeForItemAt method, I also get the Redundant conformance error.. so I am calling it already, I guess? Or do I need to add it somewhere else? – PennyWise Sep 18 '18 at 10:15
  • Have you subclassed UICollectionViewController for your HomeController class? If so, these protocols are already assigned. – JonJ Sep 18 '18 at 11:17
  • You'll get that error message in if a subclass declares conformance to a protocol which is already inherited from a superclass.Removing the protocol conformance from the subclass declaration solves the problem:Redundant conformance" means that you are specifying extension FlickrPhotosViewController : UICollectionViewDataSource, but this isn't necessary because FlickrPhotosViewController's superclass already provides a UICollectionViewDataSource conformance. You can just use extension FlickrPhotosViewController instead. – Siju Satheesachandran Sep 18 '18 at 11:26
  • Yes, I have subclassed UICollectionViewController in HomeController, because I need it to set up everything of my CollectionView (numberOfItemsInSection, cellForItemAt, minimumLineSpacingForSectionAt) and my general viewDidLoad contains the constraints for the CollectionView as well. If I remove it, I get errors because it's missing. So can't A add all of the ones I need? – PennyWise Sep 18 '18 at 11:31
  • You can just use extension Controller instead – Siju Satheesachandran Sep 18 '18 at 11:33
  • Just added extension HomeController (no added protocols as all of them result in a redundant conformance error) with the sizeForItemAt method, it doesn't give me an error now but it doesn't change the width either. I tried to set it to a fixed value (100, 200, 300 or 400) to see what happens, but nothing does. – PennyWise Sep 18 '18 at 11:39
  • It does get called however: if I print something in the extension (print("test")), it returns five times (I have 5 cells). When I set the width to 500 (more than the screen width), nothing is displayed but I get a debug error: 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. – PennyWise Sep 18 '18 at 11:50
  • You need to specify that you implement the protocol UICollectionViewDelegateFlowLayout in your class declaration. class Controller: UICollectionViewController, UICollectionViewDelegateFlowLayout – Siju Satheesachandran Sep 18 '18 at 12:00
  • I tried that too, but then it throws the same errors at me: **Extension of type 'HomeController' cannot inherit from class 'UICollectionViewController'** and **Redundant conformance of 'HomeController' to protocol 'UICollectionViewDelegateFlowLayout'**. – PennyWise Sep 18 '18 at 12:03
  • Storyboard is empty. I don't get it. Also feel like this discussion is getting way too long. – PennyWise Sep 18 '18 at 12:12
0

Update:

I have added an extension for my HomeController according to the answer from Siju Satheesachandran:

extension HomeController {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: 400, height: 100)
    }
}

Then, in the cellForItemAt method, I added a print for the width of the current cell:

print("cell width: ", cell.frame.size.width)

Which returns five times the following:

cell width:  400.0

So in my opinion, the required size is listened to, but my cell just adapts to whatever width it wants to anyways?

Another interesting update:

When I change the text of my TextView to something way too long, so it should break over multiple lines, and I run the app, the app gives this debug error infinite times while not loading and displaying a black screen:

2018-09-18 15:34:35.806688+0200 prjDynamicCells[39793:1627318] The behavior of the UICollectionViewFlowLayout is not defined because:
2018-09-18 15:34:35.807472+0200 prjDynamicCells[39793:1627318] 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.
2018-09-18 15:34:35.807572+0200 prjDynamicCells[39793:1627318] Please check the values returned by the delegate.
2018-09-18 15:34:35.807911+0200 prjDynamicCells[39793:1627318] The relevant UICollectionViewFlowLayout instance is <UICollectionViewFlowLayout: 0x7feb5ec17650>, and it is attached to <UICollectionView: 0x7feb5f841600; frame = (0 0; 414 736); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x60000044e6d0>; layer = <CALayer: 0x60000003f4a0>; contentOffset: {0, -64}; contentSize: {414, 1000}; adjustedContentInset: {64, 0, 0, 0}> collection view layout: <UICollectionViewFlowLayout: 0x7feb5ec17650>.
2018-09-18 15:34:35.808009+0200 prjDynamicCells[39793:1627318] Make a symbolic breakpoint at UICollectionViewFlowLayoutBreakForInvalidSizes to catch this in the debugger.
PennyWise
  • 543
  • 6
  • 23