在UIScrollView中使用自动布局的UITableView

目前,我正在使用一个UITableView以及包含在UIScrollView其他视图。 我希望UITableView的高度与内容高度相同。

为了使事情复杂化,我还插入/删除行以提供手风琴效果,以便当用户点击一行时,将显示该行的更多详细信息。

我已经完成了插入/删除操作,尽pipe目前它并没有更新它的超级视图的UIScrollView,所以重新计算了UIScrollView的内容大小,并且UITableViewUIScrollView中的其他视图显示正确。

我怎么能去实现这个,以便UIScrollView的大小调整,并且当我改变UITableView的内容时,它的内容正确地布置了? 我目前正在使用自动布局。

首先,那些其他的观点(表格视图的兄弟姐妹)严格地在表格视图的上方和下方? 如果是这样,你有没有考虑让表视图正常滚动,并将这些外部视图在表视图的页眉和页脚视图? 那么你不需要滚动视图。

其次,如果您还没有阅读技术说明TN2154:UIScrollView和Autolayout ,可能需要阅读。

第三,考虑到技术说明中的信息,我可以想出几个方法来做你想做的事情。 最干净的可能是创build一个实现了intrinsicContentSize方法的UITableView的子类。 实施是微不足道的:

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

然后,让自动布局使用表视图的内在内容大小。 在滚动视图的子视图(包括表格视图)之间创build约束以将其展开,并确保滚动视图的所有四个边都有约束。

您可能需要在适当的时间(在添加或删除行或更改行高时)将invalidateIntrinsicContentSize发送到表视图。 你可能可以重写MyTableView适当的方法来做到这一点。 例如在-endUpdates-reloadData- insertRowsAtIndexPaths:withRowAnimation:等中的[self invalidateIntrinsicContentSize]

这是我testing的结果:

滚动视图中具有内在内容大小的表格视图

滚动视图具有淡蓝色背景。 滚动视图中的红色标签和蓝色底部标签是表视图的兄弟。

这是testing中视图控制器的完整源代码。 没有xib文件。

 #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 

除了rob的答案,还有一个UITableView的自调整子类的快速示例:

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

我用它把表视图放到另一个可自动resize的表格视图的单元格中。

这是obj-C版本。 它基于来自用户@MuHAOS的解决scheme

 @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 

@ MuHAOS和@ klemen-zagar的代码帮了我很多,但是当tableview包含在一个本身包含在滚动视图中的堆栈视图中时,通过触发一个无尽的布局循环,实际上会导致性能问题。 请参阅下面的解决scheme

 @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 

您可以将视图添加为tableview的headerview和footerview。 因为tableview是scrollview的子视图。 按照下面的例子。

 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; 

也可以添加tableview的headerview和footerview使用简单的拖放视图到故事板中的tableview,并把这个视图的IBOutlet。