Mostrando entradas con la etiqueta UITableView. Mostrar todas las entradas
Mostrando entradas con la etiqueta UITableView. Mostrar todas las entradas

sábado, 28 de septiembre de 2013

UISearchBar & UITableView: Añadir una barra de búsqueda a una tabla.

Cuando una tabla tiene mucha información, hacer scroll por la ella buscando un elemento determinado puede llegar a ser frustante para el usuario. Una buena solución para mejorar esta situación es añadir un UISearchBar, que se integra con el UITableView y permite aplicar filtros y realizar búsquedas.

Vamos a ver un ejemplo sencillo de como crear una barra para hacer búsquedas y aplicar filtros sobre la información que muestra la tabla.

Crearemos un nuevo proyecto y en el storyboard añadiremos un table view controller embebido en un navigation controller. Colocaremos otro view controller y cablearemos desde la celda prototipo de la tabla hacia él para crear el segue, que será, por ejemplo, de tipo push. Hecho esto, tendremos que tener algo así:

Este último view controller será donde se muestra el detalle de lo seleccionado en la tabla. 

En esta UITableView vamos a mostrar deportes, así que vamos a definir la clase Sport, que será una subclase de NSObject y tendrá dos propiedades una con el nombre del deporte y otra con el tipo (motor, de pelota, acuático, etc.). Además definiremos un Class Method para crear deportes:

Sport.h: el interface por tanto quedará así:

#import <Foundation/Foundation.h>

@interface Sport : NSObject
@property (nonatomic, strong) NSString *type;
@property (nonatomic, strong) NSString *name;

+ (Sport *)newSport:(NSString *)name ofType:(NSString *)type;
@end

Sport.m: y su implementación será esta:

#import "Sport.h"

@implementation Sport
@synthesize name = _name;
@synthesize type = _type;

+ (Sport *)newSport:(NSString *)name ofType:(NSString *)type
{
    Sport *sport = [[self alloc] init];
    sport.name = name;
    sport.type = type;
    
    return sport;
}
@end


A continuación, vamos a añadir una subclase de UITableViewController que llamarermos SportTableViewController.




Esta nueva clase, será la Custom Class de nuestro Table View Controller, por tanto, en el storyboard seleccionaremos el UITableViewController creado inicialmente y en el Identity Inspector la elegiremos:



También vamos a asignar a la celda prototipo el identifier SportCell.



Y a dar un título a la tabla:


En SportTableViewController.h vamos a añadir la propiedad sports, que va a ser un NSArray. En este array meteremos posteriormente los deportes, va a ser el DataSource de la tabla:


#import <UIKit/UIKit.h>

@interface SportTableViewController : UITableViewController
@property (strong, nonatomic) NSArray *sports;
@end

Escribiremos su synthesize en SportTableViewController.m justo debajo de implementation y definiremos su get. También importaremos la clase Sport. Por ahora tendremos algo así:

#import "SportTableViewController.h"
#import "Sport.h"

@interface SportTableViewController ()
@end

@implementation SportTableViewController
@synthesize sports = _sports;



- (NSArray *)sports
{
    if (_sports == nil) {
        _sports = [NSArray arrayWithObjects:
                   [Sport newSport:@"Surf" ofType:@"Acuático"],
                   [Sport newSport:@"Fútbol" ofType:@"Pelota"],
                   [Sport newSport:@"Tenis" ofType:@"Pelota"],
                   [Sport newSport:@"Fórmula 1" ofType:@"Motor"],
                   [Sport newSport:@"kitesurf" ofType:@"Acuático"],
                   [Sport newSport:@"Motocicilismo" ofType:@"Motor"],
                   nil];
    }
    return _sports;
}


En una aplicación real, tendrías vuestros NSArray, NSDictinonary o lo que fuese para cargar en el UITableView, pero para simplificar el ejemplo hemos añadido algunos deportes en nuestro NSArray

Todos los métodos que están comentados y el numberOfSectionsInTableView: los podemos borrar ya que no los vamos a a utilizar.

El que si vamos a utilizar es tableView: numberOfRowsInSection:  que, por ahora, implementaremos así:


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [self.sports count];
}



Con esto estamos indicando que la tabla va a tener tantas filas como elementos tiene nuestro NSArray de deportes.

En el método tableView:cellForRowAtIndexPath: configuramos la celda:



- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"SportCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if ( cell == nil ) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }
    
    Sport *sport = [self.sports objectAtIndex:indexPath.row];
    cell.textLabel.text = sport.name;
    
    return cell;
}

Es el momento de ejecutar el proyecto para ver que todo va bien. Deberéis conseguir esto:



Todo lo hecho hasta ahora ha sido para crear una tabla con datos de ejemplo. A continuación veremos como añadir la barra de búsqueda.

En el Storyboard, seleccionaremos y arrastraremos  un Search Bar and Search Display Controller a nuestro Table View Controller.


El Search Bar nos proporciona un text field en el que introducir el texto a buscar, un botón de búsqueda y otro de cancel y, el search display controller, proporciona  una barra de búsqueda y una tabla que muestra el resultado de la búsqueda de datos manejados por la otra tabla.


Debemos indicar que nuestro UITableViewController va a implementar dos delegates, el de Search Bar y el de Search Display. Luego modificaremos nuestro SportTableViewController, que ahora será asÍ: 



#import <UIKit/UIKit.h>

@interface SportTableViewController : UITableViewController <UISearchBarDelegate, UISearchDisplayDelegate>
@property (strong, nonatomic) NSArray *sports;
@end

También crearemos dos nuevas propiedades para SportTableViewController; una será un array mutable que contendrá los deportes resultantes de aplicar el filtro de búsqueda y la otra será un IBOutlet de la barra de búsqueda. Este segundo lo vamos a añadir cableando desde el storyboard:







Estas dos últimas propiedades las he creado locales, el SportTableViewController.m:


@interface SportTableViewController ()
@property (weak, nonatomic) IBOutlet UISearchBar *searchBar;
@property (strong, nonatomic) NSMutableArray *filteredSports;
@end

@implementation SportTableViewController
@synthesize sports = _sports;
@synthesize searchBar = _searchBar;
@synthesize filteredSports = _filteredSports;




Ahora añadiremos el siguiente método para realizar el filtro.


#pragma mark Filter
-(void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope {
    [self.filteredSports removeAllObjects];

    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.name contains[c] %@",searchText];
    self.filteredSports = [NSMutableArray arrayWithArray:[self.sports filteredArrayUsingPredicate:predicate]];
}

Básicamente primero reinicia el array de deportes filtrados, por si tuviera cargado algo anteriormente, y luego utiliza NSPredicate para ver que deportes de los que existen en el NSArray de deportes coinciden con el texto introducido. 

Implementamos un método de UISearchDisplayControllerDelegate que será el encargado de llamar al filtro anterior cada vez que el usuario introduce un  nuevo carácter en la barra.


#pragma mark - UISearchDisplayController Delegate
-(BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
    [self filterContentForSearchText:searchString scope:
     [[self.searchDisplayController.searchBar scopeButtonTitles] objectAtIndex:[self.searchDisplayController.searchBar selectedScopeButtonIndex]]];

    return YES;
}



Dentro de tableView:cellForRowAtIndexPath: habrá que hacer un cambio para comprobar si estamos mostrando la tabla de deportes o la tabla con los resultados del filtro. Cambiaremos la linea


Sport *sport = [self.sports objectAtIndex:indexPath.row];


por


Sport *sport;
    
if (tableView == self.searchDisplayController.searchResultsTableView) {
        sport = [self.filteredSports objectAtIndex:indexPath.row];
} else {
        sport = [self.sports objectAtIndex:indexPath.row];
}


Lo mismo en tableView:numberOfRowsInSection:, habrá que comprobar que tabla se está mostrando:


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    NSInteger result;

    if (tableView == self.searchDisplayController.searchResultsTableView) {
        result = [self.filteredSports count];
    } else {
        result = [self.sports count];
    }
    
    return result;
}



Y con esto ya lo tendríamos:




Si queremos que, inicialmente, la barra de búsqueda esté oculta, podemos añadir este código en el viewDidLoad:


CGRect newBounds = self.tableView.bounds;
newBounds.origin.y = newBounds.origin.y + self.searchBar.bounds.size.height;
self.tableView.bounds = newBounds;



Solo nos quedaría mandar los datos al viewcontroller que va a contener el detalle. Bastaría con implementar el tableView:didSelectRowAtIndexPath

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 

 // Perform segue to sport detail 
 [self performSegueWithIdentifier:@"sportDetailSegue" sender:tableView]; }
Y luego en prepareForSegue: pasar la información necesaria al ViewController del detalle, pero eso ya os lo dejo a vosotros.

@Fin





viernes, 1 de febrero de 2013

Asignar el Anchor de una PopOver a una UITableViewCell

Vamos a ver una forma de colocar el Anchor de un Segue de tipo Popover apuntando a una celda de una tabla dinámica. En el storyboard lo que tenemos es un prototipo de la celda, por lo que que si intentamos cablear el segue hacia ella, nos va a dar un error.

Así que os voy a contar como podemos solucionarlo. Se trata de crear un botón oculto que va a ser al que va a apuntar la ventana popover. Y moveremos su posición en función de la celda seleccionada.

Lo primero que haremos es crear el popover segue desde el ViewControler. Le asignaremos un Identifier, por ejemplo cellSegue.


Después añadiriremos a nuestra View un UIButon. Lo podemos colocar en cualquier sitio por qué va a tener su propiedad Hidden marcada, luego va a estar oculto. En el texto del botón voy a poner Anchor, aunque esto nos daría igual.


Y seleccionaremos el botón creado como Anchor del segue.


Ahora crearemos un outlet para el botón, que voy a llamar popOverAnchor:


@property (weak, nonatomic) IBOutlet UIButton *popOverAnchor;

En el @implementation colocaremos su synthesize:

@synthesize popOverAnchor = _popOverAnchor;


Deberemos implementar el método tableView:didSelectRowAtIndexPath: del UITableViewDelegate de esta forma:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{


//Recuperamos la celda que se ha seleccionado
        UITableViewCell *tableViewCell = [tableView cellForRowAtIndexPath:indexPath];
        
//Creamos el CGRect al que va a apuntar el popover en función de la posición de la celda seleccionada
        CGRect rect = CGRectMake(tableViewCell.frame.origin.x + tableViewCell.frame.size.width/2, tableViewCell.center.y + tableView.frame.origin.y - tableView.contentOffset.y, 1, 1);
        
//Movemos el botón a esa posición
        self.popOverAnchor.frame = rect;

//Y para terminar llamamos al segue        
        [self performSegueWithIdentifier:@"cellSegue" sender:tableViewCell];

}

Y ya lo tenemos, como podéis ver, el botón se mueve al centro de la celda seleccionada y al ser el anchor de la ventana popover, conseguimos nuestro proposito.

@Fin

sábado, 21 de abril de 2012

Editando un UITableView: insertar filas

Cuando utilicéis UITableView, en muchas ocasiones en necesario permitir al usuario insertar y eliminar filas.  Borrarlas es bastante sencillo, pero insertarlas es algo un poco más complejo. Os voy a mostrar un ejemplo de como se puede hacer. 



Supongamos que tenemos el típico table view controller con el botón de editar en la parte superior derecha, algo del estilo a la siguiente imagen:



Y queréis que, al pulsar el botón Edit, además de que aparezca el simbolo a la derecha para borrar filas, también os añada una nueva al final en la que podais introducir un nuevo texto. Es decir, algo así:



La plantilla que nos proporciona Apple para el UITableViewController ya viene comentado lo que necesitamos para mostrar el botón Edit.  Está en el viewDidLoad y lo único que hay que hacer es descomentarlo. Se trata de la siguiente línea:

self.navigationItem.rightBarButtonItem = self.editButtonItem;

Con esto ya se consigue que, al pulsarlo, nos muestre el símbolo de borrado en las filas existentes del Table View. Pero lo que necesitamos es añadir una fila que permita al usuario introducir nueva información.

Para ello primero vamos a implementar el método tableView:numberOfRowsInSection: del UITableViewDataSource Protocol

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

{
    if (self.editing)
        return [self.propertyListData count] + 1;
    else 
        return [self.propertyListData count];
}

Como veis, se comprueba si estamos en modo de edición y, si es así, devuelve el número de filas actuales mas una. propertyListData es en este caso un NSArray que está definido así en el interface:

@property (weak, nonatomic) NSArray *propertyListData;

También hay que añadir código al método tableView:cellForRowAtIndexPath:


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = 
@"PropertyListCell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    
    // Configuramos la celda
    if (indexPath.row >= [self.propertyListData count]  && self.editing// Si es la nueva celda añadida y estamos en modo edición
    {

    // Creamos un campo de texto en la nueva fila
    // Estableciendo las propiedades que se necesiten
        UITextField *addTextField = [[UITextField alloc] initWithFrame:CGRectMake(10, 6, 250, 25)];
        addTextField.adjustsFontSizeToFitWidth = YES;
        addTextField.textColor = [UIColor blackColor];
        
      addTextField.placeholder = NSLocalizedString(@"Nueva fila", "New UITableView row");
        addTextField.keyboardType = UIKeyboardTypeDefault;
        addTextField.returnKeyType = UIReturnKeyDone;
        
        addTextField.backgroundColor = [UIColor whiteColor];
        addTextField.autocorrectionType = UITextAutocorrectionTypeNo
        addTextField.autocapitalizationType = UITextAutocapitalizationTypeWords
        addTextField.textAlignment = UITextAlignmentLeft;
        addTextField.tag = 0;
        addTextField.font = [UIFont systemFontOfSize:14];
        addTextField.delegate = self;        
        addTextField.clearButtonMode = UITextFieldViewModeWhileEditing;
        [addTextField setEnabled: YES];

        // Añadimos el textfield al contentView de la celda
        [cell.contentView addSubview:addTextField];
        

    }
    else 
    {
       cell.textLabel.text = [self.propertyListData objectAtIndex:indexPath.row];
    }
    
    cell.textLabel.font = [UIFont systemFontOfSize:14];

    return cell;
}



Otro método a implementar es tableView:editingStyleForRowAtIndexPath: En él se va a indicar el tipo de símbolo que vamos a añadir a la izquierda de la celda cuando esté en modo edición que, en este caso, será el de borrado o el de inserción. Si este método no se sobreescribe aparece el de borrado en todas las filas. Hay que tener en cuenta que las filas se numeran de 0 a n. Entonces, quedará así:

- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath 
{
    
    if (indexPath.row == [self.propertyListData count]) 
    {
        return UITableViewCellEditingStyleInsert;
    } 
    else 
    {
        return UITableViewCellEditingStyleDelete;
    }
    
}



Todo esto no es suficiente, también necesitamos indicarle al TableView que hay que insertar una nueva fila y se debe indicar en el momento apropiado. El método adecuado en donde hacerlo es setEditing:animated.

- (void)setEditing:(BOOL)editing animated:(BOOL)animated 
{
    [super setEditing:editing animated:animated];
//Definimos el indexpath en donde vamos a añadir la nueva fila. En este caso será la última fila

    NSIndexPath *ip = [NSIndexPath indexPathForRow:[self.propertyListData count] inSection:0];

    if (editing) //En modo edición, añadimos la fila
    {
        [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:ip]
         withRowAnimation:UITableViewRowAnimationBottom];
    } 
    else 
    {
        //Si hemos añadido datos a la nueva fila, las tendremos que añadir al modelo
        if (/*lo que sea, dependerá de vuestra codificación*/
        {

        }
        else //Y si no, la borramos
        {
            [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:ip] withRowAnimation:UITableViewRowAnimationFade];
        }   
    }   
}

Y ya lo tendríamos. Por hoy nada más.