UITableView within UIScrollView using autolayout

前端 未结 5 1104
渐次进展
渐次进展 2020-11-28 18:07

At the moment, I\'m using a UITableView along with other views that are contained in a UIScrollView. I want the UITableView to have it

相关标签:
5条回答
  • 2020-11-28 18:35

    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
    
    0 讨论(0)
  • 2020-11-28 18:38

    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.

    0 讨论(0)
  • 2020-11-28 18:40

    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
    
    0 讨论(0)
  • 2020-11-28 18:41

    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.

    0 讨论(0)
  • @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
    
    0 讨论(0)
提交回复
热议问题