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 validcell.traitCollection
, andcell.traitCollection
isreadonly
.
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:
Create a custom subclass of
UITableViewCell
for the cell (which you probably did already).In the custom subclass, add a
- (UITraitCollection *)traitCollection
method, which overrides the getter of thetraitCollection
property. Now, you can return any validUITraitCollection
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)verticalSizeClassOr, 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
Why Is Uicollectionviewcell's Outlet Nil
iPhone Ad Hoc Build Using Xcode 4
Uiapplication.Registerforremotenotifications() Must Be Called from Main Thread Only
How to Change Text on a Back Button
iOS Autolayout with Uiscrollview: Why Does Content View of Scroll View Not Fill the Scroll View
How to Display a Uipopoverview as a Annotation to the Map View? (iPad)
Difference Between _ and Self. in Objective-C
Minimum and Maximum Date in Uidatepicker
iPhone Read Uiimage (Frames) from Video with Avfoundation
What Is Prefix.Pch File in Xcode
Possible to Handle Your Own Http Url Schemes in iOS
Warning: the Copy Bundle Resources Build Phase Contains This Target's Info.Plist File
How to Change Uibutton Image in Swift
How to Turn the iPhone Camera Flash On/Off
Xcode 8 "The Aps-Environment Entitlement Is Missing from the App's Signature" on Submit
How to Convert an Int to Hex String in Swift