Як встановити висоту tableHeaderView (UITableView) з авторозкладкою?


86

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

Дивіться знімок екрана нижче.

введіть тут опис зображення

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

Єдиний спосіб, який я знайшов, щоб змінити розмір цього виду заголовка - це програмно, з таким кодом:

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

У цьому випадку headerFrameHeight - це ручний розрахунок висоти tableViewHeader наступним чином (innerHeaderView - це біла область, або другий "View", 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 на основі наданих обмежень, як це можна будь-де. Але на все життя я не можу це зрозуміти.

Існує кілька публікацій на тему SO, але жодна з них не є чіткою, і в підсумку мене це ще більше бентежить. Ось декілька:

Насправді немає сенсу змінювати властивість translatesAutoresizingMaskIntoConstraints на НІ, оскільки це просто спричиняє для мене помилки, і в будь-якому випадку не має сенсу концептуально.

Будь-яка допомога була б дуже вдячна!

EDIT 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
setTableHeaderViewне працює на Xcode6. Проблема полягає в тому, що комірки перекриваються таблицеюHeaderView. Однак це працює на Xcode5
DawnSong

@DawnSong знаєте рішення для Xcode6, щоб я міг оновити відповідь?
Ендрю Крос

1
Після багатьох випробувань я використовую UITableViewCell замість tableHeaderView, і це працює.
DawnSong

Я теж голову розбив!
Акшіт Завері

Істинне повне рішення autolayout знаходиться тут
Malex

Відповіді:


35

Вам потрібно використовувати UIView systemLayoutSizeFittingSize:метод, щоб отримати мінімальний обмежувальний розмір вашого виду заголовка.

Я надаю подальше обговорення використання цього API у цих запитаннях / відповідях:

Як змінити розмір суперперегляду, щоб відповідати всім підпрограмам з авторозкладкою?


Він повертається з висотою = 0, хоча я не впевнений на 100%, що правильно його налаштував, оскільки це не UITableViewCell, тому немає contentView для виклику "systemLayoutSizeFittingSize". Що я спробував: [self.headerView setNeedsLayout]; [self.headerView layoutIfNeeded]; CGFloat висота = [self.headerView systemLayoutSizeFittingSize: UILayoutFittingCompressedSize] .height; CGRect headerFrame = self.headerView.frame; headerFrame.size.height = висота; self.headerView.frame = headerFrame; [self.listTableView setTableHeaderView: self.headerView]; Я також підтвердив, що бажаний MaxMax ... є дійсним.
Andrew Cross

Ага! З’ясував це, мені потрібно було зробити [self.innerHeaderView systemLayoutSizeFittingSize ...] замість self.headerView, оскільки це те, що має фактичний динамічний вміст. Дякую тонна!
Ендрю Крос

Це працює, але в моєму додатку є невелика розбіжність у висоті. Можливо, тому, що мої мітки використовують атрибутивний текст та динамічний тип.
біо

23

Я дійсно боровся з цим, і викручування налаштувань у 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
        }
    }

для оновленого рішення з мінімальним кодом спробуйте це: stackoverflow.com/a/63594053/3933302
Sourabh Sharma

23

Я знайшов елегантний спосіб використання автоматичного макета для зміни розміру заголовків таблиць з анімацією та без неї.

Просто додайте це у свій контролер перегляду.

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


Гей, сонечко. Дякую за чудовий приклад. Здається, у мене проблема з tableViewHeader. Я намагався мати ті самі обмеження, що і у вашому прикладі, але не працював. Як саме я повинен додати обмеження до мітки, яку я хочу збільшити висоту? Дякую за будь-яку пропозицію
Bonnke

1
Переконайтеся, що ви під’єднали ярлик, до якого ви хочете зробити вищий @IBOutlet makeThisTallerі @IBAction fun makeThisTallerподобається в прикладі. Крім того, обмежте всі сторони вашої мітки таблицеюViewHeader (наприклад, верхньою, нижньою, лівою та правою).
п-нд

Дуже дякую! Нарешті вирішено додаванням цього рядка: lblFeedDescription.preferredMaxLayoutWidth = lblFeedDescription.bounds.widthде мітка - це та, яку я хочу збільшити. Дякую !
Bonnke

Дякую! Якщо ви робите це всередині подання замість ViewController, то замість того, щоб замінити viewDidLayoutSubviews, ви можете замінити layoutSubviews
Енді Вайнштейн

4

Це рішення змінює розмір 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


1
Я теж використовую цей метод. Подумайте, що це найкраще, тому що немає суєти з обмеженнями. Це також дуже компактно.
біо

3

Ваше рішення за допомогою systemLayoutSizeFittingSize: працює, якщо подання заголовка просто оновлюється один раз при кожному появі подання. У моєму випадку вид заголовка оновлювався кілька разів, щоб відображати зміни стану. Але systemLayoutSizeFittingSize: завжди повідомляє однаковий розмір. Тобто розмір, що відповідає першому оновленню.

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

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

2

Це працювало для мене на 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

}

2

Це працює як для верхнього, так і для нижнього колонтитула, просто замініть верхній колонтитул

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

1

Для iOS 12 і новіших версій наступні кроки забезпечать належну роботу автоматичного розкладання як у вашій таблиці, так і в заголовку.

  1. Спершу створіть таблицюView, а потім заголовок.
  2. В кінці коду створення заголовка зателефонуйте:
[headerV setNeedsLayout];
[headerV layoutIfNeeded];
  1. Після зміни орієнтації знову позначте заголовок для макета та перезавантажте таблицю, це має відбутися трохи після повідомлення про зміну орієнтації:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 *NSEC_PER_SEC), dispatch_get_main_queue(), ^{

  [tableV.tableHeaderView setNeedsLayout];
  [tableV.tableHeaderView layoutIfNeeded];
  [tableV reloadData];});

0

У моєму випадку це viewDidLayoutSubviewsпрацювало краще. viewWillLayoutSubviewsзмушує tableViewз'являтися білі лінії a . Також я додав перевірку, чи мій 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];
    }
}

0

Цілком можливо використовувати загальну основу UIViewAutoLayout з будь-якою внутрішньою структурою підвиду AL як a 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

А також розширення 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
    }
}

0

Це рішення для мене ідеально працює:

https://spin.atomicobject.com/2017/08/11/swift-extending-uitableviewcontroller/

Він розширює UITableViewController. Але якщо ви просто використовуєте UITableView, він все одно буде працювати, просто розширіть UITableView замість UITableViewController. Викликайте методи sizeHeaderToFit()або sizeFooterToFit()всякий раз, коли є подія, що змінює tableViewHeaderвисоту.


0

Скопійовано з цього допису

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    
    if let headerView = tableView.tableHeaderView {

        let height = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).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
        }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.