Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get reference of cell under heightForRowAtIndexPath: in iOS 8

I am currently working on a project where I have embedded a UITableView inside UITableViewCell.

What I need to do is to disable the UITableView's scroll and make the UITableView to fit size of all rows. But as the UITableView inherits from UIScrollView, the use of Autolayout does not force UITableView to make the height of cell depending on its contentSize (not frame) when returning UITableViewAutomaticDimension.

iOS 7 Solution

This was easy achievable until iOS 7, as I get the reference of the cell under heightForRowAtIndexPath: using the code below:

UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
int height = cell.tableView.contentSize.height;

return height;

but in iOS 8 it gives BAD_ACCESS as iOS 8 calls the heightForRowAtIndexPath: before the cellForRowAtIndexPath: has been called.

iOS 8 Approach

Declare a property to keep reference of the cell:

@property (strong, nonatomic) UITableViewCell *prototypeCell

Use a method to save the current cell reference to the property in order to use it:

- (id)prototypeCellatIndexPath:(NSIndexPath *)indexPath {
    NSString *cellID = @"MyCell";

    if (!_prototypeCell) {
        _prototypeCell = [self.tableView dequeueReusableCellWithIdentifier:cellID];
    }

    return _prototypeCell;
}

Get UITableView of the UITableViewCell from the prototype and from its contentSize I get the height and I return it under heighForRowAtIndexPath: from the method below:

-(int)heightForThreadAtIndexPath:(NSIndexPath *)indexPath {
    _prototypeCell = [self prototypeCellatIndexPath:indexPath];

    [_prototypeCell.contentView setNeedsLayout];
    [_prototypeCell.contentView layoutIfNeeded];

    int footer = [_prototypeCell.tableView numberOfSections]*_prototypeCell.tableView.sectionFooterHeight;
    int header = [_prototypeCell.tableView numberOfSections]*_prototypeCell.tableView.sectionHeaderHeight;

    int height = ceilf(_prototypeCell.tableView.contentSize.height) + _prototypeCell.tableView.contentOffset.y + _prototypeCell.tableView.contentInset.bottom + _prototypeCell.tableView.contentInset.top + header + footer;
    NSLog(@"%i, %i", (int)ceilf(_prototypeCell.tableView.contentSize.height), height);

    return height;
}

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return [self heightForThreadAtIndexPath:indexPath];
}

Problem

The contentSize.height that I get back from the prototypeCell is wrong, and it doesn't match the real contentSize of the UITableView, but when I log the real contentSize under CustomCell Class it shows the correct contentSize, which differs from the one under prototypeCell.

This makes me wondering that maybe I should try to dequeue the cell at a specific state in order to get the correct contentSize, but logs shows the same values.

I have been researching a lot and trying different ideas, but none worked so far. I don't know if anyone has tried to achieve a similar thing as me, and solved this. It will be really nice if you provide me an idea or something.

like image 995
E-Riddie Avatar asked Aug 06 '15 11:08

E-Riddie


3 Answers

As you said the heightForRowAtIndexPath delegate method will not give you the dynamic height of the rows when it gets called automatically. Instead, you must explicitly call it as: [self delegateMethod] i.e. [self tableView:tableView cellForRowAtIndexPath:indexPath];

If you have your main tableView declared as an IBOutlet like myTableView, then even calling [self.myTableView cellForRowAtIndexPath:indexPath] will not work!!

I have tested the code and this is working for me:

Inside MainTableViewController.m:

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
  return 7;
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"tableViewCellMain"];
  if (cell == nil) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"tableViewCellMain"];
  }

  UITableView *tableViewChild = [[UITableView alloc] initWithFrame:CGRectMake(cell.frame.origin.x, cell.frame.origin.y, tableView.frame.size.width, tableView.frame.size.height) style:UITableViewStylePlain];
  [self.cls setNumberOfRows:indexPath.row+1];
  [tableViewChild setDelegate:self.cls];
  [tableViewChild setDataSource:self.cls];
  [tableViewChild setSeparatorStyle:UITableViewCellSeparatorStyleNone];
  [tableViewChild setScrollEnabled:NO];
  [tableViewChild reloadData];
  [cell addSubview:tableViewChild];
  return cell;
}

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
  UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
  CGFloat height = cell.frame.size.height;
  for (int i=0; i<cell.subviews.count; i++) {
    UITableView *childTableView = (UITableView*) [cell.subviews lastObject];
    height = childTableView.contentSize.height;
  }

  NSLog(@"%f",height);
  return height;
}

I have set another class as the delegate for the childTableView to get its data.

Inside ChildTableViewController.m:

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
  return self.numberOfRows;
}

-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"tableViewCellChild"];
  if (cell == nil) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"tableViewCellChild"];
  }
  [cell.textLabel setText:[NSString stringWithFormat:@"%ld",indexPath.row]];
  UIColor *cellTextColor = [UIColor blackColor];

  switch (indexPath.row)
  {
    case 0: cellTextColor = [UIColor redColor]; break;
    case 1: cellTextColor = [UIColor greenColor]; break;
    case 2: cellTextColor = [UIColor blueColor]; break;
    case 3: cellTextColor = [UIColor magentaColor]; break;
    case 4: cellTextColor = [UIColor purpleColor]; break;
    default: break;
  }
  [cell.textLabel setTextColor:cellTextColor];
  [tableView sizeToFit];
  return cell;
}

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
  return 30;
}

And you will get the working as shown in this image:

UITableView inside rows of UITableView

My storyboard looks like this:

enter image description here

You may use any way to produce the dynamic content for the mainTableView and using another class for the childTableViewisn't necessary.

like image 124
iphondroid Avatar answered Nov 20 '22 16:11

iphondroid


You can use your iOS 7 approach also in iOS 8, the only thing changed there is you cannot use delegate from your heightForRowAtIndexPath: method but call the actual cellForRowAtIndexPath: in your controller, so change line:

UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];

to line:

UITableViewCell* cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];

And should work.

like image 2
Jakub Avatar answered Nov 20 '22 15:11

Jakub


I'm not sure if that help you or not but i had to do something like this before and i end it up to embedded UICollectionView with vertical scrolling in the cell so it act like tableview but other view not UITableView and that make me able to control each one separately from the other

hope this tip help you good luck

like image 1
ahmedHegazi Avatar answered Nov 20 '22 15:11

ahmedHegazi