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


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

см. скриншот ниже.

потому что overviewLabel (например, "Список обзорная информация здесь."текст) имеет динамический контент, я использую autolayout, чтобы изменить его размер, и это superview. У меня все изменяется красиво, за исключением tableHeaderView, который находится прямо под представлением таблицы Paralax в hiearchy.

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

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

в этом случае headerFrameHeight представляет собой ручной Расчет высоты tableViewHeader следующим образом (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's no overviewText, eliminate the padding in the overall height.
CGFloat headerFrameHeight = ceilf(startingY + overviewHeight + overviewPadding + 21.f + 10.f);

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

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

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

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

EDIT 1: Благодаря предложению Томсвифта, я смог понять это. Вместо того, чтобы вручную вычислять высоту обзора, я могу рассчитать его для меня следующим образом, а затем повторно установить 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: как отмечали другие, решение, опубликованное в Edit 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;
}
8 80

8 ответов:

вы должны использовать UIView systemLayoutSizeFittingSize: метод для получения минимального размера ограничения вашего представления заголовка.

Я предлагаю дальнейшее обсуждение использования этого API в этом Q / A:

как изменить размер супервизора, чтобы он соответствовал всем подвидам с автозапуском?

Я действительно сражался с этим, и установка установки в 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
        }
    }

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

просто добавьте это в ваш контроллер представления.

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

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

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

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

это работало для меня на 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

}

это решение изменяет размер 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
        }
    }
}

Смотрите также этот пост: https://stackoverflow.com/a/34689293/1245231

в моем случае viewDidLayoutSubviews лучше работал. viewWillLayoutSubviews вызывает белые линии a tableView появится. Также я добавил проверку, если мой

вполне можно использовать общий AutoLayout-based UIView С любой внутренней структурой подвида AL как tableHeaderView.

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

пусть self.headerView это некоторые ограничения на основе 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

, а также Свифт расширение

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
    }
}