Cómo implementar encabezados y pies de página de vista de tabla personalizada con Storyboard

176

Sin usar un guión gráfico, simplemente podríamos arrastrar un UIViewal lienzo, diseñarlo y luego configurarlo en los métodos tableView:viewForHeaderInSectiono tableView:viewForFooterInSectiondelegar.

¿Cómo logramos esto con un StoryBoard donde no podemos arrastrar una UIView al lienzo?

Seamus
fuente

Respuestas:

91

Sé que esta pregunta era para iOS 5, pero para beneficio de los futuros lectores, tenga en cuenta que ahora podemos usar iOS 6 en dequeueReusableHeaderFooterViewWithIdentifierlugar de dequeueReusableCellWithIdentifier.

Así pues, en viewDidLoad, llamar a cualquiera registerNib:forHeaderFooterViewReuseIdentifier:o registerClass:forHeaderFooterViewReuseIdentifier:. Luego en viewForHeaderInSection, llame tableView:dequeueReusableHeaderFooterViewWithIdentifier:. No utiliza un prototipo de celda con esta API (es una vista basada en NIB o una vista creada mediante programación), pero esta es la nueva API para encabezados y pies de página en desuso.

Robar
fuente
13
suspiro Una lástima que estas dos respuestas claras sobre el uso de una NIB en lugar de una celda proto están actualmente por debajo de 5 votos, y la respuesta "piratear con celdas proto" está por encima de 200.
Benjohn
55
La diferencia es que con el truco de Tieme puedes hacer tu diseño en el mismo guión gráfico y no usar un Xib por separado
CedricSoubrie
¿De alguna manera puede evitar usar un archivo NIB separado y usar storyboard en su lugar?
Foriger
@Foriger - No en este punto. Es una omisión extraña en las vistas de la tabla del guión gráfico, pero es lo que es. Use ese truco kludgy con celdas prototipo si lo desea, pero personalmente, acabo de soportar la molestia de las NIB para las vistas de encabezado / pie de página.
Rob
2
Simplemente decir que es "viejo" no es lo suficientemente fuerte, en mi humilde opinión. La respuesta aceptada es una forma poco clara de evitar el hecho de que no puede tener celdas prototipo para las vistas de encabezado y pie de página. Pero creo que es simplemente incorrecto, porque supone que la eliminación de encabezados y pies de página funciona de la misma manera que la eliminación de celdas (que la presencia de una API más nueva para encabezados / pies de página sugiere que podría no ser el caso). La respuesta aceptada es una solución creativa que era comprensible en iOS 5, pero, en iOS 6 y posterior, es mejor usar la API diseñada explícitamente para la reutilización de encabezado / pie de página.
Rob
384

Simplemente use una celda prototipo como encabezado y / o pie de página de su sección.

  • agregue una celda adicional y coloque los elementos deseados en ella.
  • establecer el identificador a algo específico (en mi caso, SectionHeader)
  • implementar el tableView:viewForHeaderInSection:método o el tableView:viewForFooterInSection:método
  • utilizar [tableView dequeueReusableCellWithIdentifier:]para obtener el encabezado
  • implementar el tableView:heightForHeaderInSection:método

(ver captura de pantalla)

-(UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    static NSString *CellIdentifier = @"SectionHeader"; 
    UITableViewCell *headerView = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (headerView == nil){
        [NSException raise:@"headerView == nil.." format:@"No cells with matching CellIdentifier loaded from your storyboard"];
    }
    return headerView;
}  

Editar: Cómo cambiar el título del encabezado (pregunta comentada):

  1. Agregar una etiqueta a la celda del encabezado
  2. establecer la etiqueta de la etiqueta a un número específico (por ejemplo, 123)
  3. En su tableView:viewForHeaderInSection:método, obtenga la etiqueta llamando a:
    UILabel *label = (UILabel *)[headerView viewWithTag:123]; 
  1. Ahora puede usar la etiqueta para establecer un nuevo título:
    [label setText:@"New Title"];
Tieme
fuente
77
Tarde a la fiesta, pero para deshabilitar los clics en la vista de encabezado de sección, simplemente devuelva cell.contentView en el viewForHeaderInSection método (no es necesario agregar ninguna UIViews personalizada).
paulvs
3
@PaulVon Traté de hacer eso en mi último proyecto, pero si lo haces, solo intenta presionar prolongadamente uno de tus encabezados y se bloqueará
Hons
13
En iOS 6.0, dequeueReusableHeaderFooterViewWithIdentifierse presenta, y ahora se prefiere sobre esta respuesta. Pero usar eso correctamente ahora requiere más pasos. Tengo guía @ samwize.com/2015/11/06/… .
Samwize
3
No cree su sectionHeaderViews usando UITableViewCells, creará un comportamiento inesperado. Use una UIView en su lugar.
ApreciateI
44
No lo use nunca UITableViewCellcomo una vista de encabezado. Te resultará muy difícil depurar fallas visuales: el encabezado a veces desaparecerá debido a la deducción de las celdas y estarás buscando durante horas por qué es así hasta que te des cuenta de UITableViewCellque no pertenece al UITableViewencabezado.
raven_raven
53

En iOS 6.0 y superior, las cosas han cambiado con la nueva dequeueReusableHeaderFooterViewWithIdentifierAPI.

He escrito una guía (probada en iOS 9), que se puede resumir como tal:

  1. Subclase UITableViewHeaderFooterView
  2. Cree Nib con la vista de subclase y agregue 1 vista de contenedor que contenga todas las demás vistas en el encabezado / pie de página
  3. Registra la punta en viewDidLoad
  4. Implemente viewForHeaderInSectiony use dequeueReusableHeaderFooterViewWithIdentifierpara recuperar el encabezado / pie de página
samwize
fuente
2
Gracias por esta respuesta que NO es un truco, y la forma correcta de hacer las cosas. Además, gracias por presentarme a UITableViewHeaderFooterVIew. Por cierto, la guía es excelente! :)
Roboris
Bienvenidos. La implementación del encabezado / pie de página para la vista de tabla o colección no está muy bien documentada por Apple. Justo ayer estaba atrapado en el encabezado getter que se muestra al diseñar a través de storyboad .
samwize
1
¡Esta debería ser la respuesta mejor calificada con seguridad!
Ben Williams
¿Podría explicar cómo hacerlo a través del guión gráfico, en lugar de xib? Al hacerlo, eliminé con éxito, pero mis UILabels son nulos.
bunkerdive
22

Lo conseguí trabajando en iOS7 usando una celda prototipo en el guión gráfico. Tengo un botón en la vista del encabezado de mi sección personalizada que desencadena una secuencia configurada en el guión gráfico.

Comience con la solución de Tieme

Como señala pedro.m, el problema con esto es que al tocar el encabezado de la sección se selecciona la primera celda de la sección.

Como señala Paul Von, esto se soluciona devolviendo el contentView de la celda en lugar de la celda completa.

Sin embargo, como señala Hons, una presión prolongada en el encabezado de dicha sección bloqueará la aplicación.

La solución es eliminar cualquier gesto Reconocimiento de contentView.

-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
     static NSString *CellIdentifier = @"SectionHeader";
     UITableViewCell *sectionHeaderView = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

     while (sectionHeaderView.contentView.gestureRecognizers.count) {
         [sectionHeaderView.contentView removeGestureRecognizer:[sectionHeaderView.contentView.gestureRecognizers objectAtIndex:0]];
     }

     return sectionHeaderView.contentView; }

Si no está utilizando gestos en las vistas de encabezado de sección, este pequeño truco parece hacerlo.

damon
fuente
2
Tienes razón. Acabo de probar eso y está funcionando, así que actualicé mi publicación ( hons82.blogspot.it/2014/05/uitableviewheader-done-right.html ) que contiene todas las posibles soluciones que encontré durante mi investigación. Muchas gracias por esta solución
Hons
Buena respuesta ... Gracias hombre
Jogendra.Com
13

Si usa guiones gráficos, puede usar una celda prototipo en la vista de tabla para diseñar su vista de encabezado. Establezca una identificación única y viewForHeaderInSection puede quitar la celda con esa ID y convertirla en una UIView.

Barksten
fuente
12

Si necesita una implementación rápida de esto, siga las instrucciones en la respuesta aceptada y luego en su UITableViewController implemente los siguientes métodos:

override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    return tableView.dequeueReusableCell(withIdentifier: "CustomHeader")
}

override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return 75
}
JZ
fuente
2
Por alguna razón, no funcionó para mí hasta que anulé heightForHeaderInSection también.
Entalpi
Este es un código bastante antiguo. deberías publicar otra respuesta!
JZ.
Estaba atascado porque no sabía que tendría que anular heightForHeaderInSection. Gracias @Entalpi
guijob
1
@JZ. En su ejemplo de Swift 3, elimina el siguiente self.tableView.dequeueReusableHeaderFooterView y Swift 2: self.tableView.dequeueReusableCellWithIdentifier ¿hay alguna diferencia?
Vrutin Rathod
1
Esto salvó mi trasero! Agregar una altura a headerCell lo hace visible.
CRey
9

La solución que se me ocurrió es básicamente la misma solución utilizada antes de la introducción de los guiones gráficos.

Cree un nuevo archivo de clase de interfaz vacío. Arrastre una vista UIV al lienzo, con el diseño que desee.

Cargue la punta manualmente, asígnela a la sección de encabezado / pie de página apropiada en los métodos de delegado viewForHeaderInSection o viewForFooterInSection.

Tenía la esperanza de que Apple simplificara este escenario con guiones gráficos y siguiera buscando una solución mejor o más simple. Por ejemplo, los encabezados y pies de página de tabla personalizados son fáciles de agregar.

Seamus
fuente
Bueno, Apple lo hizo si usas la celda del guión gráfico como método de encabezado :) stackoverflow.com/questions/9219234/#11396643
Tieme
2
Excepto que solo funciona para células prototipo, no para células estáticas.
Jean-Denis Muys
1
Una solución realmente fácil es usar Pixate ; puede hacer una gran cantidad de personalización sin obtener una referencia al encabezado. Todo lo que tienes que implementar es tableView:titleForHeaderInSection, que es una línea.
John Starr Dewar
55
Esa es la solución real ... desafortunadamente no está en la cima, así que me llevó un tiempo descubrirlo. Resumí
Hons
Es más fácil que eso, solo arrastre y suelte.
Ricardo
5

Cuando devuelva el contenido de la celda, tendrá 2 problemas:

  1. accidente relacionado con gestos
  2. no reutiliza contentView (cada vez que está de viewForHeaderInSectionguardia, crea una nueva celda)

Solución:

Clase de contenedor para encabezado de tabla \ pie de página. Es solo un contenedor, heredado de UITableViewHeaderFooterView, que contiene la celda dentro

https://github.com/Magnat12/MGTableViewHeaderWrapperView.git

Registre la clase en su UITableView (por ejemplo, en viewDidLoad)

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.tableView registerClass:[MGTableViewHeaderWrapperView class] forHeaderFooterViewReuseIdentifier:@"ProfileEditSectionHeader"];
}

En su UITableViewDelegate:

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    MGTableViewHeaderWrapperView *view = [tableView dequeueReusableHeaderFooterViewWithIdentifier:@"ProfileEditSectionHeader"];

    // init your custom cell
    ProfileEditSectionTitleTableCell *cell = (ProfileEditSectionTitleTableCell * ) view.cell;
    if (!cell) {
        cell = [tableView dequeueReusableCellWithIdentifier:@"ProfileEditSectionTitleTableCell"];
        view.cell = cell;
    }

    // Do something with your cell

    return view;
}
Vitaliy Gozhenko
fuente
2

Solía ​​hacer lo siguiente para crear vistas de encabezado / pie de página perezosamente:

  • Agregue un controlador de vista de forma libre para el encabezado / pie de página de la sección al guión gráfico
  • Maneje todas las cosas para el encabezado en el controlador de vista
  • En el controlador de vista de tabla, proporcione una matriz mutable de controladores de vista para los encabezados / pies de página de sección repoblados con [NSNull null]
  • En viewForHeaderInSection / viewForFooterInSection si el controlador de vista aún no existe, créelo con storyboards instantiateViewControllerWithIdentifier, recuérdelo en la matriz y devuelva la vista de controladores de vista
Vipera Berus
fuente
2

He tenido problemas en un escenario en el que Header nunca se reutilizó, incluso haciendo todos los pasos adecuados.

Entonces, como una nota de consejo para todos los que quieran lograr la situación de mostrar secciones vacías (0 filas), tenga en cuenta que:

dequeueReusableHeaderFooterViewWithIdentifier no reutilizará el encabezado hasta que devuelva al menos una fila

Espero eso ayude

Juan pedro lozano
fuente
1

Para seguir la sugerencia de Damon , así es como hice que el encabezado sea seleccionable como una fila normal con un indicador de divulgación.

Agregué un botón subclasificado de UIButton (nombre de subclase "ButtonWithArgument") a la celda prototipo del encabezado y eliminé el texto del título (el texto en negrita "Título" es otro UILabel en la celda prototipo)

Botón en el generador de interfaces

luego configure el botón en la vista de encabezado completo y agregue un indicador de divulgación con el truco de Avario

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    static NSString *CellIdentifier = @"PersonGroupHeader";
    UITableViewCell *headerView = (UITableViewCell *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if(headerView == nil)
    {
        [NSException raise:@"headerView == nil, PersonGroupTableViewController" format:[NSString stringWithFormat:@"Storyboard does not have prototype cell with identifier %@",CellIdentifier]];
    }

    //  https://stackoverflow.com/a/24044628/3075839
    while(headerView.contentView.gestureRecognizers.count)
    {
        [headerView.contentView removeGestureRecognizer:[headerView.contentView.gestureRecognizers objectAtIndex:0]];
    }


    ButtonWithArgument *button = (ButtonWithArgument *)[headerView viewWithTag:4];
    button.frame = headerView.bounds; // set tap area to entire header view
    button.argument = [[NSNumber alloc] initWithInteger:section]; // from ButtonWithArguments subclass
    [button addTarget:self action:@selector(headerViewTap:) forControlEvents:UIControlEventTouchUpInside];

    // https://stackoverflow.com/a/20821178/3075839
    UITableViewCell *disclosure = [[UITableViewCell alloc] init];
    disclosure.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    disclosure.userInteractionEnabled = NO;
    disclosure.frame = CGRectMake(button.bounds.origin.x + button.bounds.size.width - 20 - 5, // disclosure 20 px wide, right margin 5 px
          (button.bounds.size.height - 20) / 2,
          20,
          20);
    [button addSubview:disclosure];

    // configure header title text

    return headerView.contentView;
}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    return 35.0f;
}

-(void) headerViewTap:(UIGestureRecognizer *)gestureRecognizer;
{
    NSLog(@"header tap");
    NSInteger section = ((NSNumber *)sender.argument).integerValue;
    // do something here
}

ButtonWithArgument.h

#import <UIKit/UIKit.h>

@interface ButtonWithArgument : UIButton
@property (nonatomic, strong) NSObject *argument;
@end

ButtonWithArgument.m

#import "ButtonWithArgument.h"
@implementation ButtonWithArgument
@end
MarkF
fuente
1

Debería usar la solución de Tieme como base, pero olvidarse del viewWithTag:y otros enfoques sospechosos, en su lugar, intente recargar su encabezado (recargando esa sección).

Entonces, después de configurar su vista de encabezado de celda personalizada con todas las AutoLayoutcosas elegantes , simplemente retírela y devuelva el contentView después de su configuración, como:

-(UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
 static NSString *CellIdentifier = @"SectionHeader"; 

    SettingsTableViewCell *sectionHeaderCell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    sectionHeaderCell.myPrettyLabel.text = @"Greetings";
    sectionHeaderCell.contentView.backgroundColor = [UIColor whiteColor]; // don't leave this transparent

    return sectionHeaderCell.contentView;
}  
Laszlo
fuente
1

¿Qué pasa con una solución donde el encabezado se basa en una matriz de vista:

class myViewController: UIViewController {
    var header: [UILabel] = myStringArray.map { (thisTitle: String) -> UILabel in
        let headerView = UILabel()
            headerView.text = thisTitle
    return(headerView)
}

Siguiente en el delegado:

extension myViewController: UITableViewDelegate {
    func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        return(header[section])
    }
}
CyrIng
fuente
1
  1. Agregar celda StoryBoardy establecerreuseidentified

    sb

  2. Código

    class TP_TaskViewTableViewSectionHeader: UITableViewCell{
    }
    

    y

    enlace

  3. Utilizar:

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let header = tableView.dequeueReusableCell(withIdentifier: "header", for: IndexPath.init(row: 0, section: section))
        return header
    }
    
leo.tan
fuente
2
Esta no es la forma correcta de agregar un encabezado
Manish Malviya, el
2
entonces cual es el camino?
Pramod Shukla
1

Similar a la respuesta de laszlo, pero puede reutilizar la misma celda prototipo para las celdas de la tabla y la celda del encabezado de la sección. Agregue las dos primeras funciones a continuación a su subclase UIViewController

override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableCell(withIdentifier: "DataCell") as! DataCell
    cell.data1Label.text = "DATA KEY"
    cell.data2Label.text = "DATA VALUE"
    return cell
}

override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return 75
}

// Example of regular data cell dataDelegate to round out the example
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "DataCell", for: indexPath) as! PlayerCell

    cell.data1Label.text = "\(dataList[indexPath.row].key)"
    cell.data2Label.text = "\(dataList[indexPath.row].value)"
    return cell
}
Richard Legault
fuente
0

Aquí está la respuesta de @Vitaliy Gozhenko , en Swift.
Para resumir, creará un UITableViewHeaderFooterView que contiene un UITableViewCell. Este UITableViewCell será "invaluable" y puede diseñarlo en su guión gráfico.

  1. Crear una clase UITableViewHeaderFooterView

    class CustomHeaderFooterView: UITableViewHeaderFooterView {
    var cell : UITableViewCell? {
        willSet {
            cell?.removeFromSuperview()
        }
        didSet {
            if let cell = cell {
                cell.frame = self.bounds
                cell.autoresizingMask = [UIViewAutoresizing.FlexibleHeight, UIViewAutoresizing.FlexibleWidth]
                self.contentView.backgroundColor = UIColor .clearColor()
                self.contentView .addSubview(cell)
            }
        }
    }
    
  2. Conecte su vista de tabla con esta clase en su función viewDidLoad:

    self.tableView.registerClass(CustomHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: "SECTION_ID")
    
  3. Cuando solicite, para un encabezado de sección, elimine un CustomHeaderFooterView e inserte una celda en él

    func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let view = self.tableView.dequeueReusableHeaderFooterViewWithIdentifier("SECTION_ID") as! CustomHeaderFooterView
        if view.cell == nil {
            let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell")
            view.cell = cell;
        }
    
        // Fill the cell with data here
    
        return view;
    }
    
CedricSoubrie
fuente