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
.
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.
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];
}
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.
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:
My storyboard looks like this:
You may use any way to produce the dynamic content for the mainTableView
and using another class for the childTableView
isn't necessary.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With