Come si imposta l'altezza di tableHeaderView (UITableView) con il layout automatico?


86

Ho sbattuto la testa contro il muro con questo per le ultime 3 o 4 ore e non riesco a capirlo. Ho un UIViewController con un UITableView a schermo intero al suo interno (ci sono altre cose sullo schermo, motivo per cui non posso usare un UITableViewController) e voglio che il mio tableHeaderView venga ridimensionato con autolayout. Inutile dire che non sta collaborando.

Vedi screenshot qui sotto.

inserisci qui la descrizione dell'immagine

Poiché il overviewLabel (ad esempio il testo "List overview information here.") Ha un contenuto dinamico, sto usando il layout automatico per ridimensionarlo ed è superview. Ho tutto ridimensionato bene, ad eccezione di tableHeaderView, che si trova proprio sotto Paralax Table View nell'hiearchy.

L'unico modo che ho trovato per ridimensionare quella vista dell'intestazione è programmaticamente, con il seguente codice:

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

In questo caso, headerFrameHeight è un calcolo manuale dell'altezza tableViewHeader come segue (innerHeaderView è l'area bianca, o la seconda "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);

Il calcolo manuale funziona, ma è goffo e soggetto a errori se le cose cambiano in futuro. Quello che voglio essere in grado di fare è avere il ridimensionamento automatico tableHeaderView in base ai vincoli forniti, come puoi fare altrove. Ma per la vita di me, non riesco a capirlo.

Ci sono diversi post su SO su questo, ma nessuno è chiaro e ha finito per confondermi di più. Eccone alcuni:

Non ha davvero senso cambiare la proprietà translatesAutoresizingMaskIntoConstraints su NO, poiché ciò causa solo errori per me e non ha comunque senso concettualmente.

Qualsiasi aiuto sarebbe molto apprezzato!

EDIT 1: Grazie al suggerimento di TomSwift, sono stato in grado di capirlo. Invece di calcolare manualmente l'altezza della panoramica, posso farla calcolare per me come segue e quindi reimpostare tableHeaderView come prima.

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

Modifica 2: come altri hanno notato, la soluzione pubblicata in Modifica 1 non sembra funzionare in viewDidLoad. Tuttavia, sembra funzionare in viewWillLayoutSubviews. Codice di esempio di seguito:

// 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
setTableHeaderViewnon funziona su Xcode6. Il problema è che le celle sono sovrapposte da tableHeaderView. Tuttavia, funziona su Xcode5
DawnSong

@DawnSong conosci una soluzione per Xcode6 in modo che io possa aggiornare la risposta?
Andrew Cross

1
Dopo molte prove, utilizzo UITableViewCell invece di tableHeaderView e funziona.
DawnSong

Ho anche spaccato la testa!
Akshit Zaveri

La vera soluzione completa di layout automatico è qui
malex

Risposte:


35

Devi usare il file UIView systemLayoutSizeFittingSize: metodo per ottenere la dimensione minima del limite della vista dell'intestazione.

Fornisco ulteriori discussioni sull'utilizzo di questa API in questa domanda / risposta:

Come ridimensionare la superview per adattarla a tutte le sottoview con il layout automatico?


Sta tornando con height = 0, anche se non sono sicuro al 100% di averlo configurato correttamente poiché questo non è un UITableViewCell, quindi non c'è il contentView per chiamare "systemLayoutSizeFittingSize" su. Cosa ho provato: [self.headerView setNeedsLayout]; [self.headerView layoutIfNeeded]; CGFloat height = [self.headerView systemLayoutSizeFittingSize: UILayoutFittingCompressedSize] .height; CGRect headerFrame = self.headerView.frame; headerFrame.size.height = altezza; self.headerView.frame = headerFrame; [self.listTableView setTableHeaderView: self.headerView]; Ho anche confermato che preferredMax ... è valido.
Andrew Cross

Aha! Capito, dovevo fare [self.innerHeaderView systemLayoutSizeFittingSize ...] invece di self.headerView poiché è quello che ha il contenuto dinamico effettivo. Grazie mille!
Andrew Cross

Funziona ma nella mia app c'è una piccola discrepanza di altezza. Probabilmente perché le mie etichette utilizzano un testo attribuito e un tipo dinamico.
biografia

23

Ho davvero combattuto con questo e il ploning della configurazione in viewDidLoad non ha funzionato per me poiché il frame non è impostato in viewDidLoad, ho anche finito con tonnellate di avvisi disordinati in cui l'altezza del layout automatico incapsulato dell'intestazione veniva ridotta a 0 Ho notato il problema solo su iPad durante la presentazione di un tableView in una presentazione del modulo.

Ciò che ha risolto il problema per me è stato impostare tableViewHeader in viewWillLayoutSubviews piuttosto che 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
        }
    }

per soluzione aggiornato con codice minimo provare questo: stackoverflow.com/a/63594053/3933302
Sourabh Sharma

23

Ho trovato un modo elegante per utilizzare il layout automatico per ridimensionare le intestazioni delle tabelle, con e senza animazione.

Aggiungilo semplicemente al tuo View Controller.

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

Per ridimensionare in base a un'etichetta che cambia dinamicamente:

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

Per animare un ridimensionamento in base alle modifiche in un vincolo:

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

Vedere il progetto AutoResizingHeader per vederlo in azione. https://github.com/p-sun/Swift2-iOS9-UI

AutoResizingHeader


Ehi sole. Grazie per l'ottimo esempio. Sembra che abbia problemi con tableViewHeader. Ho provato ad avere gli stessi vincoli del tuo esempio, ma non funziona. In che modo esattamente devo aggiungere vincoli all'etichetta di cui voglio aumentare l'altezza? Grazie per qualsiasi suggerimento
Bonnke

1
Assicurati di agganciare l'etichetta a cui vuoi rendere più alta @IBOutlet makeThisTallere @IBAction fun makeThisTallercome nell'esempio. Inoltre, vincola tutti i lati della tua etichetta a tableViewHeader (ad es. In alto, in basso, a sinistra ea destra).
p-dom

Molte grazie! Infine risolto aggiungendo questa riga: lblFeedDescription.preferredMaxLayoutWidth = lblFeedDescription.bounds.widthdove l'etichetta è quella che voglio aumentare la dimensione. Grazie !
Bonnke

Grazie! Se lo stai facendo all'interno di una vista invece di un ViewController, invece di sovrascrivere viewDidLayoutSubviews, puoi sovrascrivere layoutSubviews
Andy Weinstein

4

Questa soluzione ridimensiona tableHeaderView ed evita il ciclo infinito nel viewDidLayoutSubviews()metodo che stavo utilizzando con alcune delle altre risposte qui:

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

Vedi anche questo post: https://stackoverflow.com/a/34689293/1245231


1
Sto usando anche questo metodo. Penso che sia il migliore perché non si gioca con i vincoli. È anche molto compatto.
biografia

3

La tua soluzione che utilizza systemLayoutSizeFittingSize: funziona se la vista dell'intestazione viene aggiornata solo una volta per ogni aspetto della vista. Nel mio caso, la visualizzazione dell'intestazione è stata aggiornata più volte per riflettere i cambiamenti di stato. Ma systemLayoutSizeFittingSize: riportava sempre la stessa dimensione. Cioè, la dimensione corrispondente al primo aggiornamento.

Per ottenere systemLayoutSizeFittingSize: per restituire la dimensione corretta dopo ogni aggiornamento ho dovuto prima rimuovere la visualizzazione dell'intestazione della tabella prima di aggiornarla e aggiungerla di nuovo:

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

2

Questo ha funzionato per me su iOS10 e 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

Funziona sia per la visualizzazione dell'intestazione che per il piè di pagina, basta sostituire l'intestazione con il piè di pagina

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

Per iOS 12 e versioni successive, i seguenti passaggi assicureranno che il layout automatico funzioni correttamente sia nella tabella che nell'intestazione.

  1. Crea prima il tuo tableView, poi l'intestazione.
  2. Alla fine del codice di creazione dell'intestazione, chiama:
[headerV setNeedsLayout];
[headerV layoutIfNeeded];
  1. Dopo la modifica dell'orientamento, contrassegnare nuovamente l'intestazione per il layout e ricaricare la tabella, questo deve avvenire leggermente dopo che è stata segnalata la modifica dell'orientamento:
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

Nel mio caso ha viewDidLayoutSubviewsfunzionato meglio. viewWillLayoutSubviewsfa tableViewapparire le linee bianche di a. Inoltre ho aggiunto il controllo se il mio headerViewoggetto esiste già.

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

È del tutto possibile utilizzare UIViewun layout automatico generico con qualsiasi struttura di sottoview interna AL come filetableHeaderView .

L'unica cosa di cui hai bisogno è impostare un semplice tableFooterView prima!

Lasciamo che self.headerViewsia basato su vincoli 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;
}

Se self.headerViewcambia l'altezza durante la rotazione dell'interfaccia utente, è necessario implementare

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

Si può usare la categoria ObjC per questo scopo

@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

E anche l' estensione 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

Copiato da questo post

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
        }
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.