Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UITableView within UIScrollView using autolayout

At the moment, I'm using a UITableView along with other views that are contained in a UIScrollView. I want the UITableView to have its height to be the same as its content height.

To complicate things, I'm also inserting / deleting rows to provide an accordion effect so that when the user taps on a row, it will show more detail for that row.

I've got the insert / deletion done, though at the moment it doesn't update the UIScrollView which is its superview so that the content size of the UIScrollView is recalculated and the UITableView along with other views in the UIScrollView are displayed correctly.

How can I go about implementing this so that UIScrollView's size is adjusted and its contents laid out correctly when I change the content of the UITableView? I'm currently using auto layout.

like image 954
Matt Delves Avatar asked Jun 27 '13 04:06

Matt Delves


2 Answers

First of all, are those other views (siblings of the table view) strictly above and below the table view? If so, have you considered letting the table view scroll normally, and putting those outside views in the table view's header and footer views? Then you don't need the scroll view.

Second, you may want to read Technical Note TN2154: UIScrollView And Autolayout if you haven't already.

Third, given the information in that tech note, I can think of a few ways to do what you want. The cleanest is probably to create a subclass of UITableView that implements the intrinsicContentSize method. The implementation is trivial:

@implementation MyTableView  - (CGSize)intrinsicContentSize {     [self layoutIfNeeded]; // force my contentSize to be updated immediately     return CGSizeMake(UIViewNoIntrinsicMetric, self.contentSize.height); }  @end 

Then just let auto layout use the table view's intrinsic content size. Create the constraints between the subviews of the scroll view (including the table view) to lay them out, and make sure there are constraints to all four edges of the scroll view.

You probably need to send invalidateIntrinsicContentSize to the table view at appropriate times (when you add or remove rows or change the heights of rows). You could probably just override the appropriate methods in MyTableView to do that. E.g. do [self invalidateIntrinsicContentSize] in -endUpdates, -reloadData, - insertRowsAtIndexPaths:withRowAnimation:, etc.

Here's the result of my testing:

table view with intrinsic content size in scroll view

The scroll view has the light blue background. The red top label and the blue bottom label are siblings of the table view inside the scroll view.

Here's the complete source code for the view controller in my test. There's no xib file.

#import "ViewController.h" #import "MyTableView.h"  @interface ViewController () <UITableViewDataSource, UITableViewDelegate>  @end  @implementation ViewController  - (void)loadView {     UIView *view = [[UIView alloc] init];     self.view = view;      UIScrollView *scrollView = [[UIScrollView alloc] init];     scrollView.translatesAutoresizingMaskIntoConstraints = NO;     scrollView.backgroundColor = [UIColor cyanColor];     [view addSubview:scrollView];      UILabel *topLabel = [[UILabel alloc] init];     topLabel.translatesAutoresizingMaskIntoConstraints = NO;     topLabel.text = @"Top Label";     topLabel.backgroundColor = [UIColor redColor];     [scrollView addSubview:topLabel];      UILabel *bottomLabel = [[UILabel alloc] init];     bottomLabel.translatesAutoresizingMaskIntoConstraints = NO;     bottomLabel.text = @"Bottom Label";     bottomLabel.backgroundColor = [UIColor blueColor];     [scrollView addSubview:bottomLabel];      UITableView *tableView = [[MyTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];     tableView.translatesAutoresizingMaskIntoConstraints = NO;     tableView.dataSource = self;     tableView.delegate = self;     [scrollView addSubview:tableView];      UILabel *footer = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200, 30)];     footer.backgroundColor = [UIColor greenColor];     footer.text = @"Footer";     tableView.tableFooterView = footer;      NSDictionary *views = NSDictionaryOfVariableBindings(         scrollView, topLabel, bottomLabel, tableView);     [view addConstraints:[NSLayoutConstraint         constraintsWithVisualFormat:@"V:|[scrollView]|"         options:0 metrics:nil views:views]];     [view addConstraints:[NSLayoutConstraint         constraintsWithVisualFormat:@"H:|[scrollView]|"         options:0 metrics:nil views:views]];     [view addConstraints:[NSLayoutConstraint         constraintsWithVisualFormat:@"V:|[topLabel][tableView][bottomLabel]|"         options:0 metrics:nil views:views]];     [view addConstraints:[NSLayoutConstraint         constraintsWithVisualFormat:@"H:|[topLabel]|"         options:0 metrics:nil views:views]];     [view addConstraints:[NSLayoutConstraint         constraintsWithVisualFormat:@"H:|-8-[tableView]-8-|"         options:0 metrics:nil views:views]];     [view addConstraint:[NSLayoutConstraint         constraintWithItem:tableView attribute:NSLayoutAttributeWidth         relatedBy:NSLayoutRelationEqual         toItem:view attribute:NSLayoutAttributeWidth         multiplier:1 constant:-16]];     [view addConstraints:[NSLayoutConstraint         constraintsWithVisualFormat:@"H:|[bottomLabel]|"         options:0 metrics:nil views:views]]; }  - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {     return 20; }  - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];     if (!cell) {         cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"];     }     cell.textLabel.text = [NSString stringWithFormat:@"Row %d", indexPath.row];     return cell; }  @end 
like image 111
rob mayoff Avatar answered Oct 06 '22 15:10

rob mayoff


In addition to rob's answer there is swift example of self-resizable subclass of UITableView:

Swift 2.x

class IntrinsicTableView: UITableView {      override var contentSize:CGSize {         didSet {             self.invalidateIntrinsicContentSize()         }     }       override func intrinsicContentSize() -> CGSize {         self.layoutIfNeeded()         return CGSizeMake(UIViewNoIntrinsicMetric, contentSize.height)     }  } 

Swift 3.x or Swift 4.x

class IntrinsicTableView: UITableView {      override var contentSize:CGSize {         didSet {             self.invalidateIntrinsicContentSize()         }     }      override var intrinsicContentSize: CGSize {         self.layoutIfNeeded()         return CGSize(width: UIViewNoIntrinsicMetric, height: contentSize.height)     }  } 

I have used it to put a table view into another auto-resizable table view's cell.

like image 24
MuHAOS Avatar answered Oct 06 '22 17:10

MuHAOS