Offscreen Uitableviewcells (For Size Calculations) Not Respecting Size Class

Offscreen UITableViewCells (for size calculations) not respecting size class?


Update on Dec 2015:

Apple now discourages overriding -traitCollection. Please consider using other workarounds. From the doc:

IMPORTANT


Use the traitCollection property directly. Do not override it. Do not provide a custom implementation.


Original Answer:

The existing answer is great. It explained that the problem is that:

  • -dequeueReusableCellWithIdentifier: returns a cell without a valid cell.traitCollection, and
  • cell.traitCollection is readonly.

The proposed workaround is to temporarily add the cell to the table view. However, this does NOT work if we are in, say, -viewDidLoad, in which the traitCollection of the table view, or the view of the view controller, or even the view controller itself, is not valid yet.

Here, I propose another workaround, which is to override traitCollection of the cell. To do so:

  1. Create a custom subclass of UITableViewCell for the cell (which you probably did already).

  2. In the custom subclass, add a - (UITraitCollection *)traitCollection method, which overrides the getter of the traitCollection property. Now, you can return any valid UITraitCollection you like. Here's a sample implementation:

    // Override getter of traitCollection property
    // https://stackoverflow.com/a/28514006/1402846
    - (UITraitCollection *)traitCollection
    {
    // Return original value if valid.
    UITraitCollection* originalTraitCollection = [super traitCollection];
    if(originalTraitCollection && originalTraitCollection.userInterfaceIdiom != UIUserInterfaceIdiomUnspecified)
    {
    return originalTraitCollection;
    }

    // Return trait collection from UIScreen.
    return [UIScreen mainScreen].traitCollection;
    }

    Alternatively, you can return a suitable UITraitCollection created using any one of its create methods, e.g.:

    + (UITraitCollection *)traitCollectionWithDisplayScale:(CGFloat)scale
    + (UITraitCollection *)traitCollectionWithTraitsFromCollections:(NSArray *)traitCollections
    + (UITraitCollection *)traitCollectionWithUserInterfaceIdiom:(UIUserInterfaceIdiom)idiom
    + (UITraitCollection *)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass
    + (UITraitCollection *)traitCollectionWithVerticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass

    Or, you can even make it more flexible by doing this:

    // Override getter of traitCollection property
    // https://stackoverflow.com/a/28514006/1402846
    - (UITraitCollection *)traitCollection
    {
    // Return overridingTraitCollection if not nil,
    // or [super traitCollection] otherwise.
    // overridingTraitCollection is a writable property
    return self.overridingTraitCollection ?: [super traitCollection];
    }

This workaround is compatible with iOS 7 because the traitCollection property is defined in iOS 8+, and so, in iOS 7, no one will call its getter, and thus our overriding method.

Can I load a UITableViewCell directly from a xib with a specific size class or trait collection?

After writing this I found this question: Offscreen UITableViewCells (for size calculations) not respecting size class? which seems to ask a similar thing, and the answer in there worked for me (add the offscreen table view cell as a subview of the tableview, or some other view that provides a trait environment). It's not pretty but it seems to work out.

Retrieve custom prototype cell height from storyboard?

For static (non-data-driven) height, you can just dequeue the cell once and store the height:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSNumber *height;
if (!height) {
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"MyCustomCell"];
height = @(cell.bounds.size.height);
}
return [height floatValue];
}

For dynamic (data-driven) height, you can store a prototype cell in the view controller and add a method to the cell's class that calculates the height, taking into account the default content of the prototype instance, such as subview placement, fonts, etc.:

- (MyCustomCell *)prototypeCell
{
if (!_prototypeCell) {
_prototypeCell = [self.tableView dequeueReusableCellWithIdentifier:@"MyCustomCell"];
}
return _prototypeCell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Data for the cell, e.g. text for label
id myData = [self myDataForIndexPath:indexPath];

// Prototype knows how to calculate its height for the given data
return [self.prototypeCell myHeightForData:myData];
}

Of course, if you're using custom height, you probably have multiple cell prototypes, so you'd store them in a dictionary or something.

As far as I can tell, the table view doesn't attempt to reuse the prototype, presumably because it was dequeued outside of cellForRowAtIndexPath:. This approach has worked very well for us because it allows the designer to modify cells layouts in the storyboard without requiring any code changes.

Edit: clarified the meaning of sample code and added an example for the case of static height.



Related Topics



Leave a reply



Submit