Reputation: 1605
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.
Upvotes: 62
Views: 43933
Reputation: 1128
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.
Upvotes: 66
Reputation: 8322
you can add view as headerview and footerview of tableview. because the tableview is the subview of scrollview. follow below example.
UILabel *topLabel = [[UILabel alloc] init];
topLabel.translatesAutoresizingMaskIntoConstraints = NO;
topLabel.text = @"Top Label";
topLabel.backgroundColor = [UIColor redColor];
tableView.tableFooterView = topLabel;
UILabel *footer = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200, 30)];
footer.backgroundColor = [UIColor greenColor];
footer.text = @"Footer";
tableView.tableFooterView = footer;
and also you can add headerview and footerview of tableview using simple drag and drop view to the tableview in storyboard and take IBOutlet of that views.
Upvotes: 0
Reputation: 620
@MuHAOS's and @klemen-zagar's code helped me a lot but actually causes a performance issue by triggering an endless layout loop when the tableview is contained within a stack view which itself is contained in a scroll view. See my solution below.
@interface AutoSizingTableView ()
@property (nonatomic, assign) BOOL needsIntrinsicContentSizeUpdate;
@end
@implementation AutoSizingTableView
- (void)setContentSize:(CGSize)contentSize
{
[super setContentSize:contentSize];
self.needsIntrinsicContentSizeUpdate = YES;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!self.needsIntrinsicContentSizeUpdate) {
return;
}
self.needsIntrinsicContentSizeUpdate = NO;
[self layoutIfNeeded];
[self invalidateIntrinsicContentSize];
});
}
- (CGSize)intrinsicContentSize
{
return CGSizeMake(UIViewNoIntrinsicMetric, self.contentSize.height);
}
@end
Upvotes: 2
Reputation: 2182
Here is the obj-C version. It's based on a solution from user @MuHAOS
@implementation SizedTableView
- (void)setContentSize:(CGSize)contentSize {
[super setContentSize:contentSize];
[self invalidateIntrinsicContentSize];
}
- (CGSize)intrinsicContentSize {
[self layoutIfNeeded]; // force my contentSize to be updated immediately
return CGSizeMake(UIViewNoIntrinsicMetric, self.contentSize.height);
}
@end
Upvotes: 12
Reputation: 385610
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:
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
Upvotes: 102