Como redimensionar um tableHeaderView de um UITableView?

103

Estou tendo problemas para redimensionar um tableHeaderView. Simplesmente não funciona.

1) Crie um UITableView e UIView (100 x 320 px);

2) Defina o UIView como tableHeaderView do UITableView;

3) Construa e vá. Está tudo bem.

Agora, quero redimensionar a tableHeaderView, então adiciono este código em viewDidLoad:

self.tableView.autoresizesSubviews = YES;

self.tableView.tableHeaderView = myHeaderView;
self.tableView.tableFooterView = myFooterView;

CGRect newFrame = self.tableView.tableHeaderView.frame;
newFrame.size.height = newFrame.size.height + 100;
self.tableView.tableHeaderView.frame = newFrame;

A altura de tableHeaderView deve aparecer com 200, mas aparece com 100.

Se eu escrever:

self.tableView.autoresizesSubviews = YES;


CGRect newFrame = myHeaderView.frame;
newFrame.size.height = newFrame.size.height + 100;
myHeaderView.frame = newFrame;


self.tableView.tableHeaderView = myHeaderView;
self.tableView.tableFooterView = myFooterView;

Aí começa com 200 de altura, como eu quero. Mas quero poder modificá-lo em tempo de execução.

Eu também tentei isso, sem sucesso:

self.tableView.autoresizesSubviews = YES;

self.tableView.tableHeaderView = myHeaderView;
self.tableView.tableFooterView = myFooterView;

CGRect newFrame = self.tableView.tableHeaderView.frame;
newFrame.size.height = newFrame.size.height + 100;
self.tableView.tableHeaderView.frame = newFrame;

[self.tableView.tableHeaderView setNeedsLayout];
[self.tableView.tableHeaderView setNeedsDisplay];
[self.tableView setNeedsLayout];
[self.tableView setNeedsDisplay];

O ponto aqui é: Como redimensionamos um tableHeaderView em tempo de execução ??

Alguém pode fazer isso?

obrigado

iMe

iMe
fonte

Respostas:

180

FYI: Eu fiz isso funcionar modificando o tableHeaderView e reconfigurando-o. Nesse caso, estou ajustando o tamanho de tableHeaderView quando a subvisão UIWebView terminar de carregar.

[webView sizeToFit];
CGRect newFrame = headerView.frame;
newFrame.size.height = newFrame.size.height + webView.frame.size.height;
headerView.frame = newFrame;
[self.tableView setTableHeaderView:headerView];
kubi
fonte
13
+1 Isso funcionou para mim. Chamar 'setTableHeaderView' após a mudança de tamanho da subvisualização é a chave. O problema é que minha visão secundária muda de tamanho em um segundo como uma animação. Agora estou tentando descobrir como animar o tableHeaderView com ele.
Andrew
1
Perfeito, muito obrigado. Para mim, este é um exemplo de uma das qualidades menos desejáveis ​​das propriedades em Objective-C. Não há como sabermos (e nenhuma razão que deveríamos saber) que definir a plataforma tem o efeito colateral de recalcular a altura. Ele deve fazer isso automaticamente quando atualizamos a altura do cabeçalho ou devemos chamar algo como [tableView recalculateHeaderHeight]todas as vezes.
jakeboxer
48
@Andrew Eu sei que é quase um ano tarde demais, mas antes tarde do que nunca: fui capaz de animar a mudança de altura da visualização do cabeçalho da tabela encerrando a setTableHeaderView:chamada com [tableview beginUpdates]e[tableview endUpdates]
jasongregori
2
@jasongregori comentário útil que você adicionou - estou vendo que a mesma técnica (beginUpdates + endUpdates) não anima a mudança de altura para um tableFooterView da maneira que faz com um tableHeaderView. Você descobriu uma boa maneira de animar o tableFooterView também?
Kris
1
Impressionante, o evento funciona quando é feito dentro de um bloco de animação UIView, embora eu não ache muito eficiente nesse caso.
Pode
12

Esta resposta é antiga e aparentemente não funciona no iOS 7 e superior.

Encontrei o mesmo problema e também queria que as alterações fossem animadas, então criei uma subclasse de UIView para minha visualização de cabeçalho e adicionei estes métodos:

- (void)adjustTableHeaderHeight:(NSUInteger)newHeight{
    NSUInteger oldHeight = self.frame.size.height;
    NSInteger originChange = oldHeight - newHeight;

    [UIView beginAnimations:nil context:nil];

    [UIView setAnimationDuration:1.0f];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];

    self.frame = CGRectMake(self.frame.origin.x, 
                        self.frame.origin.y, 
                        self.frame.size.width, 
                        newHeight);

    for (UIView *view in [(UITableView *)self.superview subviews]) {
        if ([view isKindOfClass:[self class]]) {
            continue;
        }
        view.frame = CGRectMake(view.frame.origin.x, 
                            view.frame.origin.y - originChange, 
                            view.frame.size.width, 
                            view.frame.size.height);
    }

    [UIView commitAnimations];
}

- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context{
    [(UITableView *)self.superview setTableHeaderView:self];
}

Isso essencialmente anima todas as subvisualizações do UITableView que não são do mesmo tipo de classe da classe de chamada. No final da animação, ele chama setTableHeaderView no superview (o UITableView) - sem isso, o conteúdo de UITableView voltará na próxima vez que o usuário rolar. A única limitação que encontrei sobre isso até agora é se o usuário tentar rolar o UITableView enquanto a animação está ocorrendo, a rolagem será animada como se a visualização do cabeçalho não tivesse sido redimensionada (não é grande coisa se a animação for rápido).

Garrettmoon
fonte
funciona perfeitamente, tirei a animação pois não precisava dela.
Jiho Kang
Funcionou perfeitamente até o iOS7 acontecer ... adicionei minha solução abaixo
avishic
1
WTF? Você está bagunçando tanto com o interior do UITableView que realmente não deveria se surpreender que ele não funcione nas versões mais recentes do iOS ...;)
Daniel Rinser
10

Se você deseja animar condicionalmente as alterações, pode fazer o seguinte:

- (void) showHeader:(BOOL)show animated:(BOOL)animated{

    CGRect closedFrame = CGRectMake(0, 0, self.view.frame.size.width, 0);
    CGRect newFrame = show?self.initialFrame:closedFrame;

    if(animated){
        // The UIView animation block handles the animation of our header view
        [UIView beginAnimations:nil context:nil];
        [UIView setAnimationDuration:0.3];
        [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];

        // beginUpdates and endUpdates trigger the animation of our cells
        [self.tableView beginUpdates];
    }

    self.headerView.frame = newFrame;
    [self.tableView setTableHeaderView:self.headerView];

    if(animated){
        [self.tableView endUpdates];
        [UIView commitAnimations];
    }
}

Observe que a animação é dupla:

  1. A animação das células abaixo do tableHeaderView. Isso é feito usando beginUpdateseendUpdates
  2. A animação da visualização do cabeçalho real. Isso é feito usando um UIViewbloco de animação.

A fim de sincronizar essas duas animações, o animationCurvedeve ser definido para UIViewAnimationCurveEaseInOute a duração para0.3 , que parece ser o que o UITableView usa para sua animação.

Atualizar

Criei um projeto Xcode no gihub, que faz isso. Confira o projeto ResizeTableHeaderViewAnimatedem besi / ios-quickies

captura de tela

Besi
fonte
2
Essa técnica funciona, mas não funciona para visualizações de tabela com cabeçalhos de seção. Quando você usa atualizações iniciais / finais, isso interfere no bloco de animação, deixando cabeçalhos de seção duplicados.
BlueFish de
9

Acho que deve funcionar se você apenas definir a altura de myHeaderView assim:

CGRect newFrame = myHeaderView.frame;
newFrame.size.height = newFrame.size.height + 100;
myHeaderView.frame = newFrame;

self.tableView.tableHeaderView = myHeaderView;
Greg Martin
fonte
Isso realmente funciona, mas apenas se usado em viewDidLayoutSubviews
KoCMoHaBTa
6

Usei a solução @garrettmoon acima até o iOS 7.
Aqui está uma solução atualizada baseada em @garrettmoon's:

- (void)adjustTableHeaderHeight:(NSUInteger)newHeight animated:(BOOL)animated {

    [UIView beginAnimations:nil context:nil];

    [UIView setAnimationDuration:[CATransaction animationDuration]];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];

    self.frame = CGRectMake(self.frame.origin.x,
                        self.frame.origin.y,
                        self.frame.size.width,
                        newHeight);

    [(UITableView *)self.superview setTableHeaderView:self];

    [UIView commitAnimations];
}

- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context{
    [(UITableView *)self.superview setTableHeaderView:self];
}
avishic
fonte
5

Isso funcionou para mim no iOS 7 e 8. Este código está sendo executado no controlador de visualização de tabela.

[UIView animateWithDuration:0.3 animations:^{
    CGRect oldFrame = self.headerView.frame;
    self.headerView.frame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y, oldFrame.size.width, newHeight);
    [self.tableView setTableHeaderView:self.headerView];
}];
Darcy Rayner
fonte
4

É porque o configurador de tableHeaderView.

Você deve definir a altura UIView antes de definir o tableHeaderView. (Seria muito mais fácil se a Apple abrisse o código-fonte desta estrutura ...)

Thomas Decaux
fonte
4

No iOS 9 e inferior, tableHeaderView o layout não seria refeito após redimensioná-lo. Este problema foi resolvido no iOS 10.

Para resolver esse problema, basta fazer isso com o seguinte código:

self.tableView.tableHeaderView = self.tableView.tableHeaderView;
klaudz
fonte
2

No iOS 9.x, fazer isso viewDidLoadfunciona perfeitamente:

var frame = headerView.frame
frame.size.height = 11  // New size
headerView.frame = frame

headerViewé declarado como @IBOutlet var headerView: UIView!e conectado no storyboard, onde é colocado na parte superior de tableView, para funcionar como tableHeaderView.

Eneko Alonso
fonte
2

Isso é apenas para quando você usa o layout automático e definir translatesAutoresizingMaskIntoConstraints = false como uma visualização de cabeçalho personalizada.

A melhor e mais simples maneira é substituir intrinsicContentSize. UITableViewUsa internamente intrinsicContentSizepara decidir o tamanho do cabeçalho / rodapé. Depois de substituir intrinsicContentSizesua visualização personalizada, o que você precisa fazer é o seguinte

  1. configurar o layout personalizado da visualização do cabeçalho / rodapé (subvisualizações)
  2. invocar invalidateIntrinsicContentSize()
  3. invocar tableView.setNeedsLayout()etableView.layoutIfNeeded()

Então o UITableView cabeçalho / rodapé do será atualizado como você deseja. Não há necessidade de definir a visualização nula ou redefinir.

Uma coisa realmente interessante para o UITableView.tableHeaderViewou .tableFooterViewé que UIStackViewperde a capacidade de gerenciar o seu arrangedSubviews. Se você quiser usar UIStackViewcomo tableHeaderView ou tableFooterView, terá que incorporar stackView em um UIViewe substituir UIView's intrinsicContentSize.

Ryan
fonte
2

Para código testado swift 5

override func viewDidLayoutSubviews() {
      super.viewDidLayoutSubviews()

        guard let headerView = self.tblProfile.tableHeaderView else {
            return
        }

        let size = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)

        if headerView.frame.size.height != size.height {
            headerView.frame.size.height = size.height
            self.tblProfile.tableHeaderView = headerView
            self.tblProfile.layoutIfNeeded()
        }
    }

Nota: Você precisa fornecer todas as restrições de subvisualização de forma superior, inferior, inicial, final. Portanto, ele terá todo o tamanho necessário.

Referência retirada de: https://useyourloaf.com/blog/variable-height-table-view-header/

Hardik Thakkar
fonte
1

Definir a altura da propriedade de visualização do cabeçalho tableView.tableHeaderViewemviewDidLoad não parece trabalho, a altura vista cabeçalho ainda não muda conforme esperado.

Depois de lutar contra esse problema por muitas tentativas. Eu descobri que você pode alterar a altura invocando a lógica de criação da visualização do cabeçalho dentro do - (void)didMoveToParentViewController:(UIViewController *)parent método.

Portanto, o código de exemplo seria assim:

- (void)didMoveToParentViewController:(UIViewController *)parent {
    [super didMoveToParentViewController:parent];

    if ( _tableView.tableHeaderView == nil ) {
        UIView *header = [[[UINib nibWithNibName:@"your header view" bundle:nil] instantiateWithOwner:self options:nil] firstObject];

        header.frame = CGRectMake(0, 0, CGRectGetWidth([UIScreen mainScreen].bounds), HeaderViewHeight);

        [_tableView setTableHeaderView:header];
    }
}
Enix
fonte
0

Descobri que o inicializador initWithFrame de um UIView não honra corretamente o ret que eu transmito. Portanto, fiz o seguinte que funcionou perfeitamente:

 - (id)initWithFrame:(CGRect)aRect {

    CGRect frame = [[UIScreen mainScreen] applicationFrame];

    if ((self = [super initWithFrame:CGRectZero])) {

        // Ugly initialization behavior - initWithFrame will not properly honor the frame we pass
        self.frame = CGRectMake(0, 0, frame.size.width, 200);

        // ...
    }
}

A vantagem disso é que ele é melhor encapsulado em seu código de visualização.

Harald Schubert
fonte
Obter o quadro de UIScreené uma má ideia. Como você reutilizará sua visualização?
Zorayr
0

Implementei a mudança de altura animada do cabeçalho da mesa para expandir para a tela geral quando tocada. No entanto, o código pode ajudar em outros casos:

// Swift
@IBAction func tapped(sender: UITapGestureRecognizer) {

    self.tableView.beginUpdates()       // Required to update cells. 

    // Collapse table header to original height
    if isHeaderExpandedToFullScreen {   

        UIView.animateWithDuration(0.5, animations: { () -> Void in
            self.scrollView.frame.size.height = 110   // original height in my case is 110
        })

    }
    // Expand table header to overall screen            
    else {      
        let screenSize = self.view.frame           // "screen" size

        UIView.animateWithDuration(0.5, animations: { () -> Void in
            self.scrollView.frame.size.height = screenSize.height
        })
    }

    self.tableView.endUpdates()  // Required to update cells. 

    isHeaderExpandedToFullScreen= !isHeaderExpandedToFullScreen  // Toggle
}
Alexander Volkov
fonte
0

Cabeçalho de redimensionamento UITableView - UISearchBar com barra de escopo

Eu queria um UITableViewcom um UISearchBarcomo o cabeçalho da tabela, então tenho uma hierarquia parecida com esta

UITableView
  |
  |--> UIView
  |     |--> UISearchBar
  |
  |--> UITableViewCells

Métodos UISearchBarDelegate

Como foi afirmado em outro lugar, se você não setTableViewHeader depois de alterá-lo, nada acontecerá.

- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar
{
    searchBar.showsScopeBar = YES;
    [UIView animateWithDuration:0.2f animations:^{
        [searchBar sizeToFit];
        CGFloat height = CGRectGetHeight(searchBar.frame);

        CGRect frame = self.tableView.tableHeaderView.frame;
        frame.size.height = height;
        self.tableHeaderView.frame = frame;
        self.tableView.tableHeaderView = self.tableHeaderView;
    }];

    [searchBar setShowsCancelButton:YES animated:YES];
    return YES;
}

- (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar
{
    searchBar.showsScopeBar = NO;
    [UIView animateWithDuration:0.f animations:^{
        [searchBar sizeToFit];

        CGFloat height = CGRectGetHeight(searchBar.frame);

        CGRect frame = self.tableView.tableHeaderView.frame;
        frame.size.height = height;
        self.tableHeaderView.frame = frame;
        self.tableView.tableHeaderView = self.tableHeaderView;
    }];

    [searchBar setShowsCancelButton:NO animated:YES];
    return YES;
}
Cameron Lowell Palmer
fonte
0

Se headerView personalizado é projetado usando autolayout e headerView precisa ser atualizado após web-fetch ou tarefa preguiçosa semelhante. então no iOS-Swift eu fiz isso e atualizei meu headerView usando o código abaixo:

//to reload your cell data
self.tableView.reloadData()
dispatch_async(dispatch_get_main_queue(),{
// this is needed to update a specific tableview's headerview layout on main queue otherwise it's won't update perfectly cause reloaddata() is called
  self.tableView.beginUpdates()
  self.tableView.endUpdates()
} 
Rafat touqir Rafsun
fonte
0

Obviamente, agora a Apple deveria ter implementado UITableViewAutomaticDimension para tableHeaderView & tableFooterView ...

O seguinte parece funcionar para mim usando restrição (ões) de layout:

CGSize   s  = [ self  systemLayoutSizeFittingSize : UILayoutFittingCompressedSize ];
CGRect   f  = [ self  frame ];

f.size   = s;

[ self  setFrame : f ];
digitaldaemon
fonte
0

Se seu tableHeaderView for um webView ajustável de conteúdo, você pode tentar:

[self.webView.scrollView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew context:nil];

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    self.webView.height = self.webView.scrollView.contentSize.height;
    self.tableView.tableHeaderView = self.webView;
}

Testei no iOS9 e iOS11 e funcionou bem.

无 夜 之 星辰
fonte
-3

Você tentou [self.tableView reloadData]depois de mudar a altura?

codelógica
fonte
1
É sobre tableHeaderView, que é uma visualização estática. - [UITableView reloadData]destina-se apenas às visualizações dinâmicas (células) e também à seçãoHeaders que você obviamente pensou que se referiam ;)
Julian F. Weinert