Redimensionando o UITableView para caber no conteúdo

170

Estou criando um aplicativo que terá uma pergunta em uma UILabele uma resposta de múltipla escolha exibida em UITableViewcada linha mostrando uma múltipla escolha. As perguntas e as respostas variam, por isso preciso UITableViewque seja dinâmico em altura.

Gostaria de encontrar uma sizeToFitsolução alternativa para a mesa. Onde o quadro da tabela está definido para a altura de todo o seu conteúdo.

Alguém pode aconselhar sobre como eu posso conseguir isso?

MiMo
fonte
Para quem quer fazer uma coisa semelhante no OSX, consulte esta pergunta: stackoverflow.com/questions/11322139/...
Michael Teper
1
@MiMo oi, você pode explicar o que está errado com a abordagem "SizeToFit" por favor :)?
iamdavidlam
"Gostaria de encontrar uma solução" sizeToFit "" . Então você já tentou ou não o - sizeToFitmétodo do UIView ?
Slipp D. Thompson

Respostas:

155

Na verdade, eu mesmo encontrei a resposta.

Acabei de criar um novo CGRectpara o tableView.framecom oheight detable.contentSize.height

Isso define a altura do UITableViewpara o heightconteúdo. Como o código modifica a interface do usuário, não esqueça de executá-lo no thread principal:

dispatch_async(dispatch_get_main_queue(), ^{
        //This code will run in the main thread:
        CGRect frame = self.tableView.frame;
        frame.size.height = self.tableView.contentSize.height;
        self.tableView.frame = frame;
    });
MiMo
fonte
3
É possível ter problemas com esse método. Se você tiver uma lista grande, é possível que a altura do quadro corte algumas das células. Você pode ver isso acontecer se você tiver o salto ativado e role para baixo até ver algumas células desaparecerem da vista.
whyoz
Onde exatamente você especificou a altura? Você fez uma chamada para reloadData e redimensioná-lo depois?
James
27
Qual é o melhor lugar para colocar esse código? Eu tentei no viewDidLoad e provavelmente não funcionou porque os métodos de delegação da tableview ainda não haviam sido acionados. Qual método de delegação é o melhor?
precisa
4
Eu fiz isso é viewDidAppear, e parece ter funcionado. Note que o tableview pode ser um mais alto bit do que seu conteúdo, porque deixa um pouco de espaço para um cabeçalho seção e rodapé
Jameo
2
Descobri que viewDidAppear é um ótimo lugar para colocar as coisas que você quer que aconteça depois de todas as chamadas tableView cellForRowAtIndexPath iniciais terem terminado
rigdonmr
120

Solução Swift 5 e 4.2 sem KVO, DispatchQueue ou sem restrições de configuração.

Esta solução é baseada na resposta de Gulz .

1) Crie uma subclasse de UITableView:

import UIKit

final class ContentSizedTableView: UITableView {
    override var contentSize:CGSize {
        didSet {
            invalidateIntrinsicContentSize()
        }
    }

    override var intrinsicContentSize: CGSize {
        layoutIfNeeded()
        return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
    }
}

2) Adicione a UITableViewao seu layout e defina restrições por todos os lados. Defina a classe dele como ContentSizedTableView.

3) Você verá alguns erros, porque o Storyboard não leva intrinsicContentSizeem consideração nossa subclasse ' . Corrija isso abrindo o inspetor de tamanho e substituindo o intrinsicContentSize por um valor de espaço reservado. Esta é uma substituição para o tempo de design. Em tempo de execução, ele usará a substituição em nossa ContentSizedTableViewclasse


Atualização: código alterado para o Swift 4.2. Se você estiver usando uma versão anterior, use em UIViewNoIntrinsicMetricvez deUIView.noIntrinsicMetric

fl034
fonte
1
Resposta muito boa, funcionou para mim, obrigado. Porém, uma coisa - no meu caso, eu precisava mudar a classe do tableViewque estava IntrinsicTableViewno storyboard. Não precisei incorporar minha exibição de tabela em outra UIView.
Dvp.petrov 25/05
Sim você está certo. Eu atualizei minha resposta. O passo 2 pode ser lido como você disse. Claro que eu pretendia definir o tableViewpara IntrinsicTableView. Também UIViewnão é necessário um invólucro extra . Você pode usar qualquer vista pai existente é claro :)
fl034
1
Swift 4 Isso funcionou para mim muito bemtableView.sizeToFit()
Souf Rochdi
Sim, isso pode ser uma boa solução, se o seu conteúdo for estático. Com a minha abordagem que você pode usar Auto-Layout para incorporar um tableView com conteúdo dinâmico em outros pontos de vista e mantê-lo na altura cheia o tempo todo, sem pensar sobre o que coloca sizeToFit()as necessidades de ser chamado
fl034
2
Maravilhoso, obrigado!. Finalmente corresponder conteúdo pai e envoltório para tableview ...
Amber K
79

Solução Swift

Siga esses passos:

1- Defina a restrição de altura para a tabela no storyboard.

2- Arraste a restrição de altura do storyboard e crie @IBOutlet para ela no arquivo do controlador de exibição.

    @IBOutlet weak var tableHeight: NSLayoutConstraint!

3- Em seguida, você pode alterar a altura da tabela dinamicamente usando este código:

override func viewWillLayoutSubviews() {
    super.updateViewConstraints()
    self.tableHeight?.constant = self.table.contentSize.height
}

Atualizar

Se a última linha for cortada para você, tente chamar viewWillLayoutSubviews () na função da célula willDisplay:

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    self.viewWillLayoutSubviews()
}
Musa almatri
fonte
1
Trabalhou para mim !! thnx
Pushpa Y
5
Essa solução sempre interrompe a última linha do Xcode 8.3 para mim.
Jen C
@Jec C: Não é para mim quer
KMC
Trabalhou para mim também !!
Antony Raphel
1
Eu tive que colocar isso no cellForRowAtIndexPath: método. Se eu colocá-lo no método viewWillLayoutSubviews, a altura contentSize calculada é baseada na altura estimada, não na altura real do conteúdo.
Manu Mateos
21

Eu tentei isso no iOS 7 e funcionou para mim

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.tableView sizeToFit];
}
Alexey
fonte
3
Se o seu UITableView for dinâmico (o que significa que as informações são carregadas e enviadas rapidamente), você precisará colocá-lo em outro lugar, não no viewDidLoad. Para mim, eu o coloquei em cellForRowAtIndexPath. Também tive que alterar "tableView" para o nome da minha propriedade IBOutlet para evitar o antigo erro "property tableView not found".
Jeremytripp # 23/14
thx, teve problema com o redimensionamento tableview dentro popover, funcionou
Zaporozhchenko Oleksandr
19

Adicione um observador para a propriedade contentSize na exibição de tabela e ajuste o tamanho do quadro adequadamente

[your_tableview addObserver:self forKeyPath:@"contentSize" options:0 context:NULL];

depois no retorno de chamada:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
    {
         CGRect frame = your_tableview.frame;
         frame.size = your_tableview.contentSize;
         your_tableview.frame = frame;
    }

Espero que isso ajude você.

Anooj VM
fonte
2
Você deve adicionar mais uma coisa. Você deve remover o observador para a visualização da tabela. Porque ele falhará se a célula for desalocada.
Mahesh Agrawal
12

Eu tinha uma exibição de tabela dentro da exibição de rolagem e tive que calcular a altura do tableView e redimensioná-la de acordo. Esses são os passos que eu tomei:

0) adicione um UIView ao seu scrollView (provavelmente funcionará sem esta etapa, mas eu fiz isso para evitar possíveis conflitos) - essa será uma exibição de conteúdo para sua exibição de tabela. Se você seguir esta etapa, defina as bordas das visualizações exatamente como as da visualização de tablaturas.

1) crie uma subclasse de UITableView:

class IntrinsicTableView: UITableView {

    override var contentSize:CGSize {
        didSet {
            self.invalidateIntrinsicContentSize()
        }
    }

    override var intrinsicContentSize: CGSize {
        self.layoutIfNeeded()
        return CGSize(width: UIViewNoIntrinsicMetric, height: contentSize.height)
    }

}

2) defina a classe de uma exibição de tabela no Storyboard para IntrinsicTableView: captura de tela: http://joxi.ru/a2XEENpsyBWq0A

3) Defina o heightConstraint para a sua exibição de tabela

4) arraste o IBoutlet da sua tabela para o seu ViewController

5) arraste o IBoutlet da restrição de altura da sua tabela para o seu ViewController

6) adicione este método ao seu ViewController:

override func viewWillLayoutSubviews() {
        super.updateViewConstraints()
        self.yourTableViewsHeightConstraint?.constant = self.yourTableView.intrinsicContentSize.height
    }

Espero que isto ajude

Gulz
fonte
Veja minha resposta, que é baseada em sua subclasse, mas não precisa de nenhuma configuração de restrições de altura.
Fl034 5/02
@ fl034 fornecer link?
Gulz
@ fl034 Eu não acho que você pode evitar o uso de restrições quando seus elementos são incorporados na Scroll View) Meu caso é com o ScrollView
Gulz
Eu também tenho a minha vista de tabela dentro de uma visão de rolagem e ele funciona perfeitamente :)
fl034
funciona para mim no iOS 13 / Xcode 11 Você colocou um fim a 3 dias de sofrimento gentil senhor, obrigado
Mehdi S.
8

Caso você não queira acompanhar o tamanho do conteúdo da visualização da tabela, você pode achar útil essa subclasse.

protocol ContentFittingTableViewDelegate: UITableViewDelegate {
    func tableViewDidUpdateContentSize(_ tableView: UITableView)
}

class ContentFittingTableView: UITableView {

    override var contentSize: CGSize {
        didSet {
            if !constraints.isEmpty {
                invalidateIntrinsicContentSize()
            } else {
                sizeToFit()
            }

            if contentSize != oldValue {
                if let delegate = delegate as? ContentFittingTableViewDelegate {
                    delegate.tableViewDidUpdateContentSize(self)
                }
            }
        }
    }

    override var intrinsicContentSize: CGSize {
        return contentSize
    }

    override func sizeThatFits(_ size: CGSize) -> CGSize {
        return contentSize
    }
}
xinatanil
fonte
1
Para rápida 3, intrinsicContentSize tornou-se um var, comooverride public var intrinsicContentSize: CGSize { //... return someCGSize }
xaphod
não está trabalhando para mim. ainda está escondendo o fundo da mesa
Gulz 02/02
5

Caso seu contentSize não esteja correto, isso se deve ao valor estimado de Rowowe (automático), use-o antes

tableView.estimatedRowHeight = 0;

fonte: https://forums.developer.apple.com/thread/81895

Yohan Dahmani
fonte
4

Swift 3, iOS 10.3

Solução 1: Basta colocarself.tableview.sizeToFit()emcellForRowAt indexPathfunção. Defina a altura da tableview mais alta do que você precisa. Esta é uma boa solução se você não tiver visualizações abaixo da visualização da tabela. No entanto, se você tiver, a restrição inferior da visualização da tabela não será atualizada (não tentei corrigi-la porque criei a solução 2)

Exemplo:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if let cell = tableView.dequeueReusableCell(withIdentifier: "TestCell", for: indexPath) as? TestCell {
        cell.configureCell(data: testArray[indexPath.row])
        self.postsTableView.sizeToFit()
        return cell
    }

    return UITableViewCell()
}

Solução 2: defina a restrição de altura da tableview no storyboard e arraste-a para o ViewController. Se você conhece a altura média da sua célula e sabe quantos elementos sua matriz contém, você pode fazer algo assim:

tableViewHeightConstraint.constant = CGFloat(testArray.count) * 90.0     // Let's say 90 is the average cell height

*EDITAR:

Depois de todas as soluções que tentei e todas estavam corrigindo algo, mas não completamente, esta é a resposta que explica e corrige esse problema completamente.

Đorđe Nilović
fonte
Esta é uma solução muito simples, ele trabalhou para mim, obrigado @Dorde Nilovic
R. Mohan
1

A resposta de Mimo e resposta de Anooj VM ambos são impressionantes, mas há um pequeno problema se você tem uma lista grande, é possível que a altura do quadro vai corte algumas das suas células.

Assim. Eu modifiquei a resposta um pouco:

dispatch_async(dispatch_get_main_queue()) {
    //This code will run in the main thread:
    CGFloat newHeight=self.tableView.contentSize.height;
    CGFloat screenHeightPermissible=(self.view.bounds.size.height-self.tableView.frame.origin.y);
    if (newHeight>screenHeightPermissible)
    {
        //so that table view remains scrollable when 'newHeight'  exceeds the screen bounds
        newHeight=screenHeightPermissible;
    }

    CGRect frame = self.tableView.frame;
    frame.size.height = newHeight;
    self.tableView.frame = frame;
}
SandeepAggarwal
fonte
1

Existe uma maneira muito melhor de fazer isso se você usar o AutoLayout: altere a restrição que determina a altura. Apenas calcule a altura do conteúdo da tabela, encontre a restrição e altere-a. Aqui está um exemplo (supondo que a restrição que determina a altura da sua tabela seja na verdade uma restrição de altura com a relação "Igual"):

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    for constraint in tableView.constraints {
        if constraint.firstItem as? UITableView == tableView {
            if constraint.firstAttribute == .height {
                constraint.constant = tableView.contentSize.height
            }
        }
    }
}
ChrisB
fonte
Você pode melhorar isso. Adicione uma restrição adicional à sua tableview, digamos, altura <= 10000. Use a tecla Control arrastada do Interface Builder para o arquivo .h para tornar essa restrição uma propriedade do seu controlador de exibição. Em seguida, você pode evitar a iteração através de restrições e pesquisa. Apenas definido diretamentemyTableHeightConstraint.constant = tableView.contentSize.height
RobP:
1

Isso funciona para mim usando o Layout automático, com uma exibição de tabela com apenas uma seção.

func getTableViewContentHeight(tableView: UITableView) -> CGFloat {
        tableView.bounds = CGRect(x: 0, y: 0, width: 300, height: 40)
        let rows = tableView.numberOfRows(inSection: 0)
        var height = CGFloat(0)
        for n in 0...rows - 1 {
            height = height + tableView.rectForRow(at: IndexPath(row: n, section: 0)).height
        }
        return height
    }

Eu chamo essa função ao configurar o Layout automático (a amostra aqui usa o SnapKit, mas você entendeu):

    let height = getTableViewContentHeight(tableView: myTableView)
    myTableView.snp.makeConstraints {
        ...
        ...
        $0.height.equalTo(height)
    }

Quero que o UITableView seja tão alto quanto a altura combinada das células; Eu atravesso as células e acumulo a altura total das células. Como o tamanho da exibição da tabela é CGRect.zero neste momento, preciso definir os limites para poder respeitar as regras de layout automático definidas pela célula. Defino o tamanho como um valor arbitrário que deve ser grande o suficiente. O tamanho real será calculado posteriormente pelo sistema Auto Layout.

André
fonte
1

Eu fiz de uma maneira um pouco diferente. Na verdade, o meu TableView estava dentro do scrollview, então eu tive que dar uma restrição de altura como 0.

Então, no tempo de execução, fiz as seguintes alterações,

       func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
            self.viewWillLayoutSubviews()
       }
    
       override func viewWillLayoutSubviews() {
            super.updateViewConstraints()
             DispatchQueue.main.async {
               self.tableViewHeightConstraint?.constant = self.myTableView.contentSize.height
               self.view.layoutIfNeeded()
          }
       }
Niki
fonte
0

Como uma extensão da resposta da Anooj VM, sugiro o seguinte para atualizar o tamanho do conteúdo apenas quando ele for alterado.

Essa abordagem também desabilita a rolagem corretamente e suporta listas e rotação maiores . Não há necessidade de dispatch_async porque as alterações contentSize são despachadas no encadeamento principal.

- (void)viewDidLoad {
        [super viewDidLoad];
        [self.tableView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:NULL]; 
}


- (void)resizeTableAccordingToContentSize:(CGSize)newContentSize {
        CGRect superviewTableFrame  = self.tableView.superview.bounds;
        CGRect tableFrame = self.tableView.frame;
        BOOL shouldScroll = newContentSize.height > superviewTableFrame.size.height;
        tableFrame.size = shouldScroll ? superviewTableFrame.size : newContentSize;
        [UIView animateWithDuration:0.3
                                    delay:0
                                    options:UIViewAnimationOptionCurveLinear
                                    animations:^{
                            self.tableView.frame = tableFrame;
        } completion: nil];
        self.tableView.scrollEnabled = shouldScroll;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    if ([change[NSKeyValueChangeKindKey] unsignedIntValue] == NSKeyValueChangeSetting &&
        [keyPath isEqualToString:@"contentSize"] &&
        !CGSizeEqualToSize([change[NSKeyValueChangeOldKey] CGSizeValue], [change[NSKeyValueChangeNewKey] CGSizeValue])) {
        [self resizeTableAccordingToContentSize:[change[NSKeyValueChangeNewKey] CGSizeValue]];
    } 
}

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
    [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
    [self resizeTableAccordingToContentSize:self.tableView.contentSize]; }

- (void)dealloc {
    [self.tableView removeObserver:self forKeyPath:@"contentSize"];
}
Ramiro
fonte
0

versão objc do Musa almatri

(void)viewWillLayoutSubviews
{
    [super updateViewConstraints];
    CGFloat desiredHeight = self.tableView.contentSize.height;
    // clamp desired height, if needed, and, in that case, leave scroll Enabled
    self.tableHeight.constant = desiredHeight;
    self.tableView.scrollEnabled = NO;
}
Anton Tropashko
fonte
0

Você pode experimentar este costume AGTableView

Para definir uma restrição de altura do TableView Usando o storyboard ou programaticamente. (Essa classe busca automaticamente uma restrição de altura e define a altura da visualização do conteúdo como sua altura da visualização da tabela).

class AGTableView: UITableView {

    fileprivate var heightConstraint: NSLayoutConstraint!

    override init(frame: CGRect, style: UITableViewStyle) {
        super.init(frame: frame, style: style)
        self.associateConstraints()
    }

    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.associateConstraints()
    }

    override open func layoutSubviews() {
        super.layoutSubviews()

        if self.heightConstraint != nil {
            self.heightConstraint.constant = self.contentSize.height
        }
        else{
            self.sizeToFit()
            print("Set a heightConstraint to Resizing UITableView to fit content")
        }
    }

    func associateConstraints() {
        // iterate through height constraints and identify

        for constraint: NSLayoutConstraint in constraints {
            if constraint.firstAttribute == .height {
                if constraint.relation == .equal {
                    heightConstraint = constraint
                }
            }
        }
    }
}

Nota Se houver algum problema para definir uma altura, então yourTableView.layoutSubviews().

AshvinGudaliya
fonte
0

Com base na resposta de fl034 . Mas para usuários do Xamarin.iOS :

    [Register("ContentSizedTableView")]
    public class ContentSizedTableView : UITableView
    {
        public ContentSizedTableView(IntPtr handle) : base(handle)
        {
        }

        public override CGSize ContentSize { get => base.ContentSize; set { base.ContentSize = value; InvalidateIntrinsicContentSize(); } }
        public override CGSize IntrinsicContentSize
        {
            get
            {
                this.LayoutIfNeeded();
                return new CGSize(width: NoIntrinsicMetric, height: ContentSize.Height);
            }
        }
    }
Jelle
fonte
0

Minha implementação do Swift 5 é definir a alta restrição do tableView para o tamanho do seu conteúdo ( contentSize.height). Este método supõe que você esteja usando o layout automático. Este código deve ser colocado dentro do cellForRowAtmétodo tableView.

tableView.heightAnchor.constraint(equalToConstant: tableView.contentSize.height).isActive = true
Dominic Egginton
fonte
-1

Se você deseja que sua tabela seja dinâmica, será necessário usar uma solução com base no conteúdo da tabela, conforme detalhado acima. Se você simplesmente deseja exibir uma tabela menor, pode usar uma exibição de contêiner e incorporar um UITableViewController nela - o UITableView será redimensionado de acordo com o tamanho do contêiner.

Isso evita muitos cálculos e chamadas para o layout.

green_knight
fonte
1
Não use a palavra aboveporque as respostas podem ser embaralhadas.
Nik Kov
-3

Mu solução para isso no rápido 3 : Chame este método emviewDidAppear

func UITableView_Auto_Height(_ t : UITableView)
{
        var frame: CGRect = t.frame;
        frame.size.height = t.contentSize.height;
        t.frame = frame;        
}
Brahimm
fonte