Barras de búsqueda en iOS

junio 12, 2011

Seguramente en muchas de nuestras aplicaciones de iOS hemos tenido que mostrar una tabla de datos con un montón de entradas. Desde el punto de vista del usuario, es bastante molesto tener que buscar el elemento que quiere entre todos los elementos.

Para mejorar la usabilidad, se plantean varias alternativas. Podemos separar los datos en varios niveles, de tal manera que dividamos los datos en varios bloques, sin embargo, esta solución tiene como inconveniente que obligamos a navegar por varias pantallas.

Concretamente en iOS tenemos varias alternativas para “ordenar” nuestros datos a nivel visual. Podemos crear listas por secciones e incluso incluir un indice para saltar entre los diferentes datos al mas puro estilo iTunes o la aplicación que muestra los Contactos.

Sin embargo, aunque ayuda, no acaba de solucionar el problema de tener que buscar la entrada que queremos en nuestra tabla.

Aquí es donde entra el campo de búsqueda que seguro habéis visto en numerosas aplicaciones de iOS como Facebook, Twitter o iTunes. En esta entrada vamos a ver como añadir una barra de búsqueda a nuestra aplicación.

Dentro del conjunto de controles estándar de iOS, tenemos la clase UISearchBar, que representa el cuadro de texto con esquinas redondeadas y la lupa a la que estamos acostumbrados. Sin embargo, este elemento es solo la parte gráfica del sistema de búsqueda. Además de esta clase, vamos a necesitar tratar con la clase UISearchDisplayController, que será la que enlazada a los ViewControllers habituales, nos van a proporcionar todo el sistema de búsqueda que hemos visto en las aplicaciones antes comentadas.

Esta instancia de UISearchDisplayController no vive sola, la mayoría de nuestras vistas, están gestionadas por una clase “Controller” que hereda de UIViewController, en esta clase esta definida una propiedad llamada searchDisplayController, a la que podemos asignar nuestra clase controladora del campo de búsqueda. Haciendo esto ganamos bastante funcionalidad.

Si hemos visto el funcionamiento de la barra de búsqueda, hemos visto como al tocar para introducir texto, esta barra se coloca en la parte superior de forma animada, sombreando además la vista entera, además, en algunos casos salen unos botones debajo de la barra de búsqueda, que indican donde se esta realizando la búsqueda. Pues bien, todo este comportamiento esta “de propina” si asignamos la propiedad del ViewController a nuestro SearchController.

Para llevar esto a la práctica, suponiendo que tenemos un proyecto con un NavigationController, tenemos que ir a la vista que se carga como contenido del navigation controller y añadir el SearchViewController.

Automáticamente XCode asigna los outlets, pero para saber como funciona, no es mala idea cotillearlos un poco. XCode establece el outlet del UINavigationController llamado searchDisplayController al SearchDisplayController que acabamos de añadir a la vista. Normalmente el NavigationController será el File’s Owner del fichero, y así lo vemos en la captura de un poco más arriba.

Hasta ahora, solo hemos hablado de la parte visual, pero después de crear el SearchDisplayController y asignarlo a la propiedad en el ViewController, si introducimos texto, vemos como no pasa nada. El siguiente paso consiste en decir como vamos a mostrar los datos de la búsqueda.

Para mostrar los datos, una de nuestras clases tiene que implementar varios protocolos habituales de una UITableView, como son UITableViewDataSource y UITableViewDelegate. Como veis, al fin y al cabo el proceso se reduce a mostrar una vista reducida de nuestros datos, filtrada por la condición de búsqueda.

Como vemos en la captura anterior, los outlets del SearchDisplay controller respecto al origen de los datos, esta establecidos al File’s Owner.

En cuanto a la estrategia de implementación, se pueden seguir varias aproximaciones. Por un lado se puede hacer que la misma clase sea DataSource de ambas tablas, es decir, la tabla con todos los datos y la tabla con los datos de la búsqueda, sin embargo esta solución tiene el problema que los mismos métodos tienen que devolver cosas diferentes. Personalmente no me gusta nada esta solución.

Por otro lado, se pueden tener dos clases diferentes, sin embargo hay que tener en cuenta que el modelo de datos a la que acceden ambas clases, es compartido, ya que la búsqueda se realiza sobre los datos que esta mostrando el ViewController de turno.

Estas dos alternativas están pensadas a cuando la búsqueda se realiza sobre una tabla de datos, sin embargo, no tiene por que ser siempre así, la búsqueda podemos hacerla sobre cualquier vista que queramos. En tal caso no habrá mucho problema en unir en la misma clase que gestiona la vista, los métodos de gestión de la tabla de los resultados de la búsqueda.

A continuación os pongo el código de una implementación, el código no pretende ser eficiente, si no mostrar el funcionamiento 😉

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
    [filteredData release];
    filteredData = [[NSMutableArray alloc] init];
    for ( NSString* name in tableData )
    {
        if ([[name lowercaseString] rangeOfString:[searchText lowercaseString]].location != NSNotFound )
            [filteredData addObject:name];
    }
}

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

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
    if ( cell == nil )
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"];
    
    cell.textLabel.text = [filteredData objectAtIndex:indexPath.row];
    return cell;
}

Ya que mucha parte del funcionamiento, esta embebido en los ficheros de Interface Builder, he subido el proyecto de prueba a github, por si alguien quiere echarle un ojo.