Cambiar el tamaño de UITableView para que se ajuste al contenido

170

Estoy creando una aplicación que tendrá una pregunta en UILabelay una respuesta de opción múltiple en UITableViewcada fila mostrando una opción múltiple. Las preguntas y respuestas variarán, así que necesito UITableViewque sea dinámico en altura.

Me gustaría encontrar una sizeToFitsolución para la mesa. Donde el marco de la tabla se establece a la altura de todo su contenido.

¿Alguien puede aconsejar sobre cómo puedo lograr esto?

MiMo
fuente
Para cualquiera que quiera hacer algo similar en OSX, vea esta pregunta: stackoverflow.com/questions/11322139/…
Michael Teper
1
@MiMo hola, ¿puedes explicar qué hay de malo con el enfoque "sizeToFit" por favor? :)
iamdavidlam
"Me gustaría encontrar una solución" sizeToFit " . ¿Has probado o no has probado el - sizeToFitmétodo de UIView ?
Slipp D. Thompson,

Respuestas:

155

En realidad encontré la respuesta yo mismo.

Acabo de crear una nueva CGRectpara tableView.frameel heightdetable.contentSize.height

Eso establece la altura de UITableViewla heightde su contenido. Como el código modifica la IU, no olvide ejecutarlo en el hilo 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
fuente
3
Es posible tener problemas con este método. Si tiene una lista grande, es posible que la altura del marco corte algunas de sus celdas. Puede ver que esto suceda si tiene habilitado el rebote y desplácese hacia abajo para ver que algunas celdas rebotan fuera de la vista.
whyoz
¿Dónde exactamente especificaste la altura? ¿Hizo una llamada para recargarData y cambiar su tamaño después de las palabras?
James
27
¿Dónde está el mejor lugar para poner este código? Lo probé en viewDidLoad y presumiblemente no funcionó porque los métodos de delegado de vista de tabla todavía no se habían activado. ¿Qué método delegado es mejor?
Kyle Clegg el
44
Hice esto es viewDidAppear, y parece haber funcionado. Tenga en cuenta que la vista de tabla puede ser un poco más alta que su contenido porque deja un poco de espacio para el encabezado y pie de página de la sección
Jameo
2
Descubrí que viewDidAppear es un gran lugar para colocar cosas que desea que sucedan después de que todas las llamadas iniciales de tableView cellForRowAtIndexPath hayan finalizado
rigdonmr
120

Solución Swift 5 y 4.2 sin KVO, DispatchQueue o sin establecer restricciones usted mismo.

Esta solución se basa en la respuesta de Gulz .

1) Crear una subclase 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) Agregue un UITableViewa su diseño y establezca restricciones en todos los lados. Establezca la clase a ContentSizedTableView.

3) Debería ver algunos errores, porque Storyboard no tiene intrinsicContentSizeen cuenta nuestra subclase . Solucione esto abriendo el inspector de tamaño y anulando intrinsicContentSize a un valor de marcador de posición. Esta es una anulación para el tiempo de diseño. En tiempo de ejecución usará la anulación en nuestra ContentSizedTableViewclase


Actualización: código modificado para Swift 4.2. Si está utilizando una versión anterior, use en UIViewNoIntrinsicMetriclugar deUIView.noIntrinsicMetric

fl034
fuente
1
Muy buena respuesta, funcionó para mí, gracias. Sin embargo, una cosa: en mi caso, necesitaba cambiar la clase del tableViewpara estar IntrinsicTableViewen el guión gráfico. No necesitaba insertar mi vista de tabla dentro de otra UIView.
dvp.petrov
Si, tienes razón. Actualicé mi respuesta. El paso 2 podría leerse como dijiste. Por supuesto que quería establecer el tableViewa IntrinsicTableView. Además, UIViewno es necesario un envoltorio adicional . Puede utilizar cualquier vista principal existente, por supuesto :)
fl034
1
Swift 4 Esto funcionó para mí bientableView.sizeToFit()
Souf ROCHDI
Sí, esa puede ser una buena solución, si su contenido es estático. Con mi enfoque, puede usar el diseño automático para incrustar una vista de tabla con contenido dinámico en otras vistas y mantenerlo en toda su altura todo el tiempo, sin pensar en qué lugares sizeToFit()deben llamarse
fl034
2
¡Maravilloso, gracias!. Finalmente haga coincidir el contenido principal y el ajuste para la vista de tabla ...
Amber K
79

Solución rápida

Sigue estos pasos:

1- Establezca la restricción de altura para la tabla desde el guión gráfico.

2- Arrastre la restricción de altura desde el guión gráfico y cree @IBOutlet para ella en el archivo del controlador de vista.

    @IBOutlet weak var tableHeight: NSLayoutConstraint!

3- Luego puedes cambiar la altura de la mesa dinámicamente usando este código:

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

Actualizar

Si la última fila se corta por usted, intente llamar a viewWillLayoutSubviews () en la función de celda willDisplay:

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    self.viewWillLayoutSubviews()
}
Musa almatri
fuente
1
¡¡Trabajó para mi!! thnx
Pushpa Y
55
Esta solución siempre cortó la última fila en Xcode 8.3 para mí.
Jen C
@Jec C: Tampoco para mí
KMC
Funcionó para mí también!
Antony Raphel
1
Tuve que poner esto en el método cellForRowAtIndexPath: Si lo pongo en el método viewWillLayoutSubviews, la altura calculada de contentSize se basa en la altura estimada, no en la altura real del contenido.
Manu Mateos el
21

He intentado esto en iOS 7 y funcionó para mí.

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.tableView sizeToFit];
}
Alexey
fuente
3
Si su UITableView es dinámico (lo que significa que la información se carga al instante), deberá colocarlo en otro lugar, no en viewDidLoad. Para mí, lo puse en cellForRowAtIndexPath. También tuve que cambiar "tableView" al nombre de mi propiedad IBOutlet para evitar el antiguo error "property tableView not found".
jeremytripp
gracias, tuve problemas para cambiar el tamaño de la vista de la tabla dentro de popover, funcionó
Zaporozhchenko Oleksandr
19

Agregue un observador para la propiedad contentSize en la vista de tabla y ajuste el tamaño del marco en consecuencia

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

luego en la devolución de llamada:

- (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 esto te ayudará.

Anooj VM
fuente
2
Deberías agregar una cosa más. Debe eliminar el observador para la vista de tabla. Porque se bloqueará si la célula se desasigna.
Mahesh Agrawal
12

Tenía una vista de tabla dentro de la vista de desplazamiento y tuve que calcular la altura de tableView y cambiar su tamaño en consecuencia. Esos son los pasos que he tomado:

0) agregue una UIView a su scrollView (probablemente funcionará sin este paso, pero lo hice para evitar posibles conflictos): esta será una vista de contenedor para su vista de tabla. Si toma este paso, configure los bordes de las vistas directamente a los de la vista de tabla.

1) crea una subclase 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) establezca la clase de una vista de tabla en Storyboard en IntrinsicTableView: captura de pantalla: http://joxi.ru/a2XEENpsyBWq0A

3) Establezca heightConstraint en su vista de tabla

4) arrastre el IBoutlet de su tabla a su ViewController

5) arrastre el IBoutlet de la restricción de altura de su tabla a su ViewController

6) agregue este método a su ViewController:

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

Espero que esto ayude

Gulz
fuente
Vea mi respuesta que se basa en su subclase pero no necesita ninguna configuración de restricciones de altura.
fl034
@ fl034 proporcionar enlace?
Gulz
@ fl034 No creo que pueda evitar el uso de restricciones cuando sus elementos están incrustados en la Vista de desplazamiento) Mi caso es con el ScrollView
Gulz
También tengo mi vista de tabla dentro de una vista de desplazamiento y funciona perfectamente :)
fl034
funciona para mí en iOS 13 / Xcode 11 Has puesto fin a 3 días de sufrimiento señor amable, gracias
Mehdi S.
8

En caso de que no desee realizar un seguimiento de los cambios en el tamaño del contenido de la vista de tabla, puede encontrar esta subclase útil.

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
fuente
1
Para Swift 3, intrinsicContentSize se ha convertido en una override public var intrinsicContentSize: CGSize { //... return someCGSize }
variable
no funciona para mi todavía oculta el fondo de la mesa
Gulz
4

Swift 3, iOS 10.3

Solución 1: simplemente ponerself.tableview.sizeToFit()encellForRowAt indexPathfunción. Asegúrese de establecer la altura de la vista de tabla más alta de lo que necesita. Esta es una buena solución si no tiene vistas debajo de la vista de tabla. Sin embargo, si tiene, la restricción de la vista de tabla inferior no se actualizará (no intenté solucionarlo porque se me ocurrió la solución 2)

Ejemplo:

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

Solución 2: establezca la restricción de altura de la vista de tabla en el guión gráfico y arrástrela al ViewController. Si conoce la altura promedio de su celda y sabe cuántos elementos contiene su matriz, puede hacer algo como esto:

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

*EDITAR:

Después de todas las soluciones que probé y cada una de ellas estaba arreglando algo, pero no completamente, esta es la respuesta que explica y soluciona este problema por completo.

Đorđe Nilović
fuente
Esta es una solución muy simple, funcionó para mí, gracias @Dorde Nilovic
R. Mohan
1

La respuesta de Mimo y respuesta Anooj VM 's tanto son impresionantes, pero hay un pequeño problema si usted tiene una lista grande, es posible que la altura del marco será cortar otras algunas de sus células.

Entonces. He modificado un poco la respuesta:

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
fuente
1

Hay una manera mucho mejor de hacerlo si usa AutoLayout: cambie la restricción que determina la altura. Simplemente calcule la altura del contenido de su tabla, luego encuentre la restricción y cámbiela. Aquí hay un ejemplo (suponiendo que la restricción que determina la altura de su tabla es en realidad una restricción de altura con relación "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
fuente
Puedes mejorar esto. Agregue una restricción adicional en su vista de tabla, por ejemplo, altura <= 10000. Use el arrastre de control desde Interface Builder a su archivo .h para hacer que esta restricción sea una propiedad de su controlador de vista. Entonces puede evitar iterar a través de restricciones y búsquedas. Directamente establecidomyTableHeightConstraint.constant = tableView.contentSize.height
RobP
1

Esto funciona para mí usando Auto Layout, con una vista de tabla con solo una sección.

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
    }

Llamo a esta función cuando configuro el diseño automático (la muestra aquí usa SnapKit, pero se entiende la idea):

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

Quiero que UITableView solo sea tan alto como la altura combinada de las celdas; Recorro las celdas y acumulo la altura total de las celdas. Dado que el tamaño de la vista de tabla es CGRect.zero en este punto, necesito establecer los límites para poder respetar las reglas de Diseño automático definidas por la celda. Establezco el tamaño en un valor arbitrario que debería ser lo suficientemente grande. El tamaño real será calculado más tarde por el sistema de diseño automático.

André
fuente
1

Lo hice de una manera un poco diferente, en realidad mi TableView estaba dentro de scrollview, así que tuve que dar la restricción de altura como 0.

Luego, en tiempo de ejecución hice los siguientes cambios,

       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
fuente
0

Como una extensión de la respuesta de Anooj VM, sugiero lo siguiente para actualizar el tamaño del contenido solo cuando cambie.

Este enfoque también deshabilita el desplazamiento correctamente y admite listas más grandes y rotación . No hay necesidad de dispatch_async porque los cambios de contentSize se envían en el hilo 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
fuente
0

versión objc de 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
fuente
0

Puedes probar esta costumbre AGTableView

Para establecer una restricción de altura de TableView usando el guión gráfico o mediante programación. (Esta clase busca automáticamente una restricción de altura y establece la altura de la vista de contenido a la altura de la vista de su tabla).

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 Si hay algún problema para establecer una altura, entonces yourTableView.layoutSubviews().

AshvinGudaliya
fuente
0

Basado en la respuesta de fl034 . Pero para los usuarios de 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
fuente
0

Mi implementación de Swift 5 es establecer la restricción de altura de tableView al tamaño de su contenido ( contentSize.height). Este método supone que está utilizando el diseño automático. Este código debe colocarse dentro del cellForRowAtmétodo tableView.

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

Si desea que su tabla sea dinámica, necesitará usar una solución basada en el contenido de la tabla como se detalla anteriormente. Si simplemente desea mostrar una tabla más pequeña, puede usar una vista de contenedor e incrustar un UITableViewController en ella; el UITableView cambiará de tamaño según el tamaño del contenedor.

Esto evita muchos cálculos y llamadas al diseño.

green_knight
fuente
1
No use palabras aboveporque las respuestas podrían barajarse.
Nik Kov
-3

Solución de Mu para esto en Swift 3 : llame a este método enviewDidAppear

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