¿Es posible usar AutoLayout con tableHeaderView de UITableView?

97

Desde que descubrí AutoLayoutque lo uso en todas partes, ahora intento usarlo con un tableHeaderView.

Hice una subclassde UIViewagregado todo (etiquetas etc ...) que quería con sus limitaciones, y luego añadí esta CustomViewa la UITableView' tableHeaderView.

Todo funciona bien, excepto las UITableViewsiempre aparece por encima de la CustomView, por encima me refiero al CustomViewdecir bajo el UITableViewpor lo que no puede ser visto!

Parece que no importa lo que haga, el heightdel UITableView' tableHeaderViewes siempre 0 (también lo es el ancho, xey).

Mi pregunta: ¿es posible lograr esto sin configurar el marco manualmente ?

EDITAR: El CustomView' subviewque estoy usando tiene estas restricciones:

_title = [[UILabel alloc]init];
_title.text = @"Title";
[self addSubview:_title];
[_title keep:[KeepTopInset rules:@[[KeepEqual must:5]]]]; // title has to stay at least 5 away from the supperview Top
[_title keep:[KeepRightInset rules:@[[KeepMin must:5]]]];
[_title keep:[KeepLeftInset rules:@[[KeepMin must:5]]]];
[_title keep:[KeepBottomInset rules:@[[KeepMin must:5]]]];

Estoy usando una biblioteca práctica 'KeepLayout' porque escribir restricciones manualmente toma una eternidad y demasiadas líneas para una sola restricción, pero los métodos son autoexplicativos.

Y UITableViewtiene estas limitaciones:

_tableView = [[UITableView alloc]init];
_tableView.translatesAutoresizingMaskIntoConstraints = NO;
_tableView.delegate = self;
_tableView.dataSource = self;
_tableView.backgroundColor = [UIColor clearColor];
[self.view addSubview:_tableView];
[_tableView keep:[KeepTopInset rules:@[[KeepEqual must:0]]]];// These 4 constraints make the UITableView stays 0 away from the superview top left right and bottom.
[_tableView keep:[KeepLeftInset rules:@[[KeepEqual must:0]]]];
[_tableView keep:[KeepRightInset rules:@[[KeepEqual must:0]]]];
[_tableView keep:[KeepBottomInset rules:@[[KeepEqual must:0]]]];

_detailsView = [[CustomView alloc]init];
_tableView.tableHeaderView = _detailsView;

No sé si tengo que establecer algunas restricciones directamente en el CustomView, creo que la altura de CustomView está determinada por las restricciones en el UILabel"título" en él.

EDITAR 2: Después de otra investigación, parece que la altura y el ancho de CustomView se calcularon correctamente, pero la parte superior de CustomView todavía está al mismo nivel que la parte superior de UITableView y se mueven juntos cuando me desplazo.

Es un secreto
fuente
Sí, es posible. ¿Puedes mostrar el código que estás usando? Es difícil aconsejar sin saber qué restricciones ha configurado en la vista del encabezado.
jrturton
Una manera fácil de lograr esto es agregar esa vista en IB a tableView ... simplemente cree la vista en la misma escena que contiene la vista de tabla y arrástrela a la tabla.
Mariam K.
Estoy tratando de evitar IB lo más que puedo, hasta ahora no tuve que usarlo, si no puedo hacer que funcione, lo intentaré con IB
ItsASecret
1
Apple aconseja a los desarrolladores que utilicen IB siempre que sea posible cuando se trata de diseño automático. Realmente ayuda a evitar muchos problemas de inconsistencia.
Mariam K.
La verdadera solución completa de diseño automático está aquí
malex

Respuestas:

134

Pregunté y respondí una pregunta similar aquí . En resumen, agrego el encabezado una vez y lo uso para encontrar la altura requerida. Luego, esa altura se puede aplicar al encabezado, y el encabezado se establece una segunda vez para reflejar el cambio.

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.header = [[SCAMessageView alloc] init];
    self.header.titleLabel.text = @"Warning";
    self.header.subtitleLabel.text = @"This is a message with enough text to span multiple lines. This text is set at runtime and might be short or long.";

    //set the tableHeaderView so that the required height can be determined
    self.tableView.tableHeaderView = self.header;
    [self.header setNeedsLayout];
    [self.header layoutIfNeeded];
    CGFloat height = [self.header systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;

    //update the header's frame and set it again
    CGRect headerFrame = self.header.frame;
    headerFrame.size.height = height;
    self.header.frame = headerFrame;
    self.tableView.tableHeaderView = self.header;
}

Si tiene etiquetas de varias líneas, esto también se basa en la configuración de la vista personalizada PreferredMaxLayoutWidth de cada etiqueta:

- (void)layoutSubviews
{
    [super layoutSubviews];

    self.titleLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.titleLabel.frame);
    self.subtitleLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.subtitleLabel.frame);
}

o quizás más en general:

override func layoutSubviews() {
    super.layoutSubviews()  
    for view in subviews {
        guard let label = view as? UILabel where label.numberOfLines == 0 else { continue }
        label.preferredMaxLayoutWidth = CGRectGetWidth(label.frame)
    }
}

Actualización de enero de 2015

Desafortunadamente, esto todavía parece necesario. Aquí hay una versión rápida del proceso de diseño:

tableView.tableHeaderView = header
header.setNeedsLayout()
header.layoutIfNeeded()
header.frame.size = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
tableView.tableHeaderView = header

Me resultó útil mover esto a una extensión en UITableView:

extension UITableView {
    //set the tableHeaderView so that the required height can be determined, update the header's frame and set it again
    func setAndLayoutTableHeaderView(header: UIView) {
        self.tableHeaderView = header
        header.setNeedsLayout()
        header.layoutIfNeeded()
        header.frame.size = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
        self.tableHeaderView = header
    }
}

Uso:

let header = SCAMessageView()
header.titleLabel.text = "Warning"
header.subtitleLabel.text = "Warning message here."
tableView.setAndLayoutTableHeaderView(header)
Ben Packard
fuente
8
Una alternativa al uso preferredMaxLayoutWidthes agregar una restricción de ancho (igual al ancho de la vista de tabla) en la vista del encabezado antes de usar systemLayoutSizeFittingSize:.
Benjohn
2
NOTA: si experimenta que el encabezado está encima de las primeras celdas, entonces olvidó restablecer la propiedad del encabezado aself.tableView.tableHeaderView
Laszlo
7
A menudo me sorprende lo complicado que puede ser hacer algo completamente trivial como esto.
TylerJames
5
NOTA: Si necesita obtener el ancho exacto como tableView, debe obtener la altura con la prioridad horizontal requeridalet height = header.systemLayoutSizeFittingSize(CGSizeMake(CGRectGetWidth(self.bounds), 0), withHorizontalFittingPriority: UILayoutPriorityRequired, verticalFittingPriority: UILayoutPriorityFittingSizeLevel).height
JakubKnejzlik
3
Just Use header.setNeedsLayout() header.layoutIfNeeded() header.frame.size = header.systemLayoutSizeFitting(UILayoutFittingCompressedSize) self.tableHeaderView = headerfuncionaría en iOS 10.2
Kesong Xie
24

No pude agregar una vista de encabezado usando restricciones (en el código). Si le doy a mi vista una restricción de ancho y / o alto, aparece un bloqueo con el mensaje que dice:

 "terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Auto Layout still required after executing -layoutSubviews. UITableView's implementation of -layoutSubviews needs to call super."

Cuando agrego una vista en el guión gráfico a la vista de mi tabla, no muestra restricciones y funciona bien como vista de encabezado, por lo que creo que la ubicación de la vista de encabezado no se realiza mediante restricciones. No parece comportarse como una vista normal en ese sentido.

El ancho es automáticamente el ancho de la vista de tabla, lo único que necesita establecer es la altura; los valores de origen se ignoran, por lo que no importa lo que ingrese para esos. Por ejemplo, esto funcionó bien (al igual que 0,0,0,80 para el rect):

UIView *headerview = [[UIView alloc] initWithFrame:CGRectMake(1000,1000, 0, 80)];
headerview.backgroundColor = [UIColor yellowColor];
self.tableView.tableHeaderView = headerview;
rdelmar
fuente
También tuve esa excepción, pero al agregar una categoría a UITableView lo solucionó, lo encontré en esa respuesta: stackoverflow.com/questions/12610783/…
ItsASecret
Todavía voy a intentar lo que sugieres, pero mañana por la mañana, es la 1:34 am. Me voy a la cama, ¡muchas gracias por tomarte el tiempo de responder! (Pero realmente quiero no especificar una altura, me gustaría que se calcule según las restricciones que configuré en la etiqueta en CustomView)
ItsASecret
Lo probé y sí, configurar el marco funciona, pero estaba buscando una manera de evitar configurar el marco, seguiré buscando y si no encuentro nada más, aceptaré tu respuesta
ItsASecret
1
Recibo esta excepción (actualmente probando 7.1) si la vista de encabezado agregada tiene translatesAutoresizingMaskIntoConstraints = NO . Activar la traducción evita el error: sospecho que a UITableViewpartir de la versión 7.1 no se intenta crear un diseño automático de su vista de encabezado y quiere algo con el marco preestablecido.
Benjohn
16

Vi muchos métodos aquí haciendo tantas cosas innecesarias, pero no necesitas tanto para usar el diseño automático en la vista del encabezado. Solo tiene que crear su archivo xib, poner sus restricciones e instanciarlo así:

func loadHeaderView () {
        guard let headerView = Bundle.main.loadNibNamed("CourseSearchHeader", owner: self, options: nil)?[0] as? UIView else {
            return
        }
        headerView.autoresizingMask = .flexibleWidth
        headerView.translatesAutoresizingMaskIntoConstraints = true
        tableView.tableHeaderView = headerView
    }
Ramón Vasconcelos
fuente
Esto también nos funcionó en iOS 11 con un encabezado de altura dinámico con etiquetas de varias líneas.
Ben Scheirman
1
También puede eliminar la opción flexibleHeight-Autoresizing-Option en IB, por supuesto.
d4Rk
He intentado establecer la altura de mi tableFooterView (a través de un xib / nib) y no tuve éxito al configurar el marco, la altura, layoutIfNeeded (), etc. Pero esta solución finalmente me permitió configurarlo.
vikzilla
No olvide establecer la restricción de altura para la vista completa en un archivo xib.
Denis Kutlubaev
6

Otra solución es enviar la creación de la vista del encabezado a la siguiente llamada del hilo principal:

- (void)viewDidLoad {
    [super viewDidLoad];

    // ....

    dispatch_async(dispatch_get_main_queue(), ^{
        _profileView = [[MyView alloc] initWithNib:@"MyView.xib"];
        self.tableView.tableHeaderView = self.profileView;
    });
}

Nota: Soluciona el error cuando la vista cargada tiene una altura fija. No lo he probado cuando la altura del encabezado solo depende de su contenido.

EDITAR:

Puede encontrar una solución más limpia a este problema implementando esta función y llamándola enviewDidLayoutSubviews

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    [self sizeHeaderToFit];
}
Martín
fuente
1
@TussLaszlo tableHeaderViewson una especie de errores con el diseño automático. Hay algunas soluciones alternativas, como esta. Pero desde que escribí esto, he encontrado una solución mejor y más limpio aquí stackoverflow.com/a/21099430/127493 llamando a su - (void)sizeHeaderToFitenviewDidLayoutSubviews
Martin
4

Código:

  extension UITableView {

          func sizeHeaderToFit(preferredWidth: CGFloat) {
            guard let headerView = self.tableHeaderView else {
              return
            }

            headerView.translatesAutoresizingMaskIntoConstraints = false
            let layout = NSLayoutConstraint(
              item: headerView,
              attribute: .Width,
              relatedBy: .Equal,
              toItem: nil,
              attribute:
              .NotAnAttribute,
              multiplier: 1,
              constant: preferredWidth)

            headerView.addConstraint(layout)

            let height = headerView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
            headerView.frame = CGRectMake(0, 0, preferredWidth, height)

            headerView.removeConstraint(layout)
            headerView.translatesAutoresizingMaskIntoConstraints = true

            self.tableHeaderView = headerView
          }
  }
Phil
fuente
Funciona Proporcione las restricciones de diseño automático adecuadas a todas las subvistas de la vista del encabezado de la tabla. Si omite una sola restricción, no funcionará.
abhimuralidharan
4

Amplió esta solución http://collindonnell.com/2015/09/29/dynamically-sized-table-view-header-or-footer-using-auto-layout/ para la vista del pie de página de la tabla:

@interface AutolayoutTableView : UITableView

@end

@implementation AutolayoutTableView

- (void)layoutSubviews {
    [super layoutSubviews];

    // Dynamic sizing for the header view
    if (self.tableHeaderView) {
        CGFloat height = [self.tableHeaderView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
        CGRect headerFrame = self.tableHeaderView.frame;

        // If we don't have this check, viewDidLayoutSubviews() will get
        // repeatedly, causing the app to hang.
        if (height != headerFrame.size.height) {
            headerFrame.size.height = height;
            self.tableHeaderView.frame = headerFrame;
            self.tableHeaderView = self.tableHeaderView;
        }

        [self.tableHeaderView layoutIfNeeded];
    }

    // Dynamic sizing for the footer view
    if (self.tableFooterView) {
        CGFloat height = [self.tableFooterView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
        CGRect footerFrame = self.tableFooterView.frame;

        // If we don't have this check, viewDidLayoutSubviews() will get
        // repeatedly, causing the app to hang.
        if (height != footerFrame.size.height) {
            footerFrame.size.height = height;
            self.tableFooterView.frame = footerFrame;
            self.tableFooterView = self.tableFooterView;
        }

        self.tableFooterView.transform = CGAffineTransformMakeTranslation(0, self.contentSize.height - footerFrame.size.height);
        [self.tableFooterView layoutIfNeeded];
    }
}

@end
k06a
fuente
Ayer pasé un día tratando de que tableHeader cambiara el tamaño / diseño automáticamente correctamente. Esta solución me funciona. Gracias un montón.
docchang
¡Hola! ¿Podría explicar una self.tableFooterView.transformparte? ¿Por qué es necesario?
mrvn
La transformación @mrvn se usa para mover el pie de página a la parte inferior de tableView.
k06a
3

Puede obtener el diseño automático para proporcionarle un tamaño mediante el método systemLayoutSizeFittingSize .

Luego puede usar esto para crear el marco para su aplicación. Esta técnica funciona siempre que necesite saber el tamaño de una vista que usa el diseño automático internamente.

El código en Swift parece

//Create the view
let tableHeaderView = CustomTableHeaderView()

//Set the content
tableHeaderView.textLabel.text = @"Hello world"

//Ask auto layout for the smallest size that fits my constraints    
let size = tableHeaderView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)

//Create a frame    
tableHeaderView.frame = CGRect(origin: CGPoint.zeroPoint, size: size)

//Set the view as the header    
self.tableView.tableHeaderView = self.tableHeaderView

O en Objective-C

//Create the view
CustomTableHeaderView *header = [[CustomTableHeaderView alloc] initWithFrame:CGRectZero];

//Set the content
header.textLabel.text = @"Hello world";

//Ask auto layout for the smallest size that fits my constraints
CGSize size = [header systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];

//Create a frame
header.frame = CGRectMake(0,0,size.width,size.height);

//Set the view as the header  
self.tableView.tableHeaderView = header

También debe tenerse en cuenta que en esta instancia en particular, anular requireConstraintBasedLayout en su subclase, da como resultado que se realice una pasada de diseño, sin embargo, los resultados de esta pasada de diseño se ignoran y el marco del sistema se establece en el ancho de tableView y 0 de altura.

Jonathan
fuente
3

Lo siguiente funcionó para mí.

  1. Utilice una UIViewvista de encabezado simple y antigua .
  2. Agregue subvistas a eso UIView
  3. Utilice el diseño automático en las subvistas

El principal beneficio que veo es limitar los cálculos de marcos. Apple realmente debería actualizarUITableView la API para hacerlo más fácil.

Ejemplo usando SnapKit:

let layoutView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.bounds.width, height: 60))
layoutView.backgroundColor = tableView.backgroundColor
tableView.tableHeaderView = layoutView

let label = UILabel()
layoutView.addSubview(label)
label.text = "I'm the view you really care about"
label.snp_makeConstraints { make in
    make.edges.equalTo(EdgeInsets(top: 10, left: 15, bottom: -5, right: -15))
}
David Nix
fuente
3

Suceden cosas extrañas. systemLayoutSizeFittingSize funciona muy bien para iOS9, pero no para iOS 8 en mi caso. Entonces este problema se resuelve con bastante facilidad. Simplemente obtenga el enlace a la vista inferior en el encabezado y en viewDidLayoutSubviews después de la super llamada actualice los límites de la vista del encabezado insertando la altura como CGRectGetMaxY (yourview.frame) + padding

UPD: La solución más fácil de todos : Entonces, en la vista de encabezado, coloque la subvista y fíjela a la izquierda , derecha , arriba . En esa subvista, coloque sus subvistas con restricciones de altura automática. Después de eso, dé todo el trabajo al diseño automático (no se requiere cálculo)

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    CGFloat height = CGRectGetMaxY(self.tableView.tableHeaderView.subviews.firstObject.frame);
    self.tableView.tableHeaderView.bounds = CGRectMake(0, 0, CGRectGetWidth(self.tableView.bounds), height);
    self.tableView.tableHeaderView = self.tableView.tableHeaderView;
}

Como resultado, la subvista se expande / contrae como debería, al final llama a viewDidLayoutSubviews. En ese momento, sabemos el tamaño real de la vista, así que configure headerView height y actualícelo reasignando. ¡Funciona de maravilla!

También funciona para la vista de pie de página.

HotJard
fuente
1
Esto se repite para mí en iOS 10.
Simon
3

Actualizado para Swift 4.2

extension UITableView {

    var autolayoutTableViewHeader: UIView? {
        set {
            self.tableHeaderView = newValue
            guard let header = newValue else { return }
            header.setNeedsLayout()
            header.layoutIfNeeded()
            header.frame.size = 
            header.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
            self.tableHeaderView = header
        }
        get {
            return self.tableHeaderView
        }
    }
}
Caleb Friden
fuente
2

puede agregar una restricción de ubicación superior + horizontal entre el encabezado y la vista de tabla, para colocarlo correctamente (si el encabezado en sí contiene todas las restricciones de diseño internas necesarias para tener un marco correcto)

en el método tableViewController viewDidLoad

    headerView.translatesAutoresizingMaskIntoConstraints = false

    tableView.tableHeaderView = headerView

    headerView.widthAnchor.constraint(equalTo: tableView.widthAnchor).isActive = true
    headerView.topAnchor.constraint(equalTo: tableView.topAnchor).isActive = true
    headerView.centerXAnchor.constraint(equalTo: tableView.centerXAnchor).isActive = true
GreatWiz
fuente
1

Mi vista de encabezado de tabla es una subclase de UIView: creé una única UIView de contentView dentro del inicializador, con sus límites iguales a los del marco de la vista de encabezado de tabla y agregué todos mis objetos como una subvista de eso.

Luego agregue las restricciones para sus objetos dentro del layoutSubviewsmétodo de la vista del encabezado de la tabla en lugar de dentro del inicializador. Eso resolvió el accidente.

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:CGRectMake(0, 0, 0, 44.0)];
    if (self) {
        UIView *contentView = [[UIView alloc] initWithFrame:self.bounds];
        contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth;

        // add other objects as subviews of content view

    }
    return self;
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    // remake constraints here
}
Ryan
fuente
1

Mi AutoLayout funciona muy bien:

CGSize headerSize = [headerView systemLayoutSizeFittingSize:CGSizeMake(CGRectGetWidth([UIScreen mainScreen].bounds), 0) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
headerView.frame = CGRectMake(0, 0, headerSize.width, headerSize.height);
self.tableView.tableHeaderView = headerView;
RomanV
fuente
No hice esto exactamente, pero me diste una buena idea: eliminar headerView, restablecer su marco y volver a agregarlo.
dinesharjani
1

Para la mayoría de los casos, la mejor solución es simplemente no luchar contra el marco y adoptar máscaras de autoresización:

// embrace autoresizing masks and let the framework add the constraints for you
headerView.translatesAutoresizingMaskIntoConstraints = true
headerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]

// figure out what's the best size based on the table view width
let width = self.tableView.frame.width
let targetSize = headerView.systemLayoutSizeFitting(CGSize(width: width, height: CGFloat.greatestFiniteMagnitude), withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
headerView.frame.size = targetSize
self.tableView.tableHeaderView = headerView

Al usar máscaras de tamaño automático, le está diciendo al marco cómo su vista debería cambiar su tamaño cuando la supervista cambia su tamaño. Pero este cambio se basa en el marco inicial que ha establecido.

pfandrade
fuente
0

Sé que esta es una publicación antigua, pero después de revisar todas las publicaciones de SO sobre esto y pasar una tarde entera jugando con esto, finalmente encontré una solución limpia y muy simple.

En primer lugar, la jerarquía de mi vista se ve así:

  1. Vista de tabla
    1. Ver tableHeaderView
      1. Ver con una salida llamada headerView

Ahora, dentro de la Vista (n. ° 3), configuré todas las restricciones como lo haría normalmente, incluido el espacio inferior al contenedor. Esto hará que el contenedor (es decir, 3.View, es decir, headerView) se dimensione a sí mismo en función de sus subvistas y sus limitaciones.

Después de eso, establezco las restricciones entre 3. Viewy 2. Viewa estos:

  1. Espacio superior al contenedor: 0
  2. Espacio principal al contenedor: 0
  3. Espacio final al contenedor: 0

Note que omito intencionalmente el espacio inferior intencionalmente.

Una vez que todo esto está hecho en el guión gráfico, todo lo que queda por hacer es pegar esas tres líneas de códigos:

if (self.headerView.frame.size.height != self.tableView.tableHeaderView.frame.size.height) {
    UIView *header = self.tableView.tableHeaderView;
    CGRect frame = self.tableView.tableHeaderView.frame;
    frame.size.height = self.headerView.frame.size.height + frame.origin.y;
    header.frame = frame;
    self.tableView.tableHeaderView = header;
}
Marc-Alexandre Bérubé
fuente
0

Consejos: Si usa el método setAndLayoutTableHeaderView, debe actualizar el marco de las subvistas, por lo que en esta situación UILabel's favoriteMaxLayoutWidth debe llamar antes de que se llame a systemLayoutSizeFittingSize, no llame a layoutSubview.

demostración de código

usuario1511613
fuente
0

Comparte mi enfoque.

UITableView+XXXAdditions.m

- (void)xxx_setTableHeaderView:(UIView *)tableHeaderView layoutBlock:(void(^)(__kindof UIView *tableHeaderView, CGFloat *containerViewHeight))layoutBlock {
      CGFloat containerViewHeight = 0;
      UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectZero];
      [backgroundView addSubview:tableHeaderView];
      layoutBlock(tableHeaderView, &containerViewHeight);

      backgroundView.frame = CGRectMake(0, 0, 0, containerViewHeight);

      self.tableHeaderView = backgroundView;
}

Uso.

[self.tableView xxx_setTableHeaderView:myView layoutBlock:^(__kindof UIView * _Nonnull tableHeaderView, CGFloat *containerViewHeight) {
    *containerViewHeight = 170;

    [tableHeaderView mas_makeConstraints:^(MASConstraintMaker *make) {
      make.top.equalTo(@20);
      make.centerX.equalTo(@0);
      make.size.mas_equalTo(CGSizeMake(130, 130));
    }];
  }];
Vincent sentarse
fuente
0

En mi caso, el método con systemLayoutSizeFittingSize por alguna razón no funcionó. Lo que funcionó para mí es una modificación de la solución publicada por HotJard (su solución original tampoco funcionó en mi caso en iOS 8). Lo que tenía que hacer es colocar una subvista en la vista de encabezado y fijarla a la izquierda, derecha, arriba (no fijarla a la parte inferior). Ponga todo usando autolayout en esa subvista y en el código haga esto:

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];
    [self resizeHeaderToFitSubview];
}

- (void)resizeHeaderToFitSubview
{
    UIView *header = self.tableView.tableHeaderView;
    [header setNeedsLayout];
    [header layoutIfNeeded];
    CGFloat height = CGRectGetHeight(header.subviews.firstObject.bounds);
    header.bounds = CGRectMake(0, 0, CGRectGetWidth(self.tableView.bounds), height);
    self.tableView.tableHeaderView = nil;
    self.tableView.tableHeaderView = header;
}
Leszek Szary
fuente
0

Una publicación antigua. Pero una buena publicación. Aquí están mis 2 centavos.

En primer lugar, asegúrese de que su vista de encabezado tenga sus restricciones dispuestas para que pueda admitir su propio tamaño de contenido intrínseco. Luego haz lo siguiente.

//ViewDidLoad
headerView.translatesAutoresizingMaskIntoConstraints = false
headerView.configure(title: "Some Text A")

//Somewhere else
headerView.update(title: "Some Text B)

private var widthConstrained = false

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    if widthConstrained == false {
        widthConstrained = true
        tableView.addConstraint(NSLayoutConstraint(item: headerView, attribute: .width, relatedBy: .equal, toItem: tableView, attribute: .width, multiplier: 1, constant: 0))
        headerView.layoutIfNeeded()
        tableView.layoutIfNeeded()
    }
}

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)
    coordinator.animate(alongsideTransition: { (context) in
        self.headerView.layoutIfNeeded()
        self.tableView.layoutIfNeeded()
    }, completion: nil)
}

fuente
0

Pude lograrlo con el siguiente enfoque (esto funciona para el pie de página de la misma manera).

Primero, necesitará pequeños UITableView extensión:

Swift 3

extension UITableView {
    fileprivate func adjustHeaderHeight() {
        if let header = self.tableHeaderView {
            adjustFrame(header)
        }
    }

    private func adjustFrame(_ view: UIView) {
        view.frame.size.height = calculatedViewHeight(view)
    }

    fileprivate func calculatedHeightForHeader() -> CGFloat {
        if let header = self.tableHeaderView {
            return calculatedViewHeight(header)
        }
        return 0.0
    }

    private func calculatedViewHeight(_ view: UIView) -> CGFloat {
        view.setNeedsLayout()
        let height = view.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
        return height
    }
}

En su implementación de la clase de controlador de vista:

// this is a UIView subclass with autolayout
private var headerView = MyHeaderView()

override func loadView() {
    super.loadView()
    // ...
    self.tableView.tableHeaderView = headerView
    self.tableView.sectionHeaderHeight = UITableViewAutomaticDimension
    // ...
}

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()

    // this is to prevent recursive layout calls
    let requiredHeaderHeight = self.tableView.calculatedHeightForHeader()
    if self.headerView.frame.height != requiredHeaderHeight {
        self.tableView.adjustHeaderHeight()
    }
}

Notas sobre UIViewla implementación de una subvista de un encabezado :

  1. Tienes que estar 100% seguro de que tu vista de encabezado tiene la configuración correcta de diseño automático. Recomendaría comenzar con una vista de encabezado simple con solo una restricción de altura y probar la configuración anterior.

  2. Anular requiresConstraintBasedLayouty devolver true:

.

class MyHeaderView: UIView {
   // ...
   override static var requiresConstraintBasedLayout : Bool {
       return true
   }
   // ...
}
Yevhen Dubinin
fuente
0

Para usuarios de Xamarin:

public override void ViewDidLayoutSubviews()
{
    base.ViewDidLayoutSubviews();

    TableviewHeader.SetNeedsLayout();
    TableviewHeader.LayoutIfNeeded();

    var height = TableviewHeader.SystemLayoutSizeFittingSize(UIView.UILayoutFittingCompressedSize).Height;
    var frame = TableviewHeader.Frame;
    frame.Height = height;
    TableviewHeader.Frame = frame;
}

Suponiendo que nombró la vista de encabezado de su vista de tabla como TableviewHeader

Gustavo Baiocchi Costa
fuente
0

Así es como puede hacerlo en su UIViewController

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    if headerView.frame.size.height == 0 {
      headerView.label.preferredMaxLayoutWidth = view.bounds.size.width - 20
      let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height

      headerView.frame.size = CGSize(width: tableView.bounds.size.width, height: height)
    }
  }
onmyway133
fuente
0

Cualquier basada en restricciones UIViewpuede ser una buenatableHeaderView .

Es necesario establecer un tableFooterViewantes y luego imponer una restricción final adicional en tableFooterViewy tableHeaderView.

- (void)viewDidLoad {

    ........................
    // let self.headerView is some constraint-based UIView
    self.tableView.tableFooterView = [UIView new];
    [self.headerView layoutIfNeeded];
    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;
    [self.tableFooterView.trailingAnchor constraintEqualToAnchor:self.headerView.trailingAnchor].active = YES;

}

Puede encontrar todos los detalles y fragmentos de código aquí.

malex
fuente
0

He descubierto una solución. envuelva su vista de encabezado xib de autolayout wrriten en un contenedor uiview vacío, y asigne la vista de encabezado a la propiedad tableViewHeader de tableView.

    UIView *headerWrapper = [[UIView alloc] init];
    AXLHomeDriverHeaderView *headerView = [AXLHomeDriverHeaderView loadViewFromNib];
    [headerWrapper addSubview:headerView];
    [headerView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(headerWrapper);
    }];
    self.tableView.tableHeaderView = headerView;
tounaobun
fuente
0

Esto es lo que funciona para UITableViewController en ios 12,

Coloque un UIView en TableView encima de todas las celdas prototipo para el encabezado y debajo de todas las celdas prototipo para el pie de página. Configure su encabezado y pie de página según sea necesario. Establezca todas las restricciones necesarias.

Ahora use los siguientes métodos de extensión

public static class UITableVIewExtensions
{

    public static void MakeHeaderAutoDimension(this UITableView tableView)
    {
        if (tableView.TableHeaderView is UIView headerView) {
            var size = headerView.SystemLayoutSizeFittingSize(UIView.UILayoutFittingCompressedSize);
            if (headerView.Frame.Size.Height != size.Height) {
                var frame = headerView.Frame;
                frame.Height = size.Height;
                headerView.Frame = frame;
                tableView.TableHeaderView = headerView;
                tableView.LayoutIfNeeded();
            }
        }
    }

    public static void MakeFooterAutoDimension(this UITableView tableView)
    {
        if (tableView.TableFooterView is UIView footerView) {
            var size = footerView.SystemLayoutSizeFittingSize(UIView.UILayoutFittingCompressedSize);
            if (footerView.Frame.Size.Height != size.Height) {
                var frame = footerView.Frame;
                frame.Height = size.Height;
                footerView.Frame = frame;
                tableView.TableFooterView = footerView;
                tableView.LayoutIfNeeded();
            }
        }
    }
}

y llámelo en ViewDidLayoutSubviews de la subclase de UITableViewController

public override void ViewDidLayoutSubviews()
{
    base.ViewDidLayoutSubviews();

    TableView.MakeHeaderAutoDimension();
    TableView.MakeFooterAutoDimension();
}
Papi borracho
fuente
0

Encontré el problema de obtener un ancho de 375 puntos, la única forma en que funcionó para mí es retransmitir el tableView para obtener el ancho correcto. También preferí el Autodiseño a la configuración del tamaño del marco.

Aquí está la versión que me funciona:

Xamarin.iOS

public static void AutoLayoutTableHeaderView(this UITableView tableView, UIView header)
{
    tableView.TableHeaderView = header;
    tableView.SetNeedsLayout();
    tableView.LayoutIfNeeded();
    header.WidthAnchor.ConstraintEqualTo(tableView.Bounds.Width).Active = true;       
    tableView.TableHeaderView = header;
}

Versión Swift (modificado de la respuesta de @Ben Packard)

extension UITableView {
    //set the tableHeaderView so that the required height can be determined, update the header's frame and set it again
    func setAndLayoutTableHeaderView(header: UIView) {
        self.tableHeaderView = header
        self.setNeedsLayout()
        self.layoutIfNeeded()
        header.widthAnchor.widthAnchor.constraint(equalTo: self.bounds.width).isActive = true
        self.tableHeaderView = header
    }
}
Barón Ch'ng
fuente
0

Mi solución es hacer una nueva clase como esta.

class BaseTableHeaderView: UIView {

    func sizeToFitBasedOnConstraints(width: CGFloat = Screen.width) {
        let size = systemLayoutSizeFitting(CGSize(width: width, height: 10000),
                                              withHorizontalFittingPriority: .required,
                                              verticalFittingPriority: .fittingSizeLevel)
        frame = CGRect(origin: .zero, size: size)
    }

    override func willMove(toSuperview newSuperview: UIView?) {
        sizeToFitBasedOnConstraints()
        super.willMove(toSuperview: newSuperview)
    }

}

Para usarlo, simplemente agregue todas sus subvistas en una instancia de BaseTableHeaderViewy adjúntelo a su vista de tabla.

let tableHeaderView = BaseTableHeaderView()
tableHeaderView.addSubview(...)
tableView.tableHeaderView = tableHeaderView

Cambiará el tamaño automáticamente en función de sus limitaciones.

Hesse Huang
fuente
-1

La respuesta aceptada solo es útil para tablas con una sola sección. Para múltiples secciones, UITableViewasegúrese de que su encabezado herede deUITableViewHeaderFooterView y estará bien.

Como alternativa, simplemente inserte su encabezado actual en el formato contentViewde UITableViewHeaderFooterView. Exactamente como UITableViewCellfunciona.

redent84
fuente
9
La pregunta no es sobre tableHeaderViewel encabezado de la sección.
Rpranata