16

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.

E-Riddie
  • 14,660
  • 7
  • 52
  • 74

4 Answers4

7

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.

Community
  • 1
  • 1
iphondroid
  • 498
  • 7
  • 19
  • This answer has some points to be reconsidered because they are misleading. The fact that you need to avoid making the properties as IBOutlets it is not true. The example code that you provided crashes on lastObject since it's not the tableView but something else, and also `cellForRowAtIndexPath:`. Anyway there are some points on this that helped me, so I am voting you up. – E-Riddie Aug 18 '15 at 15:08
  • Thanks for the upvote. Regarding creating an IBOutlet, I didn't mean to avoid creating, I meant calling the `cellForRowAtIndexPath:` of the IBOutlet UITableView in the first line of the `heightForRowAtIndexPath` doesn't work. You can always use IBOutlets wherever you want. Regarding the crash, can you tell me where and what is causing the crash because I ran it again and found it to be working. – iphondroid Aug 19 '15 at 09:04
2

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.

Jakub
  • 13,712
  • 17
  • 82
  • 139
  • Like was mentioned in the comments, this method gives EXC_BAD_ACCESS under `cellForRowAtIndexPath:`. – E-Riddie Aug 14 '15 at 12:09
  • Works for me, but you can feel the damage it does to the performance When try to add new cells in the tableview. Likewise it is not advisable to access a cell in `heightForRowAtIndexPath` – jose920405 Feb 18 '16 at 17:12
1

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

ahmedHegazi
  • 190
  • 1
  • 14
  • I am able to control separately the tableViews where the main tableView handling is done inside ViewController, meanwhile the embedded one is handled on the customCell. This is not the case, the case is that iOS 8 made some major changes when added automatic height. – E-Riddie Aug 13 '15 at 13:03
1

I set another response, i set everything completely dynamic and cannot reproduce your problem: Here is a repository: https://bitbucket.org/Kettu/dynamiccellfrorow. What i do is call reload data when i awakeForNib that calculates all rows in a fly and final value can be found like in iOS 7 under scrollView contentSize.

Please note since number of rows and height are completely random in this demo project this will reload and flicker every time cell will be reusable. Shouldn't happen on real data.

Jakub
  • 13,712
  • 17
  • 82
  • 139
  • Yes but on the method you provided it has `[tableView dequeueReusableCellWithIdentifier:CellIdentifier]` but mine is `[tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]`. Changes are here: http://stackoverflow.com/questions/25826383/when-to-use-dequeuereusablecellwithidentifier-vs-dequeuereusablecellwithidentifi Also to mention that the size here doesn't get calculated correctly, as I am using `UITableViewAutomaticDimension`, it's the same where I am stuck. If you want I can provide your project by my modified data back. – E-Riddie Aug 14 '15 at 14:31
  • This was a good example, I solved it by using this project as a reference, so this one. So I am giving you the bounty. :) – E-Riddie Aug 18 '15 at 15:05