Как установить высоту tableHeaderView (UITableView) с автозапуском?

Я размахиваю своей головой о стену в течение последних 3 или 4 часов, и я не могу понять это. У меня есть UIViewController с полноэкранным UITableView внутри него (там некоторые другие вещи на экране, поэтому я не могу использовать UITableViewController), и я хочу, чтобы мой tableHeaderView изменил размер с автозапуском. Излишне говорить, что он не сотрудничает.

Смотрите снимок экрана ниже.

enter image description here

Поскольку обзорная заметка (например, текст "Обзор обзорной информации здесь." ) имеет динамический контент, я использую автозапуск, чтобы изменить его размер и контролировать. У меня есть все изменения размера, за исключением таблицыHeaderView, которая находится прямо под Paralax Table View в hiearchy.

Единственный способ, которым я нашел изменение размера заголовка, является программным, со следующим кодом:

CGRect headerFrame = self.headerView.frame;
headerFrame.size.height = headerFrameHeight;
self.headerView.frame = headerFrame;
[self.listTableView setTableHeaderView:self.headerView];

В этом случае headerFrameHeight представляет собой ручной расчет высоты таблицы TableHeader следующим образом (innerHeaderView - это белая область или второй "вид", headerView - tableHeaderView):

CGFloat startingY = self.innerHeaderView.frame.origin.y + self.overviewLabel.frame.origin.y;
CGRect overviewSize = [self.overviewLabel.text
                       boundingRectWithSize:CGSizeMake(290.f, CGFLOAT_MAX)
                       options:NSStringDrawingUsesLineFragmentOrigin
                       attributes:@{NSFontAttributeName: self.overviewLabel.font}
                       context:nil];
CGFloat overviewHeight = overviewSize.size.height;
CGFloat overviewPadding = ([self.overviewLabel.text length] > 0) ? 10 : 0; // If there no overviewText, eliminate the padding in the overall height.
CGFloat headerFrameHeight = ceilf(startingY + overviewHeight + overviewPadding + 21.f + 10.f);

Ручной расчет работает, но он неуклюже и подвержен ошибкам, если в будущем ситуация изменится. То, что я хочу, чтобы иметь возможность сделать, - это сделать автоматическое изменение размера таблицыHeaderView на основе предоставленных ограничений, например, в любом другом месте. Но для жизни меня я не могу понять.

Там есть несколько сообщений об этом, но ни один из них не ясен и в конечном итоге запутывал меня больше. Вот несколько:

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

Любая помощь будет действительно оценена!

РЕДАКТИРОВАТЬ 1: Благодаря предложению TomSwift, я смог понять это. Вместо того, чтобы вручную вычислять высоту обзора, я могу рассчитать его для меня следующим образом, а затем снова установить tableHeaderView как обычно.

[self.headerView setNeedsLayout];
[self.headerView layoutIfNeeded];
CGFloat height = [self.innerHeaderView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + self.innerHeaderView.frame.origin.y; // adding the origin because innerHeaderView starts partway down headerView.

CGRect headerFrame = self.headerView.frame;
headerFrame.size.height = height;
self.headerView.frame = headerFrame;
[self.listTableView setTableHeaderView:self.headerView];

Изменить 2: Как отмечали другие, решение, опубликованное в редакторе 1, похоже, не работает в viewDidLoad. Однако, похоже, он работает в viewWillLayoutSubviews. Пример кода ниже:

// Note 1: The variable names below don't match the variables above - this is intended to be a simplified "final" answer.
// Note 2: _headerView was previously assigned to tableViewHeader (in loadView in my case since I now do everything programatically).
// Note 3: autoLayout code can be setup programatically in updateViewConstraints.
- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];

    [_headerWrapper setNeedsLayout];
    [_headerWrapper layoutIfNeeded];
    CGFloat height = [_headerWrapper systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;

    CGRect headerFrame = _headerWrapper.frame;
    headerFrame.size.height = height;
    _headerWrapper.frame = headerFrame;
    _tableView.tableHeaderView = _headerWrapper;
}

Ответы

Ответ 2

Я действительно сражался с этим, и наложение настроек в viewDidLoad не сработало для меня, поскольку фрейм не установлен в viewDidLoad, я также столкнулся с множеством беспорядочных предупреждений, где инкапсулированная высота автоматического макета заголовка уменьшено до 0. Я заметил только проблему с iPad при представлении tableView в представлении формы.

Для меня проблема заключалась в установке tableViewHeader в viewWillLayoutSubviews, а не в viewDidLoad.

func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        if tableView.tableViewHeaderView == nil {
            let header: MyHeaderView = MyHeaderView.createHeaderView()
            header.setNeedsUpdateConstraints()
            header.updateConstraintsIfNeeded()
            header.frame = CGRectMake(0, 0, CGRectGetWidth(tableView.bounds), CGFloat.max)
            var newFrame = header.frame
            header.setNeedsLayout()
            header.layoutIfNeeded()
            let newSize = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
            newFrame.size.height = newSize.height
            header.frame = newFrame
            self.tableView.tableHeaderView = header
        }
    }

Ответ 3

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

Просто добавьте это в свой контроллер просмотра.

func sizeHeaderToFit(tableView: UITableView) {
    if let headerView = tableView.tableHeaderView {
        let height = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
        var frame = headerView.frame
        frame.size.height = height
        headerView.frame = frame
        tableView.tableHeaderView = headerView
        headerView.setNeedsLayout()
        headerView.layoutIfNeeded()
    }
}

Чтобы изменить размер в соответствии с динамически меняющейся меткой:

@IBAction func addMoreText(sender: AnyObject) {
    self.label.text = self.label.text! + "\nThis header can dynamically resize according to its contents."
}

override func viewDidLayoutSubviews() {
    // viewDidLayoutSubviews is called when labels change.
    super.viewDidLayoutSubviews()
    sizeHeaderToFit(tableView)
}

Анимация размера в соответствии с изменениями ограничения:

@IBOutlet weak var makeThisTallerHeight: NSLayoutConstraint!

@IBAction func makeThisTaller(sender: AnyObject) {

    UIView.animateWithDuration(0.3) {
        self.tableView.beginUpdates()
        self.makeThisTallerHeight.constant += 20
        self.sizeHeaderToFit(self.tableView)
        self.tableView.endUpdates()
    }
}

См. проект AutoResizingHeader, чтобы увидеть это в действии. https://github.com/p-sun/Swift2-iOS9-UI

AutoResizingHeader

Ответ 4

Ваше решение, использующее systemLayoutSizeFittingSize: работает, если вид заголовка просто обновляется один раз на каждом представлении вида. В моем случае представление заголовка обновлялось несколько раз, чтобы отразить изменения состояния. Но systemLayoutSizeFittingSize: всегда сообщается о том же размере. То есть размер, соответствующий первому обновлению.

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

self.listTableView.tableHeaderView = nil;
[self.headerView removeFromSuperview];

Ответ 5

Это работало для меня на ios10 и Xcode 8

func layoutTableHeaderView() {

    guard let headerView = tableView.tableHeaderView else { return }
    headerView.translatesAutoresizingMaskIntoConstraints = false

    let headerWidth = headerView.bounds.size.width;
    let temporaryWidthConstraints = NSLayoutConstraint.constraintsWithVisualFormat("[headerView(width)]", options: NSLayoutFormatOptions(rawValue: UInt(0)), metrics: ["width": headerWidth], views: ["headerView": headerView])

    headerView.addConstraints(temporaryWidthConstraints)

    headerView.setNeedsLayout()
    headerView.layoutIfNeeded()

    let headerSize = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
    let height = headerSize.height
    var frame = headerView.frame

    frame.size.height = height
    headerView.frame = frame

    self.tableView.tableHeaderView = headerView

    headerView.removeConstraints(temporaryWidthConstraints)
    headerView.translatesAutoresizingMaskIntoConstraints = true

}

Ответ 6

Это решение изменяет размер tableHeaderView и избегает бесконечного цикла в методе viewDidLayoutSubviews(), который я имел с некоторыми другими ответами здесь:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    if let headerView = tableView.tableHeaderView {
        let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
        var headerFrame = headerView.frame

        // comparison necessary to avoid infinite loop
        if height != headerFrame.size.height {
            headerFrame.size.height = height
            headerView.frame = headerFrame
            tableView.tableHeaderView = headerView
        }
    }
}

Смотрите также эту запись: fooobar.com/questions/23277/...

Ответ 7

В моем случае viewDidLayoutSubviews работал лучше. viewWillLayoutSubviews приводит к появлению белых строк tableView. Также я добавил, проверяет, существует ли мой объект headerView.

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];

    if ( ! self.userHeaderView ) {
        // Setup HeaderView
        self.userHeaderView = [[[NSBundle mainBundle] loadNibNamed:@"SSUserHeaderView" owner:self options:nil] objectAtIndex:0];
        [self.userHeaderView setNeedsLayout];
        [self.userHeaderView layoutIfNeeded];
        CGFloat height = [self.userHeaderView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
        CGRect headerFrame = self.userHeaderView.frame;
        headerFrame.size.height = height;
        self.userHeaderView.frame = headerFrame;
        self.tableView.tableHeaderView = self.userHeaderView;

        // Update HeaderView with data
        [self.userHeaderView updateWithProfileData];
    }
}

Ответ 8

Вполне возможно использовать универсальный UIView на основе UIView с любой внутренней структурой tableHeaderView AL в качестве tableHeaderView.

Единственное, что нужно - это tableFooterView установить простой tableFooterView !

Пусть self.headerView - это некий UIView основанный на UIView.

- (void)viewDidLoad {

    ........................

    self.tableView.tableFooterView = [UIView new];

    [self.headerView layoutIfNeeded]; // to set initial size

    self.tableView.tableHeaderView = self.headerView;

    [self.tableView.leadingAnchor constraintEqualToAnchor:self.headerView.leadingAnchor].active = YES;
    [self.tableView.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor].active = YES;
    [self.tableView.topAnchor constraintEqualToAnchor:self.headerView.topAnchor].active = YES;

    // and the key constraint
    [self.tableFooterView.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor].active = YES;
}

Если self.headerView меняет высоту при повороте пользовательского интерфейса, нужно реализовать

- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
    [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];

    [coordinator animateAlongsideTransition: ^(id<UIViewControllerTransitionCoordinatorContext> context) {
        // needed to resize header height
        self.tableView.tableHeaderView = self.headerView;
    } completion: NULL];
}

Для этого можно использовать категорию ObjC

@interface UITableView (AMHeaderView)
- (void)am_insertHeaderView:(UIView *)headerView;
@end

@implementation UITableView (AMHeaderView)

- (void)am_insertHeaderView:(UIView *)headerView {

    NSAssert(self.tableFooterView, @"Need to define tableFooterView first!");

    [headerView layoutIfNeeded];

    self.tableHeaderView = headerView;

    [self.leadingAnchor constraintEqualToAnchor:headerView.leadingAnchor].active = YES;
    [self.trailingAnchor constraintEqualToAnchor:headerView.trailingAnchor].active = YES;
    [self.topAnchor constraintEqualToAnchor:headerView.topAnchor].active = YES;

    [self.tableFooterView.trailingAnchor constraintEqualToAnchor:headerView.trailingAnchor].active = YES;
}
@end

А также расширение Swift

extension UITableView {

    func am_insertHeaderView2(_ headerView: UIView) {

        assert(tableFooterView != nil, "Need to define tableFooterView first!")

        headerView.layoutIfNeeded()

        tableHeaderView = headerView

        leadingAnchor.constraint(equalTo: headerView.leadingAnchor).isActive = true
        trailingAnchor.constraint(equalTo: headerView.trailingAnchor).isActive = true
        topAnchor.constraint(equalTo: headerView.topAnchor).isActive = true

        tableFooterView?.trailingAnchor.constraint(equalTo: headerView.trailingAnchor).isActive = true
    }
}

Ответ 9

Он работает как для представления заголовка, так и для нижнего колонтитула, просто замените заголовок нижним колонтитулом

func sizeHeaderToFit() {
    if let headerView = tableView.tableHeaderView {

        headerView.setNeedsLayout()
        headerView.layoutIfNeeded()

        let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
        var frame = headerView.frame
        frame.size.height = height
        headerView.frame = frame

        tableView.tableHeaderView = headerView
    }
}