Set UITableView Cell height (contain dynamic UICollectionView) - uitableview

MY GOAL:
Each UITableView Cell Contain 1 UIColletionViewController (dynamic items count)
PROBLEM: UITableView 'HeightForRow's delegate, is call before UIColletionViewController finish load his view + height.
RESULTS:
UITableView 'HeightForRow = 0
How can i set the UITableView "HeightForRow" delegate correctly?
the UIColletionViewController's content height haven't loaded yet
the result should be like in the following draw:
ALREAD TRY:
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.estimatedRowHeight = 300.0;
self.tableView.rowHeight = UITableViewAutomaticDimension;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
//not working well
return UITableViewAutomaticDimension;
//also tried this:
ResultsTableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
Object * object = [_array objectAtIndex:indexPath.section];
[cell setCellObject:object]; //this is build the cell ui
return cell.collection.rect.size.height;
}

return the "content size" (instead of view.size)
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
//1. get the cell (by identifier)
ResultsTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ResultsTableViewCell"];
//2. build cell ui
Object * object = [_array objectAtIndex:indexPath.section];
[cell buildCell:object];
//3. return content size
return cell.collection.collectionView.contentSize.height;
}

Related

Hiding a UILabel from a UITableViewCell doesn't resize the contentView

I'm trying to create a dynamic UITableView where a cell can expand/collapse as the user selects the cell.
- (void)setUpCell:(DynamicTableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
cell.label.text = [self.dataSource objectAtIndex:indexPath.row];
cell.secondLabel.text = [self.dataSource objectAtIndex:self.dataSource.count - indexPath.row - 1];
if ([self.isVisible[indexPath.row] isEqual:#NO]) {
cell.secondLabel.hidden = YES;
} else {
cell.secondLabel.hidden = NO;
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.dataSource.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
DynamicTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
[self setUpCell:cell atIndexPath:indexPath];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
DynamicTableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if ([self.isVisible[indexPath.row] isEqual: #YES]) {
self.isVisible[indexPath.row] = #NO;
cell.secondLabel.hidden = YES;
} else {
self.isVisible[indexPath.row] = #YES;
cell.secondLabel.hidden = NO;
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
static DynamicTableViewCell *cell = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
});
[self setUpCell:cell atIndexPath:indexPath];
return [self calculateHeightForConfiguredSizingCell:cell];
}
- (CGFloat)calculateHeightForConfiguredSizingCell:(DynamicTableViewCell *)sizingCell {
[sizingCell layoutIfNeeded];
CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height;
}
I forked this project and have the test code here.
Once the cell has been sized it does not change when selecting the cell, only hides/shows the content of the cell. I've tried replacing the explicit size calculation with UITableViewAutomaticDimension. Also tried reloading the cell. Seems once the cell size has been calculated, it does not change.
Any suggestions as to what to try would be greatly appreciated!
In iOS development a view never collapses if you set the hidden property to true.
Instead you should use autolayout. Assuming your view has two labels vertically stacked on top of each other, pin the first label to the cells contentView's top, give it a height constraint, pin the second label's top to the first labels bottom, pin the second labels bottom to the cell's contentView bottom. Set the height of the second label, and save this constraint in a variable, lets called it secondLabelHeightConstraint, now you can collapse and expand the cell by setting the value of secondLabelHeightConstraint to 0 or what ever value you would like.

Why tableview's delegate method heightForRowAtIndexPath: executes one more time?

my platform is ios8 and xcode 6.3.1
tableview's delegate like this:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 3;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1;
}
so, the delegate of heightForRowAtIndexPath: should be execute three times , but my code execute four, why ?
My code :
init tableView
- (void)setupTableView {
_selectTableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
_selectTableView.delegate = self;
_selectTableView.dataSource = self;
_selectTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
[self.view addSubview:_selectTableView];
}
other delegate method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSInteger section = indexPath.section;
static NSString *identified = #"selectCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identified];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identified];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
return [self cellWith:cell andSection:section];
}
- (UITableViewCell *)cellWith:(UITableViewCell *)cell andSection:(NSInteger)section {
....
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
CGFloat height = 0;
if (section != SVCellTypeHot) {
height = 5;
}
return height;
}
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
UIView *footerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, kScreenSize.width, 5)];
[footerView setBackgroundColor:[UIColor lightGrayColor]];
return footerView;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat height = 0;
switch (indexPath.section) {
case SVCellTypeBanner:
{
height = kHeaderViewHeigth;
}
break;
case SVCellTypeRecommand:
{
height = kRecommandViewHeight;
}
break;
case SVCellTypeHot:
{
height = kHotViewHeight;
}
break;
default:
break;
}
return height;
}
heightForRowAtIndexPath allows the delegate to specify rows with varying heights. If this method is implemented, the value it returns overrides the value specified for the rowHeight property of UITableView for the given row. There is no guarentee that this method can only be called 'section count * item count' times in the UITableView. As you can tell from its name, it will calculate the height for the cells at IndexPath, so combining the re-using technique, this method will be called many times, as long as it needs to calculate the height for cell at IndexPath
So actually, it is a system behaviour to decide how many times it should be called and when. In your comment, it seems like something changed in indexPath {1-0} so heightForRowAtIndexPath is called twice for {1-0}. You might need to check have you changed any content that cause iOS to re-calculate the cell's height.
Without knowing more details, this is the best we can do to provide you some clues to debug. However, you should not rely on how many times it calls heightForRowAtIndexPath, again, this can be called at any time, as long as you scroll or change any frame inside that cell
heightForRowAtIndexPath: will execute as many times as it needs to. If you are scrolling, for example, it will execute as offscreen cells are about to come onscreen. That method should always be able to provide the correct height and you normally shouldn't be concerned with how often it's called. cellForRowAtIndexPath: executes 3 times as it should.

Make UILabel Fit Height in a Dynamic Sized UITableViewCell

I have the following setup in my app:
I have a UITableView which has animated height change, when a cell is selected. In the cell is I have a label, which text I want to be on one line with truncation, when the cell is not selected and then when it is selected (the cell height get's bigger) I want the text (UILabel) to be on multiple lines, based on the text.
This is my code at the moment:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if(self.selectedIndex != nil && [self.selectedIndex isEqual:indexPath]) {
return kCellHeight * 3.9;
}
return kCellHeight;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:NO];
ProductCell *cell = (ProductCell *) [tableView cellForRowAtIndexPath:indexPath];
if (![indexPath isEqual:self.selectedIndex]) {
cell.addButton.hidden = NO;
cell.descLabel.numberOfLines = 0;
cell.descLabel.lineBreakMode = NSLineBreakByWordWrapping;
[cell.descLabel sizeToFit];
tableView.scrollEnabled = NO;
self.selectedIndex = indexPath;
}else {
self.selectedIndex = nil;
cell.descLabel.lineBreakMode = NSLineBreakByTruncatingTail;
tableView.scrollEnabled = YES;
cell.addButton.hidden = YES;
}
[tableView beginUpdates];
[tableView endUpdates];
}
The first part (when the cell is selected, expanded and the height gets bigger) the text fits as I want, but the second part (when the cell is contracted, and the cell height get back to the normal height) the text does not fit to the new height and is truncated as I want it to, it just stays multi lined. How can I fix this? So when the cell is selected the text gets multilined and when the cell is deselected the text gets back to its normal truncated state?
EDIT:
Override
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
and add below line in this function
cell.descLabel.numberOfLines = 1;

IOS SearchDisplayController with Custom Cell StoryBoard

I have a cell prototype in storyboard. So I custom the height, subview (Labels and Images).
But eventually the cell does not appear to be used for SearchDisplayController...
Code snippet >>
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
#warning Reusable cell not working for custom cell.
ItemCell *cell = [tableView dequeueReusableCellWithIdentifier:itemCell];
if (cell == nil) {
cell = [[ItemCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:itemCell];
}
if (tableView == self.searchDisplayController.searchResultsTableView){
cell.itemLabel.text = [_filteredList objectAtIndex:indexPath.row];
cell.priceLabel.text = #"RM 7.00";
// TODO : Insert Image Here
} else {
cell.itemLabel.text = [_items objectAtIndex:indexPath.row];
cell.priceLabel.text = #"RM 7.00";
// TODO : Insert Image Here
}
return cell;}
I'm out of idea. Even if I use self.tableView instead of tableView it shows something like this.
add the following code before cell for row method.
- (void)searchDisplayController:(UISearchDisplayController *)controller didLoadSearchResultsTableView:(UITableView *)tableView
{
tableView.rowHeight = 24.0f; // this should be the same hight as the re usable cell you implemented
}
Then when the table view loads and search is performed the cell hight of the searchcontroller and the tableview would be the same.
you should make further changes using this method, for example:
-(BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
[controller.searchResultsTableView setBackgroundColor:[UIColor colorWithRed:0xED/255.0 green:0xED/255.0 blue:0xED/255.0 alpha:1.0]];
controller.searchResultsTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
return YES;
}

Retrieve custom prototype cell height from storyboard?

When using "Dynamic Prototypes" for specifying UITableView content on the storyboard, there is a "Row Height" property that can be set to Custom.
When instantiating cells, this custom row height is not taken into account. This makes sense, since which prototype cell I use is decided by my application code at the time when the cell is to be instantiated. To instantiate all cells when calculating layout would introduce a performance penalty, so I understand why that cannot be done.
The question then, can I somehow retrieve the height given a cell reuse identifier, e.g.
[myTableView heightForCellWithReuseIdentifier:#"MyCellPrototype"];
or something along that line? Or do I have to duplicate the explicit row heights in my application code, with the maintenance burden that follows?
Solved, with the help of #TimothyMoose:
The heights are stored in the cells themselves, which means the only way of getting the heights is to instantiate the prototypes. One way of doing this is to pre-dequeue the cells outside of the normal cell callback method. Here is my small POC, which works:
#import "ViewController.h"
#interface ViewController () {
NSDictionary* heights;
}
#end
#implementation ViewController
- (NSString*) _reusableIdentifierForIndexPath:(NSIndexPath *)indexPath
{
return [NSString stringWithFormat:#"C%d", indexPath.row];
}
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(!heights) {
NSMutableDictionary* hts = [NSMutableDictionary dictionary];
for(NSString* reusableIdentifier in [NSArray arrayWithObjects:#"C0", #"C1", #"C2", nil]) {
CGFloat height = [[tableView dequeueReusableCellWithIdentifier:reusableIdentifier] bounds].size.height;
hts[reusableIdentifier] = [NSNumber numberWithFloat:height];
}
heights = [hts copy];
}
NSString* prototype = [self _reusableIdentifierForIndexPath:indexPath];
return [heights[prototype] floatValue];
}
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 3;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString* prototype = [self _reusableIdentifierForIndexPath:indexPath];
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:prototype];
return cell;
}
#end
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.
I created a category for UITableView some time ago that may come helpful for this. It stores 'prototype' cells using asociated objects for reusing the prototypes and provides a convenience method for obtaining the height of the row assigned in storyboard. The prototypes are released when the table view is deallocated.
UITableView+PrototypeCells.h
#import <UIKit/UIKit.h>
#interface UITableView (PrototypeCells)
- (CGFloat)heightForRowWithReuseIdentifier:(NSString*)reuseIdentifier;
- (UITableViewCell*)prototypeCellWithReuseIdentifier:(NSString*)reuseIdentifier;
#end
UITableView+PrototypeCells.m
#import "UITableView+PrototypeCells.h"
#import <objc/runtime.h>
static char const * const key = "prototypeCells";
#implementation UITableView (PrototypeCells)
- (void)setPrototypeCells:(NSMutableDictionary *)prototypeCells {
objc_setAssociatedObject(self, key, prototypeCells, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSMutableDictionary *)prototypeCells {
return objc_getAssociatedObject(self, key);
}
- (CGFloat)heightForRowWithReuseIdentifier:(NSString*)reuseIdentifier {
return [self prototypeCellWithReuseIdentifier:reuseIdentifier].frame.size.height;
}
- (UITableViewCell*)prototypeCellWithReuseIdentifier:(NSString*)reuseIdentifier {
if (self.prototypeCells == nil) {
self.prototypeCells = [[NSMutableDictionary alloc] init];
}
UITableViewCell* cell = self.prototypeCells[reuseIdentifier];
if (cell == nil) {
cell = [self dequeueReusableCellWithIdentifier:reuseIdentifier];
self.prototypeCells[reuseIdentifier] = cell;
}
return cell;
}
#end
Usage
Obtaining the static height set in storyboard is as simple as this:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return [tableView heightForRowWithReuseIdentifier:#"cellIdentifier"];
}
Assuming a multi-section table view:
enum {
kFirstSection = 0,
kSecondSection
};
static NSString* const kFirstSectionRowId = #"section1Id";
static NSString* const kSecondSectionRowId = #"section2Id";
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat height = tableView.rowHeight; // Default UITableView row height
switch (indexPath.section) {
case kFirstSection:
height = [tableView heightForRowWithReuseIdentifier:kFirstSectionRowId];
break;
case kSecondSection:
height = [tableView heightForRowWithReuseIdentifier:kSecondSectionRowId];
}
return height;
}
And finally if the row height is dynamic:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
id thisRowData = self.allData[indexPath.row]; // Obtain the data for this row
// Obtain the prototype cell
MyTableViewCell* cell = (MyTableViewCell*)[self prototypeCellWithReuseIdentifier:#"cellIdentifier"];
// Ask the prototype cell for its own height when showing the specified data
return [cell heightForData:thisRowData];
}

Resources