ios 使用自动布局的 UIScrollView 中的 UITableView

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17334478/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 00:06:17  来源:igfitidea点击:

UITableView within UIScrollView using autolayout

iosuitableviewuiscrollviewautolayoutnslayoutconstraint

提问by Matt Delves

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

目前,我正在使用 aUITableView以及包含在UIScrollView. 我希望UITableView它的高度与其内容高度相同。

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 UIScrollViewis recalculated and the UITableViewalong with other views in the UIScrollVieware displayed correctly.

我已经完成了插入/删除,但目前它没有更新 UIScrollView 这是它的超级视图,以便UIScrollView重新计算的内容大小并正确显示UITableView中的其他视图UIScrollView

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.

我该如何去实现这使UIScrollView的规模进行调整,其内容奠定了正确的当我改变的内容UITableView?我目前正在使用自动布局。

回答by rob mayoff

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 Autolayoutif you haven't already.

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

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 UITableViewthat implements the intrinsicContentSizemethod. The implementation is trivial:

第三,鉴于该技术说明中的信息,我可以想到几种方法来做您想做的事。最干净的可能是创建一个UITableView实现该intrinsicContentSize方法的子类。实现是微不足道的:

@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 invalidateIntrinsicContentSizeto 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 MyTableViewto do that. E.g. do [self invalidateIntrinsicContentSize]in -endUpdates, -reloadData, - insertRowsAtIndexPaths:withRowAnimation:, etc.

您可能需要invalidateIntrinsicContentSize在适当的时间发送到表格视图(当您添加或删除行或更改行的高度时)。您可能只需覆盖适当的方法MyTableView即可做到这一点。例如做[self invalidateIntrinsicContentSize]-endUpdates-reloadData- insertRowsAtIndexPaths:withRowAnimation:,等。

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.

这是我测试中视图控制器的完整源代码。没有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

回答by MuHAOS

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

除了 rob 的回答之外,还有 UITableView 的可自行调整大小的子类的快速示例:

Swift 2.x

斯威夫特 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.xor Swift 4.x

Swift 3.xSwift 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.

我用它把一个表格视图放入另一个自动调整大小的表格视图的单元格中。

回答by Klemen

Here is the obj-C version. It's based on a solution from user @MuHAOS

这是 obj-C 版本。它基于用户@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

回答by mhoeller

@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.

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

@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

回答by KKRocks

you can add view as headerview and footerview of tableview. because the tableview is the subview of scrollview. follow below example.

您可以将视图添加为 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;

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.

并且您还可以使用简单的拖放视图将 tableview 的 headerview 和 footerview 添加到故事板中的 tableview 并获取该视图的 IBOutlet。