it-swarm.com.ru

UITableView в UIScrollView с использованием автоматического размещения

В настоящее время я использую UITableView вместе с другими представлениями, содержащимися в UIScrollView. Я хочу, чтобы UITableView имела высоту, равную его высоте содержимого.

Чтобы усложнить ситуацию, я также вставляю/удаляю строки, чтобы обеспечить эффект аккордеона, чтобы при нажатии пользователем строки она отображала больше деталей для этой строки.

Я сделал вставку/удаление, хотя в данный момент он не обновляет UIScrollView, который является его суперпредставлением, так что размер содержимого UIScrollView пересчитывается и UITableView вместе с другими представлениями в UIScrollView отображаются правильно.

Как я могу реализовать это так, чтобы размер UIScrollView корректировался, а его содержимое корректно отображалось при изменении содержимого UITableView? Я в настоящее время использую автоматическую разметку.

51
Matt Delves

Прежде всего, находятся ли эти другие представления (родственные элементы табличного представления) строго выше и ниже табличного представления? Если это так, рассматривали ли вы возможность прокрутки табличного представления в обычном режиме и помещения этих внешних представлений в представления верхнего и нижнего колонтитула представления таблицы? Тогда вам не нужно представление прокрутки.

Во-вторых, вы можете прочитать Техническое примечание TN2154: UIScrollView и Autolayout , если вы еще этого не сделали.

В-третьих, учитывая информацию в этой технической записке, я могу придумать несколько способов сделать то, что вы хотите. Самым чистым, вероятно, является создание подкласса UITableView, который реализует метод intrinsicContentSize. Реализация тривиальна:

@implementation MyTableView

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

@end

Тогда просто позвольте автоматическому макету использовать внутренний размер содержимого табличного представления. Создайте ограничения между подпредставлениями представления прокрутки (включая представление таблицы), чтобы разложить их, и убедитесь, что существуют ограничения для всех четырех ребер вида прокрутки.

Возможно, вам нужно отправить invalidateIntrinsicContentSize в табличное представление в подходящее время (когда вы добавляете или удаляете строки или изменяете высоту строк). Вы могли бы просто переопределить соответствующие методы в MyTableView, чтобы сделать это. Например. сделать [self invalidateIntrinsicContentSize] в -endUpdates, -reloadData, - insertRowsAtIndexPaths:withRowAnimation: и т. д.

Вот результат моего тестирования:

table view with intrinsic content size in scroll view

Представление прокрутки имеет светло-синий фон. Красная верхняя метка и синяя нижняя метка являются родственными элементами представления таблицы в представлении прокрутки.

Вот полный исходный код для контроллера представления в моем тесте. Там нет файла 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
91
rob mayoff

В дополнение к ответу Роба есть Swift пример подкласса 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 или 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)
    }

}

Я использовал его, чтобы поместить табличное представление в ячейку другого автоматически изменяемого размера табличного представления.

53
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
10
Klemen Zagar

Код @ MuHAOS и @ klemen-zagar мне очень помог, но фактически вызывает проблему с производительностью, вызывая бесконечный цикл компоновки, когда табличное представление содержится в представлении стека, которое само содержится в представлении прокрутки. Смотрите мое решение ниже.

@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
2
mhoeller

вы можете добавить представление в виде заголовка и нижнего колонтитула таблицы просмотра. потому что просмотр таблицы является подпредставлением прокрутки. следуйте приведенному ниже примеру.

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;

а также вы можете добавить представление заголовка и нижнего колонтитула представления таблицы с помощью простого перетаскивания в представление таблицы в раскадровке и использовать IBOutlet этих представлений.

0
KKRocks