Wie stelle ich die Höhe von tableHeaderView (UITableView) mit Autolayout ein?

86

Ich habe in den letzten 3 oder 4 Stunden meinen Kopf gegen die Wand geschlagen und ich kann es scheinbar nicht herausfinden. Ich habe einen UIViewController mit einer UITableView im Vollbildmodus (es gibt einige andere Dinge auf dem Bildschirm, weshalb ich keinen UITableViewController verwenden kann) und ich möchte, dass meine tableHeaderView die Größe mit Autolayout ändert. Unnötig zu sagen, dass es nicht kooperiert.

Siehe Screenshot unten.

Geben Sie hier die Bildbeschreibung ein

Da das Übersichtslabel (z. B. der Text "Übersichtsinformationen hier auflisten") dynamischen Inhalt hat, verwende ich das automatische Layout, um die Größe zu ändern und die Übersicht anzuzeigen. Ich habe alles schön in der Größe geändert, außer der tableHeaderView, die sich direkt unter Paralax Table View in der Hiearchy befindet.

Die einzige Möglichkeit, die Größe dieser Header-Ansicht zu ändern, ist programmgesteuert mit dem folgenden Code:

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

In diesem Fall ist headerFrameHeight eine manuelle Berechnung der tableViewHeader-Höhe wie folgt (innerHeaderView ist der weiße Bereich oder die zweite "Ansicht", headerView ist 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);

Die manuelle Berechnung funktioniert, ist jedoch umständlich und fehleranfällig, wenn sich die Dinge in Zukunft ändern. Ich möchte in der Lage sein, die Größe von tableHeaderView basierend auf den bereitgestellten Einschränkungen automatisch zu ändern, wie Sie es überall tun können. Aber für mein Leben kann ich es nicht herausfinden.

Es gibt mehrere Beiträge zu SO darüber, aber keiner ist klar und hat mich mehr verwirrt. Hier sind einige:

Es ist nicht wirklich sinnvoll, die Eigenschaft translatesAutoresizingMaskIntoConstraints in NO zu ändern, da dies nur Fehler für mich verursacht und konzeptionell sowieso keinen Sinn ergibt.

Jede Hilfe wäre wirklich dankbar!

EDIT 1: Dank TomSwifts Vorschlag konnte ich es herausfinden. Anstatt die Höhe der Übersicht manuell zu berechnen, kann ich sie wie folgt für mich berechnen lassen und dann die tableHeaderView wie zuvor zurücksetzen.

[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];

Bearbeiten 2: Wie andere angemerkt haben, scheint die in Bearbeiten 1 veröffentlichte Lösung in viewDidLoad nicht zu funktionieren. Es scheint jedoch in viewWillLayoutSubviews zu funktionieren. Beispielcode unten:

// 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;
}
Andrew Cross
quelle
2
setTableHeaderViewfunktioniert nicht auf Xcode6. Das Problem ist, dass sich Zellen von tableHeaderView überlappen. Es funktioniert jedoch auf Xcode5
DawnSong
@DawnSong kennen Sie eine Lösung für Xcode6, damit ich die Antwort aktualisieren kann?
Andrew Cross
1
Nach vielen Versuchen verwende ich UITableViewCell anstelle von tableHeaderView und es funktioniert.
DawnSong
Ich habe auch meinen Kopf zerschlagen!
Akshit Zaveri
Die wahre vollständige Autolayout-Lösung ist hier
Malex

Antworten:

35

Sie müssen die UIView systemLayoutSizeFittingSize:Methode verwenden, um die minimale Begrenzungsgröße Ihrer Header-Ansicht zu erhalten.

Ich biete weitere Diskussionen zur Verwendung dieser API in dieser Frage / Antwort:

Wie kann ich die Größe der Übersicht ändern, um sie an alle Unteransichten mit Autolayout anzupassen?

TomSwift
quelle
Es kommt mit height = 0 zurück, obwohl ich nicht 100% sicher bin, ob ich es richtig konfiguriert habe, da dies keine UITableViewCell ist. Daher gibt es nicht die contentView, auf der "systemLayoutSizeFittingSize" aufgerufen werden kann. Was ich versucht habe: [self.headerView setNeedsLayout]; [self.headerView layoutIfNeeded]; CGFloat height = [self.headerView systemLayoutSizeFittingSize: UILayoutFittingCompressedSize] .height; CGRect headerFrame = self.headerView.frame; headerFrame.size.height = height; self.headerView.frame = headerFrame; [self.listTableView setTableHeaderView: self.headerView]; Ich habe auch bestätigt, dass PreferredMax ... gültig ist.
Andrew Cross
Aha! Um es herauszufinden, musste ich [self.innerHeaderView systemLayoutSizeFittingSize ...] anstelle von self.headerView ausführen, da dies der eigentliche dynamische Inhalt ist. Danke vielmals!
Andrew Cross
Es funktioniert, aber in meiner App gibt es eine kleine Höhenabweichung. Wahrscheinlich, weil meine Beschriftungen einen zugeordneten Text und einen dynamischen Typ verwenden.
Bio
23

Ich habe wirklich mit diesem gekämpft und das Einfügen des Setups in viewDidLoad hat bei mir nicht funktioniert, da der Frame nicht in viewDidLoad festgelegt ist. Außerdem gab es unzählige unordentliche Warnungen, bei denen die gekapselte Höhe des automatischen Layouts des Headers auf 0 reduziert wurde Ich habe das Problem auf dem iPad nur beim Präsentieren einer tableView in einer Formularpräsentation bemerkt.

Was das Problem für mich löste, war das Festlegen des tableViewHeader in viewWillLayoutSubviews und nicht in 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
        }
    }
Daniel Galasko
quelle
22

Ich habe einen eleganten Weg gefunden, das automatische Layout zum Ändern der Größe von Tabellenüberschriften mit und ohne Animation zu verwenden.

Fügen Sie dies einfach Ihrem View Controller hinzu.

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

So ändern Sie die Größe entsprechend einer sich dynamisch ändernden Bezeichnung:

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

So animieren Sie eine Größenänderung gemäß einer Änderung einer Einschränkung:

@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()
    }
}

Sehen Sie sich das AutoResizingHeader-Projekt an, um dies in Aktion zu sehen. https://github.com/p-sun/Swift2-iOS9-UI

AutoResizingHeader

p-Sonne
quelle
Hey p-Sonne. Danke für das tolle Beispiel. Scheint, ich habe ein Problem mit tableViewHeader. Ich habe versucht, die gleichen Einschränkungen wie in Ihrem Beispiel zu haben, aber es funktioniert nicht. Wie genau sollte ich dem Etikett Einschränkungen hinzufügen, um die Höhe zu erhöhen? Vielen Dank für jeden Vorschlag
Bonnke
1
Stellen Sie sicher, dass Sie das Etikett einhaken, das Sie größer machen möchten @IBOutlet makeThisTallerund das Sie @IBAction fun makeThisTallerim Beispiel mögen. Beschränken Sie außerdem alle Seiten Ihres Etiketts auf den tableViewHeader (z. B. oben, unten, links und rechts).
p-sun
Vielen Dank! Zum Schluss durch Hinzufügen dieser Zeile gelöst: lblFeedDescription.preferredMaxLayoutWidth = lblFeedDescription.bounds.widthwobei das Etikett dasjenige ist, das ich vergrößern möchte. Vielen Dank !
Bonnke
Vielen Dank! Wenn Sie dies in einer Ansicht anstelle eines ViewControllers tun, können Sie anstelle von viewDidLayoutSubviews layoutSubviews
Andy Weinstein
3

Ihre Lösung mit systemLayoutSizeFittingSize: funktioniert, wenn die Kopfzeilenansicht bei jedem Erscheinungsbild nur einmal aktualisiert wird. In meinem Fall wurde die Header-Ansicht mehrmals aktualisiert, um Statusänderungen widerzuspiegeln. Aber systemLayoutSizeFittingSize: hat immer die gleiche Größe gemeldet. Das heißt, die Größe, die dem ersten Update entspricht.

Um systemLayoutSizeFittingSize zu erhalten: Um nach jedem Update die richtige Größe zurückzugeben, musste ich zuerst die Tabellenkopfansicht entfernen, bevor ich sie aktualisierte und erneut hinzufügte:

self.listTableView.tableHeaderView = nil;
[self.headerView removeFromSuperview];
Kim André Sand
quelle
3

Diese Lösung ändert die Größe von tableHeaderView und vermeidet Endlosschleifen in der viewDidLayoutSubviews()Methode, die ich mit einigen der anderen Antworten hier hatte:

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

Siehe auch diesen Beitrag: https://stackoverflow.com/a/34689293/1245231

petrsyn
quelle
1
Ich benutze diese Methode auch. Ich denke, es ist das Beste, weil man nicht mit Einschränkungen herumspielen muss. Es ist auch sehr kompakt.
Bio
2

Dies funktionierte bei mir auf ios10 und 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

}
Ravi Randeria
quelle
2

Es funktioniert sowohl für die Kopf- als auch für die Fußzeile. Ersetzen Sie einfach die Kopfzeile durch die Fußzeile

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
    }
}
Sai Kumar Reddy
quelle
1

Unter iOS 12 und höher stellen die folgenden Schritte sicher, dass das automatische Layout sowohl in Ihrer Tabelle als auch im Header ordnungsgemäß funktioniert.

  1. Erstellen Sie zuerst Ihre tableView und dann den Header.
  2. Rufen Sie am Ende Ihres Header-Erstellungscodes Folgendes auf:
[headerV setNeedsLayout];
[headerV layoutIfNeeded];
  1. Markieren Sie bei einer Änderung der Ausrichtung die Überschrift für das Layout erneut und laden Sie die Tabelle neu. Dies muss geringfügig geschehen, nachdem die Änderung der Ausrichtung gemeldet wurde:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 *NSEC_PER_SEC), dispatch_get_main_queue(), ^{

  [tableV.tableHeaderView setNeedsLayout];
  [tableV.tableHeaderView layoutIfNeeded];
  [tableV reloadData];});
RunLoop
quelle
0

In meinem Fall viewDidLayoutSubviewshat es besser funktioniert. viewWillLayoutSubviewsbewirkt, dass weiße Linien von a tableViewerscheinen. Außerdem habe ich die Überprüfung hinzugefügt, ob mein headerViewObjekt bereits vorhanden ist.

- (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];
    }
}
Denis Kutlubaev
quelle
0

Es ist durchaus möglich, generisches AutoLayout-basiertes UIViewmit jeder inneren AL-Unteransichtsstruktur als zu verwenden tableHeaderView.

Das einzige, was man braucht, ist ein einfaches tableFooterViewvorher einzustellen !

Let self.headerViewbasiert auf Einschränkungen 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;
}

Wenn self.headerViewsich die Höhe unter UI-Rotation ändert, muss dies implementiert werden

- (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];
}

Zu diesem Zweck kann die ObjC- Kategorie verwendet werden

@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

Und auch Swift- Erweiterung

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
    }
}
Malex
quelle
0

Diese Lösung funktioniert perfekt für mich:

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

Es erweitert den UITableViewController. Wenn Sie jedoch nur eine UITableView verwenden, funktioniert dies weiterhin. Erweitern Sie einfach die UITableView anstelle des UITableViewController. Rufen Sie die Methoden auf sizeHeaderToFit()oder sizeFooterToFit()wann immer es ein Ereignis gibt, das die tableViewHeaderHöhe ändert .

meow2x
quelle