Vertically scrolling UICollectionView with self-sizing cells only displays half the cells - ios

I've set up a UICollectionView (with the default flow layout, and vertical scrolling) with custom cells that load from a xib. The cells contain a couple of elements (a UILabel and a UIView) with constraints on them. I've set up the constraints such that the cell height grows as the label text height increases using the new UICollectionViewFlowLayout property that's available in iOS8, estimatedItemSize:
layout.estimatedItemSize = CGSizeMake(self.collectionView.frame.width, 100)
Everything works like a charm except there's one big problem: the UICollectionView loads only half the items as returned by the numberOfItemsInSection method. So, when the method returns, say, 10, my UICollectionView only displays 5 cells (but displays and lays them out perfectly).
Some relevant findings from my debugging attempts:
I've been able to force the loading of the remaining items by calling invalidateLayout or changing the number of sections from 1 to 2. But those are just debugging hacks.
Separately, everything works like a charm when I replace estimatedItemSize with the itemSize property, i.e. hard-coding the item size. But that defeats the self-sizing functionality I'd like to implement.
I'm assuming there's something wrong with how I'm thinking about self-sized cells. Specifically, I wonder if the problem has something to do with constraints.
I'd appreciate any help here.

I believe this is due to a bug in iOS8 which still exists as of iOS8.3.
Flow layout collection views with self-sizing cells will fail to display some of their content if the estimatedItemSize is too small. This is despite the fact that according to the the API docs the only requirement on the estimatedItemSize is that it be non-zero to trigger the self-sizing behavior.
Only Apple is in a position to fix this. I think the best workaround in the mean time is to put an inflated value into estimatedItemSize`.
This repo demonstrates the issue: https://github.com/algal/SelfSizingCellsDemo. (I've filed a radar at rdar://18078598.)

The obvious non-hacky solution is to implement
flowLayout.collectionView(layout:sizeForItemAtIndexPath:)
but of course that is more code than you presumably have hoped for.
I would suggest you try two more things:
Try to experiment with more plausible estimated sizes. Also try the default value, CGSizeZero.
Check what happens in preferredlLayoutAttributesFittingAttributes() by examining the layoutAttributes parameter. Maybe you find a clue to why the other cells are "crowded out".

In a custom subclass of UICollectionViewFlowLayout
override prepareLayout like so:
- (void)prepareLayout {
[super prepareLayout];
CGSize contentSize = self.collectionView.contentSize;
// Without this line, the scrolling doesn't go all the way to the bottom, right away but takes a while
[super layoutAttributesForElementsInRect:CGRectMake(0.0f, 0.0f, contentSize.width, contentSize.height)];
}

I had this exact same problem.
I was using layout.estimatedItemSize = CGSizeMake(373.0, 500.0) together with
preferredLayoutAttributesFittingAttributes( layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes!
and I got exactly the self-sizing behaviour i wanted, except I was only getting half the cells displayed.
The curious thing is when I changed the estimatedItemSize property to: layout.estimatedItemSize = view.bounds.size ALL the cells appear!!
Hope this helps. Good luck.

Related

UICollectionViewCell dynamic height w/two dynamic labels & auto layout

I have a UICollectionViewCell subclass that's setup with a prototype cell and constraints (every view is connected both vertically and horizontally).
I have two labels that can vary in size, they can be either one or two lines. To that end I have set two height constraints on each label, one with a greater than or equal (16 or 20 depending on the label) and a less than or equal (32 or 40 depending on the label).
The number of lines on the labels are set to 0. (Though I have tried a variety of settings).
Since I've used auto layout and constraints to setup the view I've specified a width on the content view in the cell's awakeFromNib()
override func awakeFromNib() {
NSLayoutConstraint.activate([contentView.widthAnchor.constraint(equalToConstant: 341)])
super.awakeFromNib()
configureBorder()
}
On the collectionView's layout I've specified the automaticDimension constant for estimateItemSize & itemSize
if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
flowLayout.itemSize = UICollectionViewFlowLayoutAutomaticSize
}
With the idea of using auto layout I have not implemented a delegate on the collection view.
I am experiencing very inconsistent layout, some instances the cell's will appear as I'd like them to, in others the labels are truncated and in others the labels are extremely narrow and display text over three lines.
I have tried many things, including calling
.sizeToFit()
on each label after it's text property is set, as well as calling
.setNeedsDisplay()
before the cell returns from cellForItemAt
I'm wondering what is the right way to do this with storyboard constraints. I've worked with some of the popular answers to similar questions here on SO, though none to any real success.
flowLayout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
flowLayout.itemSize = UICollectionViewFlowLayoutAutomaticSize
Of course I am familiar with Apple's repeated claims that a UICollectionViewCell in a UICollectionViewFlowLayout can be self-sizing based on internal constraints. But I have never seen working code from Apple that demonstrated this feature, and I have never once myself succeeded in confirming those claims to be true. I do not believe there is really such a thing as a self-sizing cell. For years, trying to get a cell to be self-sizing resulted in crashes. In iOS 10, the crashing mostly stopped, but now the flow layout was not laying out the cells correctly (which sounds like what you're seeing).
Naturally, I have filed bugs on all this, year after year.
Meanwhile, what I do is to give the collection view a delegate and implement collectionView(_:layout:sizeForItemAt:), and I suggest that you should do the same.
EDIT New in iOS 13 you can use a composable layout and abandon UICollectionViewFlowLayout altogether. Self-sizing cells work fine in a composable layout.

Autolayout Width Constants in UICollectionViewCell doesn't work well

Below is a uicollectionviewcell, bar width must be according to number at right of it (vote count), event I log its width contants in console you can see it's calculated correctly, but on ui, two of them seems doesn't work well, if I scroll collectionview for several times it will be fixed, and then it will be fail again, how to set a view's contraint contant in a cell correctly?
code is too long but I want to summarize here
1) in viewcontroller's cellForItem
cell.setup()
cell.layoutIfNeeded()
return cell
2) in cell's setup()
setupBarWidths()
3) in setupBardWidhts()
resetAllWitdhs -> 0
calculate and set widths
is this correct way?
It depends upon how you are setting your constants for your subview inside uicollectionviewcell.
If your cell does not update properly constraints, possible solution is there
You must provide separate width constraint for your horizontal bar, and set it every time your reload cell.
"if I scroll collectionview for several times it will be fixed"
This problem occurs when you does not handle properly constraints changing, for example,you have "if - else " clause in your code, and you don't implement one branch.
Anyway, it's much easier to provide solution if you share your collectionView(_:cellForItemAt:)
UPDATED:
UICollectionView has many bugs internal, so one possible solution is to override UICollectionViewCell and insert the following method:
override var bounds: CGRect {
didSet {
contentView.frame = bounds
}
}
See this thread for more info.

UICollectionView self-sizing-cell on iOS 8 will crash with UIDynamic flowLayout and repeating call to _updateVisibleCellsNow

I am trying to use the so called "self sizing cell" which means:
set estimatedItemSize on flowLayout
overide preferredLayoutAttributesFittingAttributes in cell class
Such as this: UICollectionView Self Sizing Cells with Auto Layout
And I need dynamic effect like this:http://www.teehanlax.com/blog/implementing-a-bouncy-uicollectionviewlayout-with-uikit-dynamics/
It works fine without UIDynamic, but I need UIDynamic. As what I see, it will call the prepareLayout and layoutAttributesForElementsInRect until die, there will be too many _updateVisibleCellsNow waiting in line.
I have no idea how to solve that, please help me in case u see. Or, if I am using those technologies in wrong way, please let me know.
Two things worked for me:
Make sure your collection view itself has layout constraints defined for placement within its superview.
I got this crash when the estimated size was larger than the final size. If I set the estimated size to a smaller value the crash stopped.
If any size or frame position changed, it will trigger all cells' preferredLayoutAttributesFittingAttributes until all cells' frames did not change.
The behavior flow is as the following:
1. Before self-sizing cell
2. Validated self-sizing cell again after other cells recalculated.
3. Did changed self-sizing cell
If second steps can't get stable all cells' position and size, it will become infinite loop.
Please reference my post here: UICollectionView Self Sizing Cells with Auto Layout

Swift, dynamic table view cell heights using Storyboards

I've been wrestling with this for a better part of a week and I'd love some help.
I have a cell laid out in storyboards that's a subclass of UITableViewCell called EntryCell. I've been using autolayout to set constraints in storyboards and all of the constraints on all the elements are blue, lldb throws no errors at runtime so nothing is duplicated or out of place.
My label that says "Journal Entry Here" below is a UILabel and has lines set to 0.
In my view controller under ViewDidLoad, I have
tableView.estimatedRowHeight = 185
tableView.rowHeight = UITableViewAutomaticDimension
but when I run that, simulator shows this:
This is holding up my entire project. I've read a number of tutorials and tried their implementations to no avail. I'm currently not doing anything with heightForRowAtIndexPath. I wonder if that's the problem or if it's something else.
Any help would be hugely appreciated. Thank you!
You shouldn't have to use heightForRowAtIndexPath to get this to work. The main reason UITableViewAutomaticDimension won't expand to fit unlimited line labels is because of missing layout constraints--constraints that won't necessarily show up in the console as errors. There must be continuous anchoring for every UI Object from the top to the bottom and to the left and right of your cell for expansion to work properly. If you think you've taken care of this and the cell still doesn't expand, try posting a screen shot and list of your layout constraints so we can review.

Autolayout scrollview with collectionview

I have been struggling with this for a few days now, and I am looking to see if someone can help me with this AutoLayout problem.
In my iOS7 application, I have a UIView that has a UIScrollView and inside it a UIView(container) with some elements positioned. I have in there, a UIImageView, UITableView, UICollectionView, UITextView and a MapView. There is no height constraint on the UIScrollView and the container UIView. There are no height constraints on the UICollectionView and the UITextView.
What I want to accomplish is
The UITextview should expand to the content size as in all the text should appear without any vertical scrolling enabled for the UITextView.
The UICollectionView should always show all items and there should not be any scrolling enabled there as well.
Overall, I want a UIScrollView with items in it, that scale based on content. I have tried numerous things, but failed.
If anyone has pointers or suggestions on how to go about doing this, it would be very helpful.
OK, I would go about this in a completely different way.
First, get rid of the scrollView completely.
Just use a UICollectionView for this entire interface.
The UICollectionView can take a UIView for a section header. Make this UIView with your UITextView inside. You will need to manually calculate the correct height for your UITextView (and UIView).
Something like...
CGSize size = [theText sizeWithFont:<the font used> constrainedToSize:CGSizeMake(desiredWidth, CGFLOAT_MAX)];
Then just populate your collection view.
By doing this your collection view will control all the scrolling. Because you have set the textview to the correct size in the header you will have all the text there.
This is how I would go about it:
I assume your issue is with the height of the views, that affect the scrolling.
In the textViewDidChange: I would set the frame of the UITextView same as it's contentSize. When they are both the same, scrolling gets disabled.
After populating your UITableView and calling reloadData, I would set it's frame same as it's contentSize.
The mapView (MKMapView, I suppose) has the same frame throughout, I suppose. So you just use it's fixed height. If it changes height, you must store it's changing height each time it changes.
Once you have all the heights, add them up, and set the frame of the outer view same as then combined height of the inner view. Iterate this to all nested views, beginning from innermost views, and moving to outer views.
The catch here is, every time your content changes, the frames have to be resized. Hence changing the frames in textViewDidChange:, after reloadData, etc makes sense.
EDIT : One thing you might want to do first is, getting rid of redundant views. Your view hierarchy seems Rube Goldberg to me. The lesser views you have, the lesser work you will have to do.
Ok.. so I solved this problem by creating a IBOutlet for my NSLayoutConstraint on the UITextView in question.
I simply computed the height and then applied it on the constraint and it worked..
#Fogmeister - Your solution will also work, but it would require me to rewire a whole UI page.. Your approach is definitely a feasible one and shall keep in mind for future iOS apps..

Resources