Self-sizing cells not working on Xcode 10

Summary: I am attempting to use self-sizing cells on iOS 12 / Xcode 10. Unfortunately, they are not working as expected.



Steps to Reproduce: Run attached project on iOS 12. Run attached project on iOS 11.



Expected Results: The cells should be 1/3 of the screen width.



Actual Results: On iOS 11, the cells are 1/3 of the screen width. On iOS 12, they are the width of their prototype cell.



Version/Build: Xcode 10 beta 6, iOS 12 beta 7


Base project attached at rdar://43260298

Replies

So you submitted a radar, which is good, but we don't get to see the radar or the project attached to it, so it's hard to know to help.


I do note, though, that you talk about "self-sizing cells". In the context of a table view, "self-sizing" refers to the height, because the width is tied to the actual width of the table, and adjustments to the width are done with regular autolayout constraints.


How are you constraining the widths? Contraints added in IB? Constraints added in code? Explicit frame setting in code?

Thank you for your help! I uploaded the sample project here if you want to take a look: https://www.dropbox.com/s/tkm82yu20yn4d3s/TestCV.zip?dl=0. Sorry for not specifying - I'm talking about UICollectionView self-sizing cells. I set the width constraint dynamically in UICollectionViewCell.applyLayoutAttributes (you can see that in TestCollectionViewCell).

Well, I think you're just Doing It Wrong™.


I didn't spend very long on it (because debugging collection view layouts makes my head hurt), but I think you're seeing different behavior in iOS 12 because you're incorrectly relying on autolayout being done after you've created the width constraint in each cell, but it's actually being done before. (And it only worked in iOS 11 by accident.)


AFAICT you shouldn't be using autolayout for this at all. By the time you attempt to modify the constraint (TestCollectionViewCell.apply(:)) autolayout is long out of the picture. Instead, the "apply" method should actually apply your custom "cellWidth" attribute by resizing the cell. You don't really need the autolayout constraint for the width at all.


In fact, I tried modifying your code in CustomFlowLayout.layoutAttributesForElements(in:) by changing the line inside the loop from this:


            attribute.cellWidth = self.widthForCollectionView(collectionVIew: collectionView!)


to this:


            let cellWidth = self.widthForCollectionView(collectionVIew: collectionView!)
            attribute.size = CGSize(width: cellWidth, height: attribute.size.height)


(using a standard attribute to set the width instead of your custom attribute, to avoid mucking about with the cell code). After that, the cells seemed to be drawn with the correct width (though at the wrong origin).


So you can either set a standard layout attribute (like "size") to change the width, or you can manually resize the cell (in "apply") to use a custom cellWidth. Either way, I don't think autolayout attributes come into it. More precisely, if you wanted to set the width via an autolayout constraint, you'd have to arrange to customize the constraint much earlier in the layout process.

Interesting - that's not how I understood the lifecycle of self-sizing cells. My understanding was the following:


1. layoutAttributesForElements(in:) is called to get the default layout attributes

2. UICollectionViewCell.applyLayoutAttributes is then called on the cell to apply any custom layout attributes. This gets called immediately after the cell has been added to the UICollectionView according to the documentation:

// Classes that want to support custom layout attributes specific to a given UICollectionViewLayout subclass can apply them here.
// This allows the view to work in conjunction with a layout class that returns a custom subclass of UICollectionViewLayoutAttributes from -layoutAttributesForItem: or the corresponding layoutAttributesForHeader/Footer methods.
// -applyLayoutAttributes: is then called after the view is added to the collection view and just before the view is returned from the reuse queue.
// Note that -applyLayoutAttributes: is only called when attributes change, as defined by -isEqual:.
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes;

3. UICollectionViewCell.preferredLayoutAttributesFittingAttributes is called. This allows the cell to modify the default attributes for a given cell. My understanding was the default implementation of this used autolayout to calculate the final size. I actually saw this when using instruments to see stack traces on iOS 11.

4. The UICollectionViewCell is given their size based on the final attribute returned in preferredLayoutAttributesFittingAttributes.


The timing of these methods works out - it just seems like the default implementation of preferredLayoutAttributesFittingAttributes doesn't make the autolayout calculation. @QuinceyMorris - for self-sizing cells using autolayout, when is the calculation was done? In a few WWDC presentations, they say self-sizing cells using autolayout is supported.

I don't know, you may be right about some of that. The thing is, all those references (in API function names and documentation) to "layout" and "attributes" seems to be talking about applying explicit attributes to cells, and there'd be no point if autolayout was run after those non-auto layout attributes were applied. It would be nightmarish if you could determine a set of layout attributes, but then they weren't honored.


I think it's likely there's a point at which auto-layout does apply, but I believe your code proves it's earlier than you thought.


Anyway, it would certainly be nice to know the real answer to this question.

Yeah you definitely could be correct, I'm certainly no expert. My understanding was the following:


1. apply(:) - A chance for the cell to take custom layoutAttributes and apply them to the cell. The default implementation does nothing so my thinking is this is just for custom use cases.

2. preferredLayouAttributesFitting(:) - This is the last chance for anyone to modify the layoutAttribute before the base attributes are applied to the cell. To me, this is the biggest clue that autolayout can run here - if the system provides a mechanism to change the layoutAttribute one more time, I would think this is where the autolayout calculation gets run. I actually just looked at instruments on iOS 12 simulator and it does still call systemLayoutSizeFittingSize (https://www.dropbox.com/s/2w0dnvr29tjt7i1/Screen%20Shot%202018-08-21%20at%2012.54.10%20PM.png?dl=0 ). However, the new size doesn't seem to be applied to the attribute...


I definitely agree it would be great to get an answer from Apple. Any suggestions on how to contact someone who can help? I noticed TSI tickets are not available for pre-release software.

For everything except size and position, it would make sense for autolayout to run after the attributes are applied, but for size and position that would make no sense.


Yeah, I guess you can't use a TSI, but you can submit a bug report against iOS 12 beta. It may turn out that this behavior is just a bug of some kind.

Is there a way to file a bug against iOS 12 specifically? I filed it against iOS + SDK / UIKit last week but still haven't heard back on it.

So long as you specified the iOS version, I'd expect the bug would get routed to the right place.