2009-12-22 9 views
13

¿Alguien ha visto una implementación de una vista de "acordeón" (quizás llamada "esquema animado") para el iPhone? Encontré un proyecto de ejemplo para Cocoa, pero antes de probar un puerto, esperaba que alguien ya haya inventado la rueda.¿Cómo implementar una vista de acordeón para una aplicación de iPhone SDK?

Para dejar en claro, en una UIView, considere una pila de secciones, cada una con un encabezado y algunos contenidos. Cuando el usuario toca el encabezado (oa través de algún mensaje/evento), si la sección ya está abierta => ciérrela; si la sección está cerrada => ábrala y cierre cualquier otra sección abierta. Un ejemplo en jQuery se ve así: http://docs.jquery.com/UI/Accordion

En mi caso, me gustaría poder poner cualquier contenido de UIView en cada sección.

Me interesaría simplemente ver algunas aplicaciones reales que han implementado esto, solo para saber que es posible.

+3

Cualquier cosa es posible! –

+0

@Jacob ¡No todo es posible! Ver http://stackoverflow.com/questions/4962539/creating-generic-method-names-in-generic-class. ;) – jakev

+0

Eche un vistazo a https://github.com/nacho4d/Accordion Implementa una navegación de estilo Accordion para iPad, ¡allí puede ver cómo funciona todo! – myell0w

Respuesta

18

Solo usaría una UITableView, haga que la altura de cada celda dependa de si está "abierta" o no y luego continúe desde allí. Es fácil cambiar el tamaño de las filas y puede hacer que la altura total de las celdas combinadas sea la altura disponible en UITableView para que parezca un acordeón más que solo una tabla.

Este es un truco rápido que debería funcionar, pero en el archivo .h de su subclase UITableViewController:

bool sectionopen[4]; ///or some other way of storing the sections expanded/closed state 

Y en el archivo .m poner algo como:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 
    return 1; 
} 

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 
    return 4; 
} 

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 
    if (sectionopen[indexPath.row]) { 
     return 240;///it's open 
    } else { 
     return 45;///it's closed 
    } 

} 

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 
    UITableViewCell *mycell = [[[UITableViewCell alloc] init] autorelease]; 
    mycell.textLabel.text= @"Section Name"; 
    return mycell; 
} 


- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 
    ///turn them all off 
    sectionopen[0]=NO; 
    sectionopen[1]=NO; 
    sectionopen[2]=NO; 
    sectionopen[3]=NO; 

    ///open this one 
    sectionopen[indexPath.row]=YES; 

    ///animate the opening and expand the row 
    [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade]; 
} 

Esto básicamente tomar 4 filas y conviértalas en secciones colapsables donde seleccionar una fila la ampliará a 240 píxeles y colapsará todas las otras filas a 40. Puede cambiar todos esos números y descubrir las secciones y hacer lo que quiera con ella.

Lo he probado y funciona. Luego puede completarlo agregando el otro contenido al código de creación de su celular para agregar lo que quiera a una sección (incluyendo posiblemente una UITextView desplazable si lo desea).

2

Me tropecé con esto y encontré la solución de mjdth muy directa y útil. Sin embargo, es posible que desee utilizar

[self.tableView reloadRowsAtIndexPaths: paths withRowAnimation:UITableViewRowAnimationBottom]; 

en lugar del método de reloadSections propoesed como las filas de recarga le da una transición mucho más suave.

1

Aquí está la clase CollapsingTableViewDelegate con la que estoy trabajando actualmente para hacer esto. Esto solo funciona con contenido de tabla estática.

Proporciona la implementación CollapsingTableCellDelegate a esta clase, que debe saber cómo calcular los tamaños plegados y expandidos de cada fila, y cómo crear un UIView para cada fila. La vista se mantiene igual, ya sea que se haya colapsado o expandido, de modo que la parte superior de la vista de cada fila sirva como encabezado seleccionable de esa fila.

A continuación, convierta esta clase en la fuente de datos y delegue para su UITableView.

Archivo de cabecera CollapsingTableViewDelegate.h:

#import <UIKit/UIKit.h> 

@protocol CollapsingTableCellDelegate<NSObject> 

@required 
- (CGFloat)collapsingCellHeightForRow:(int)row expanded:(BOOL)expanded; 
- (UIView *)collapsingCellViewForRow:(int)row; 

@optional 
- (BOOL)collapsingCellAllowCollapse:(int)row; 

@end 

struct cell; 

@interface CollapsingTableViewDelegate : NSObject <UITableViewDelegate, UITableViewDataSource> { 
    id<CollapsingTableCellDelegate> cellDelegate; 
    int numCells; 
    int currentSelection; 
    struct cell *cells; 
} 

@property (nonatomic, retain, readonly) id<CollapsingTableCellDelegate> cellDelegate; 
@property (nonatomic, assign, readonly) int numCells; 
@property (nonatomic, assign) int currentSelection; 
@property (nonatomic, assign, readonly) struct cell *cells; 

- (CollapsingTableViewDelegate *)initWithCellDelegate:(id<CollapsingTableCellDelegate>)delegate numCells:(int)numCells; 
- (void)tableView:(UITableView *)tableView touchRow:(int)newSelection; 

@end 

y fuente de archivo CollapsingTableViewDelegate.m:

#import "CollapsingTableViewDelegate.h" 

@implementation CollapsingTableViewDelegate 

struct cell { 
    u_char expanded; 
    u_char collapsable; 
}; 

@synthesize cellDelegate; 
@synthesize currentSelection; 
@synthesize cells; 
@synthesize numCells; 

#pragma mark - 
#pragma mark Setup and Teardown 

- (CollapsingTableViewDelegate *)initWithCellDelegate:(id<CollapsingTableCellDelegate>)delegate numCells:(int)num { 
    if ([super init] == nil) 
     return nil; 
    if ((cells = calloc(num, sizeof(*cells))) == NULL) { 
     [self autorelease]; 
     return nil; 
    } 
    cellDelegate = [delegate retain]; 
    numCells = num; 
    for (int row = 0; row < self.numCells; row++) { 
     struct cell *const cell = &self.cells[row]; 

     cell->collapsable = ![self.cellDelegate respondsToSelector:@selector(collapsingCellAllowCollapse:)] 
      || [self.cellDelegate collapsingCellAllowCollapse:row]; 
     cell->expanded = !cell->collapsable; 
    } 
    currentSelection = -1; 
    return self; 
} 

- (void)dealloc { 
    [cellDelegate release]; 
    free(cells); 
    [super dealloc]; 
} 

- (void)tableView:(UITableView *)tableView reloadRow:(int)row fade:(BOOL)fade { 
    [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:row inSection:0]] 
        withRowAnimation:fade ? UITableViewRowAnimationFade : UITableViewRowAnimationNone]; 
} 

- (void)tableView:(UITableView *)tableView touchRow:(int)newSelection { 

    // Sanity check 
    if (newSelection < -1 || newSelection >= self.numCells) { 
     NSLog(@"CollapsingTableViewDelegate: invalid row %d not in the range [-1..%d)", newSelection, self.numCells); 
     return; 
    } 

    // Gather info 
    int oldSelection = self.currentSelection; 
    BOOL sameCellSelected = newSelection == oldSelection; 
    struct cell *const oldCell = oldSelection != -1 ? &self.cells[oldSelection] : NULL; 
    struct cell *const newCell = newSelection != -1 ? &self.cells[newSelection] : NULL; 

    // Mark old cell as collapsed and new cell as expanded 
    if (newCell != NULL) 
     newCell->expanded = TRUE; 
    if (oldCell != NULL) 
     oldCell->expanded = FALSE; 
    self.currentSelection = sameCellSelected ? -1 : newSelection; 

    // Update table view 
    if (oldSelection >= newSelection) { 
     if (oldSelection != -1) 
      [self tableView:tableView reloadRow:oldSelection fade:sameCellSelected]; 
     if (newSelection != -1 && !sameCellSelected) 
      [self tableView:tableView reloadRow:newSelection fade:TRUE]; 
    } else { 
     if (newSelection != -1 && !sameCellSelected) 
      [self tableView:tableView reloadRow:newSelection fade:TRUE]; 
     if (oldSelection != -1) 
      [self tableView:tableView reloadRow:oldSelection fade:sameCellSelected]; 
    } 

    // If expanding a cell, scroll it into view 
    if (newSelection != -1 && !sameCellSelected) { 
     [tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:newSelection inSection:0] 
         atScrollPosition:UITableViewScrollPositionTop 
           animated:TRUE]; 
    } 
} 

#pragma mark - 
#pragma mark Table view data source 

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 
    return 1; 
} 

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 
    return self.numCells; 
} 

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 
    int row = [indexPath row]; 
    struct cell *const cell = &self.cells[row]; 
    return [self.cellDelegate collapsingCellHeightForRow:row expanded:cell->expanded]; 
} 

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 
    int row = [indexPath row]; 
    UIView *cellView = [self.cellDelegate collapsingCellViewForRow:row]; 
    [cellView removeFromSuperview]; 
    UITableViewCell *tvcell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil] autorelease]; 
    [tvcell.contentView addSubview:cellView]; 
    tvcell.clipsToBounds = TRUE; 
    tvcell.selectionStyle = UITableViewCellSelectionStyleNone; 
    return tvcell; 
} 

#pragma mark - 
#pragma mark Table view delegate 

- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath { 
    int row = [indexPath row]; 
    struct cell *const cell = &self.cells[row]; 
    return cell->collapsable ? indexPath : nil; 
} 

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)newSelection { 
    [tableView deselectRowAtIndexPath:newSelection animated:TRUE]; 
    [self tableView:tableView touchRow:[newSelection row]]; 
} 

@end 

no la perfección, pero parece que funciona básicamente para mí.

16

Cada solución que encontré fue el uso de UITableView, que no funcionaba para mí, porque no me visualización de datos tabulares. Es por eso que creé el control AccordionView. El uso es bastante sencillo:

AccordionView *accordion = [[AccordionView alloc] initWithFrame:CGRectMake(0, 0, 320, 420)]; 
[self addSubview:accordion]; 

// Only height is taken into account, so other parameters are just dummy 
UIButton *header1 = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 0, 30)]; 
[header1.titleLabel setText:@"First row"]; 

UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 200)]; 
// ... add subviews to view1 

[accordion addHeader:header1 withView:view1]; 

// ... add more panels 

[accordion setSelectedIndex:0]; 

En la vida real que se parece a esto:

enter image description here

Las barras negras son las cabeceras y se puede personalizarlos todo lo que quiere (estoy usando Three20).

+0

Hola Suda, gracias por el control. He golpeado un pequeño inconveniente al usarlo. He abierto un problema [aquí] (https://github.com/appsome/AccordionView/issues/11) en Github del proyecto. ¿Puedes echar un vistazo? – Isuru

+0

Claro, estoy respondiendo ahora :) – suda

+0

Hola suda, muchas gracias por tu trabajo. Aquí he abierto un pequeño problema. Por favor échale un vistazo. https://github.com/appsome/AccordionView/issues/23 –

Cuestiones relacionadas