iOS: Crear vista de tabla personalizada sin usar UITableView

Detalles del tutorial

  • Tecnología: iOS
  • Dificultad: Nivel medio
  • Tiempo de realización: 45 – 60 minutos

Muchas veces queremos crear una vista de tabla usando UITableView y a la hora de personalizarla nos encontramos con ciertas limitaciones que nos impiden reflejar fielmente un diseño previo.
En este tutorial vamos a crear una vista de tabla sin usar UITableView ni UITableViewCell y añadiendo los elementos mediante código en nuestro ViewController. Posteriormente tendremos un controlador de la vista detalle y un archivo .xib que podremos reutilizar en otras vistas detalle de la aplicación.
Para hacer el ejemplo se ha usado iOS5, Automatic Reference Counting (ARC) y Storyboard. Se trata de una galería de imágenes en la que primero aparece un listado y al pulsar se muestra la imagen en otra vista.

Crear proyecto y añadir materiales

Creamos un nuevo proyecto en Xcode a partir de la plantilla Single View Aplication y mantendremos marcadas las casillas de las opciones Use Storyboard y Use Automatic Reference Counting.

ios tabla personalizada

ios tabla personalizada 2

Añadiremos a continuación todo los materiales gráficos que va a necesitar la aplicación al proyecto de Xcode. Para el ejemplo se muestran 4 celdas en la tabla, con lo que tendremos 4 imágenes para la tabla, además de otra imagen de separador y otra de fondo. Además para la vista detalle tendremos la versión más grande de las anteriores 4 imágenes (todas estos materiales con sus correspondientes versiones retina ).

ios tabla personalizada 3

Agregamos el controlador de la vista detalle y su XIB

Vamos a añadir al proyecto la clase DetalleGaleriaViewController que es subclase de UIViewController y será la clase que controlará la vista detalle. Marcamos la opción de crear XIB. Este .xib lo podremos usar posteriormente como archivo base de otras vistas detalle.

ios tabla personalizada 4

Preparamos Storyboard y XIB

Abrid MainStoryboard y vereis que se ha creado un View Controller por defecto en nuestra escena. Borramos este View Controller y añadimos una instancia de Navigation Controller, que a su vez ya lleva asociada una instancia de un nuevo View Controller. Asegúrate que Navigation Controller está marcado como is initial View Controller y elige en la vista del View Controller, nuestra clase ViewController como clase base de esta vista.

ios tablapersonalizada 5

ios tabla personalizada 6

Añadimos ahora una instancia de Image View y de Scroll View en la Vista de nuestro View Controller y cambia el título a Tableless en las propiedades de Navigation Item. A Image View le asociamos la imagen del fondo (fondo_vacio.jpg) y a Scroll View lo conectamos como un outlet en la clase ViewController que llamaremos scrollView. No nos olvidemos de sintetizar la propiedad en ViewController.m.

ios tabla personalizada 7

Exactamente de igual forma que hemos hecho en el Storyboard vamos a proceder con el archivo DetalleGaleriaViewController.xib. Añadimos Image View y de Scroll View en la Vista. A Image View le asociamos la imagen del fondo (fondo_vacio.jpg) y a Scroll View lo conectamos como un outlet en la clase DetalleGaleriaViewController que llamaremos scrollView. No nos olvidemos de sintetizar la propiedad en DetalleGaleriaViewController.m.

ios tabla personalizada 8

Código de ViewController

Os dejo el código completo de ViewController y lo comento. Ya os adelanto que solamente vamos a crear 2 métodos nuevos que son initialSetup y buttonPressed y que por sus nombres ya podreis adivinar lo que van a hacer. De propiedades tan solo scrollView.


#import <UIKit/UIKit.h>

@interface ViewController : UIViewController 

@property (strong, nonatomic) IBOutlet UIScrollView *scrollView;

-(void) initialSetup;
-(void) buttonPressed:(id)sender;

@end

Declaramos el método initialSetup que lo llamaremos desde viewDidLoad para disponer todos los elementos en la pantalla. el método buttonPressed será llamado cada vez que pulsemos sobre una de las imágenes/botones a modo de celdas que componen nuestra tabla/listado.


#import "ViewController.h"
#import "DetalleGaleriaViewController.h"

@implementation ViewController
@synthesize scrollView;

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Release any cached data, images, etc that aren't in use.
}

#pragma mark - View lifecycle

- (void)viewDidLoad
{
    [self initialSetup];
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.

}

- (void)viewDidUnload
{
    [self setScrollView:nil];
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
}

- (void)viewWillDisappear:(BOOL)animated
{
	[super viewWillDisappear:animated];
}

- (void)viewDidDisappear:(BOOL)animated
{
	[super viewDidDisappear:animated];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations
   return (interfaceOrientation == UIInterfaceOrientationPortrait || interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown );
}

//*****************************************************************

-(void) initialSetup
{

    // Custom initialization

    NSMutableArray* textosArray = [NSMutableArray arrayWithObjects:  @"LIMA", @"PASEO EN BICICLETA", @"PUESTA DE SOL", @"COCHE ABANDONADO", nil];
    CGFloat  margenY=20.0;
    CGFloat  margenMiniatura=30.0;

    CGFloat  margenInicialYSeparador=144.0;
    CGFloat  margenDeSeparador=124.0;

    CGFloat  margenInicialYTexto=128.0;
    CGFloat  margenDeTexto=108.0;

    for (int i=0; i< [textosArray count]; i++) {

        //**********miniatura***********************************
        NSString *fileName= [NSString stringWithFormat:@"min_galeria_00%i.jpg",i];
        UIImage *miniatura = [[UIImage imageNamed:fileName] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 0)];

        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        button.frame=CGRectMake( (scrollView.frame.size.width - miniatura.size.width)/2, margenY, miniatura.size.width, miniatura.size.height);

        // Set the button's image
        [button setBackgroundImage:miniatura forState:UIControlStateNormal];
        button.tag=i;

        // Attach an event
        [button addTarget:self action:@selector(buttonPressed:)
         forControlEvents:UIControlEventTouchUpInside];  

        //*************separador**********************************
        NSString *separadorName= [NSString stringWithFormat:@"separador.jpg"];
        UIImage *separador = [[UIImage imageNamed:separadorName] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 0, 0, 0)];
        UIImageView* separadorView = [[UIImageView alloc] initWithImage:separador];

        separadorView.frame = CGRectMake( 0, margenInicialYSeparador, separadorView.frame.size.width, separadorView.frame.size.height);

        //*******************texto***********************

        UILabel *label =  [[UILabel alloc]  initWithFrame: CGRectMake(button.frame.origin.x, margenInicialYTexto, button.frame.size.width, 14)];
        [label setBackgroundColor:[UIColor clearColor ]];
        label.numberOfLines = 1;
        label.textColor=[[UIColor alloc] initWithRed:164.0 / 255 green:164.0 / 255 blue:164.0 / 255 alpha:1.0];

        UIFont* font=[UIFont systemFontOfSize:12.0];

        [label setFont:font];
        label.text = [textosArray objectAtIndex:i];

        //*******************************************
        margenY= button.frame.origin.y + button.frame.size.height  + margenMiniatura ;
        margenInicialYSeparador= margenY  + margenDeSeparador ;
        margenInicialYTexto= margenY  + margenDeTexto ;

        [self.scrollView addSubview:label];
        [self.scrollView addSubview:button];
        [self.scrollView addSubview:separadorView];

    }

    [self.scrollView setContentSize:CGSizeMake(320,margenY + 8)];

}

-(void) buttonPressed: (id)sender
{
    //NSLog(@"pulsa: %i",[(UIButton*)sender tag]);

    DetalleGaleriaViewController *detalleView = [[DetalleGaleriaViewController alloc] initWithNibName:@"DetalleGaleriaViewController" bundle:nil] ;
    detalleView.tag=[(UIButton*)sender tag];

    [self.navigationController pushViewController:detalleView animated:YES];

}

@end

Dentro de viewDidLoad hemos puesto la llamada a initialSetup. En este método tenemos un Array llamado textosArray donde van a ir los textos de los títulos de nuestro listado. Aprovecharemos este Array para ir maquetando cada una de las partes que tienen nuestras celdas, que van a tener: Un título, un botón (con una imagen de fondo) y un separador (una imagen). Después tenemos una serie de variables que empiezan con margen… y que debemos inicializar con los valores que consideremos oportunos.


 // Custom initialization

    NSMutableArray* textosArray = [NSMutableArray arrayWithObjects:  @"LIMA", @"PASEO EN BICICLETA", @"PUESTA DE SOL", @"COCHE ABANDONADO", nil];
    CGFloat  margenY=20.0;
    CGFloat  margenMiniatura=30.0;

    CGFloat  margenInicialYSeparador=144.0;
    CGFloat  margenDeSeparador=124.0;

    CGFloat  margenInicialYTexto=128.0;
    CGFloat  margenDeTexto=108.0;

Después de la inicialización recorremos textosArray. Las imágenes que van a ir dentro de los botones están nombradas de tal forma que, al recorrer el Array, las podemos llamar fácilmente. Usaremos instancias de UIImage, UIButton y UIImageView y UILabel para disponer los elementos y posteriormente añadirlos como subvistas de scrollView.
Es importante observar como a la propiedad tag de nuestra instancia de UIButton le damos el valor del iterador i del Array y como le añadimos en el evento UIControlEventTouchUpInside una llamada a la función buttonPressed.


      button.tag=i;

      // Attach an event
      [button addTarget:self action:@selector(buttonPressed:)
      forControlEvents:UIControlEventTouchUpInside];  

Cuando se termina de recorrer el array por completo, seteamos el tamaño de scrollView convenientemente para que contenga todas nuestras celdas.


[self.scrollView setContentSize:CGSizeMake(320,margenY + 8)];

A continuación definimos el método buttonPressed que está ligado al evento de pulsar alguno de los botones de nuestras celdas. Es muy sencillo lo que hacemos: Creamos una instancia de DetalleGaleriaViewController llamada detalleView. Esta clase va a tener una propiedad llamada tag y le damos el valor que ya le dimos al botón en initialSetup. Por último mandamos un mensaje a self.navigationController para colocar detalleView como controlador activo. No olvideis importar DetalleGaleriaViewController.h.


-(void) buttonPressed: (id)sender
{

    DetalleGaleriaViewController *detalleView = [[DetalleGaleriaViewController alloc] initWithNibName:@"DetalleGaleriaViewController" bundle:nil] ;
    detalleView.tag=[(UIButton*)sender tag];
    [self.navigationController pushViewController:detalleView animated:YES];

}

Código de DetalleGaleriaViewController


#import <UIKit/UIKit.h>

@interface DetalleGaleriaViewController : UIViewController 

@property (strong, nonatomic) IBOutlet UIScrollView *scrollView;

@property  NSInteger tag;
-(void)initialSetup;

@end

Tenemos 2 propiedades, las ya comentadas scrollView y tag. Tan solo un método, initialSetup. De tag y el valor que le pasemos desde ViewController, dependerá el mostrar una u otra de las imágenes convenientemente nombradas que ya existen en nuestro proyecto.


#import "DetalleGaleriaViewController.h"

@implementation DetalleGaleriaViewController
@synthesize scrollView;
@synthesize tag;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)didReceiveMemoryWarning
{
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];

    // Release any cached data, images, etc that aren't in use.
}

#pragma mark - View lifecycle

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
    [self initialSetup];
}

- (void)viewDidUnload
{
    [self setScrollView:nil];
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait || interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown );
}

//***********************************************************************************************

-(void)initialSetup
{

    NSString *fileName= [NSString stringWithFormat:@"max_galeria_00%i.jpg",self.tag];

    UIImage *foto = [UIImage imageNamed:fileName] ;

    UIImageView* fotoView = [[UIImageView alloc] initWithImage:foto];

    [self.scrollView setContentSize: CGSizeMake(fotoView.frame.size.width, fotoView.frame.size.height)];

    [self.scrollView setScrollEnabled:YES];

    [self.scrollView addSubview:fotoView];

}

@end

llamanos a initialSetup desde viewDidLoad. Dentro de este método vamos a recuperar nuestra imagen nombrada de acuerdo al valor de tag y la añadimos a scrollView como subvista.

Conclusión

Hemos creado una vista de tabla de datos maquetada a nuestro antojo y una vista de detalle en la que se muestra una imagen dentro de un Scroll View; además no hemos usado UITableView ni UITableViewCell.
Para el ejemplo he limitado la orientación a UIInterfaceOrientationPortrait y UIInterfaceOrientationPortraitUpsideDown para no complicar en exceso el tutorial. Espero os sea de utilidad, podeis descargar el proyecto de Xcode completo:

Valora esta entrada:

2 votos. Promedio: 3,50 de 5
1 Estrella2 Estrellas3 Estrellas4 Estrellas5 Estrellas

Entrada archivada en:

Categorías: iOS

Etiquetas:

★ Por Raúl Flores (11 entradas) el

Entradas relacionadas

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *

*