Cuando tenemos aplicaciones que se comunican con servicios web, las respuestas suelen por normal general en XML o JSON.
En este tutorial vamos a aprender como parsear (recorrer y analizar) este XML, mostrando los datos en una tabla.
Supongamos que enviamos con nuestra aplicación una petición a un
servidor que en función del parámetro nos devuelve unos resultados u
otros, pero siempre con el mismo formato.
Pongamos que queremos recuperar la lista de peliculas de dos actores distintos.
Nuestra aplicación cuenta con una lista de actores que tienen unos “id_cator” asociado.
Si por ejemplo queremos saber las películas de Bruce Willis,
pasaremos al servicio el id_actor de Bruce, de forma similar a esto:
Pero si queremos las películas de Nicolas Cage, será:
*Estas url no existen, son solo ejemplos.
La respuesta podría ser similar a:
<listado>
<pelicula id=«1»>
<titulo>La Jungla 4.0</titulo>
<descripcion>La mejor pelicula de los ultimos tiempos. El agente Jonh McCain…</descripcion>
<anio>2010</anio>
</pelicula>
<pelicula id=«2»>
<titulo>El sexto sentido</titulo>
<descripcion>La película con un final prometedor e insesperado.</descripcion>
<anio>1998</anio>
</pelicula>
<pelicula id=«3»>
<titulo>Falsas Apariencias</titulo>
<descripcion>Mezcla de acción y comedia junto a Mathew Perry</descripcion>
<anio>2004</anio>
</pelicula>
</listado>
Como vemos la respuesta contiene un Nodo principal. Por norma, todo XML debe tener un nodo padre principal.
Después vemos claramente cuales son los distintos items, en este caso, delimitados por “película”
Dentro tenemos 4 elementos:
– El id de la pelicula (dentro de “pelicula id=”id_pelicula”»
Utilizamos un atributo para mostrar después como recuperarlo. Los
atributos hacen ahorrar bastante código en el XML, lo que para las
respuestas web son más eficientes).
– El título (‘<‘titulo’>”<‘/titulo’>‘).
– La descripción (‘<‘descripcion’>”<‘/descripcion’>‘).
– El año de estreno (‘<‘anio’>”<‘/anio’>‘).
Para realizar este tutorial será necesario tener conocimiento de UITableview (tutorial disponible) y UINavigationController (tutorial disponible)
En otro tutorial explicaremos como conectar a un servicio web de forma asíncrona y recuperar sus valores parapoder interacturar.
En este ejemplo, pondemos crear en un NSString cada XML.
Creamos un nuevo proyecto de tipo Single View Application.
Lo primero que vamos a hacer es crear una clase que contendrá los valores de las películas.
Creamos un NSObject.
En Pelicula.h:
#import@interface Pelicula : NSObject { } @property(nonatomic) int id_pelicula; @property(nonatomic, retain) NSString *titulo; @property(nonatomic, retain) NSString *descripcion; @property(nonatomic) int anio; @end
En Pelicula.m:
#import "Pelicula.h" @implementation Pelicula @synthesize id_pelicula, titulo, descripcion, anio; @end
Como nuestra aplicación va a navegar entre ventanas le añadimos al AppDelegate un UINavigationController, como ya se ha hecho en el tutorial correspondiente.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; // Override point for customization after application launch. self.viewController = [[ViewController alloc] initWithNibName:nil bundle:nil]; UINavigationController *navController = [[UINavigationController alloc]initWithRootViewController:self.viewController]; self.window.rootViewController = navController; [self.window makeKeyAndVisible]; return YES; }
Én viewController.xib insertamos dos botones.
Y les asignamos el tag 0 y 1 respectivamente.
Creamos un UIViewController ListadoViewController en el proyecto.
Dejaremos el código de ListadoViewController.h así:
#import#import "Pelicula.h" @interface ListadoViewController : UIViewController { NSMutableArray *peliculas; Pelicula *auxPelicula; NSMutableString *currentElementValue; IBOutlet UITableView *tableView; } @property(nonatomic, retain) NSString *stringXML; @end
Como vemos hemos importado el objeto Pelicula. Hemos declarado al controlador como delegado de UITableView y de NSXMLParser, que será el que implementaremos en el tutorial.
Además declaramos el array que contendrá las películas del actor, el
string que almacenará el contenido de cada etiqueta del XML, y la tabla
para mostrar los resultados.
stringXMl hará del contenido XML que en una situación real recuperaríamos de un servicio web (o de un fichero local).
Ahora volvemos a viewController.m, y creamos un método que
llame al listado, y en función del tag que tenga el botón que hace la
llamada, mostraremos un xml u otro.
- (IBAction)mostrarPeliculasPorActor:(id)sender { UIButton *auxButton = (UIButton*)sender; NSString *stringXML = @""; switch (auxButton.tag) { case 0: { stringXML = [stringXML stringByAppendingString:@"" ]; stringXML = [stringXML stringByAppendingString:@"" 1">"
];
stringXML = [stringXML stringByAppendingString:@«
stringXML = [stringXML stringByAppendingString:@«
stringXML = [stringXML stringByAppendingString:@«
stringXML = [stringXML stringByAppendingString:@««];
stringXML = [stringXML stringByAppendingString:@«
stringXML = [stringXML stringByAppendingString:@«
stringXML = [stringXML stringByAppendingString:@«
stringXML = [stringXML stringByAppendingString:@«
stringXML = [stringXML stringByAppendingString:@««];
stringXML = [stringXML stringByAppendingString:@«
stringXML = [stringXML stringByAppendingString:@«
stringXML = [stringXML stringByAppendingString:@«
stringXML = [stringXML stringByAppendingString:@«
stringXML = [stringXML stringByAppendingString:@««];
stringXML = [stringXML stringByAppendingString:@««];
break;
}
case 1:
{
stringXML = [stringXML stringByAppendingString:@«
stringXML = [stringXML stringByAppendingString:@«
stringXML = [stringXML stringByAppendingString:@«
stringXML = [stringXML stringByAppendingString:@«
stringXML = [stringXML stringByAppendingString:@«
stringXML = [stringXML stringByAppendingString:@««];
stringXML = [stringXML stringByAppendingString:@«
stringXML = [stringXML stringByAppendingString:@«
stringXML = [stringXML stringByAppendingString:@«
stringXML = [stringXML stringByAppendingString:@«
stringXML = [stringXML stringByAppendingString:@««];
stringXML = [stringXML stringByAppendingString:@«
stringXML = [stringXML stringByAppendingString:@«
stringXML = [stringXML stringByAppendingString:@«
stringXML = [stringXML stringByAppendingString:@«
stringXML = [stringXML stringByAppendingString:@««];
stringXML = [stringXML stringByAppendingString:@««];
break;
}
default:
break;
}
ListadoViewController *listadoController = [[ListadoViewController alloc]initWithNibName:nil bundle:nil];
NSLog(@«XML = %@», stringXML);
[listadoController setStringXML:stringXML];
[self.navigationController pushViewController:listadoController animated:YES];
}
Ahora tenemos que llamar desde el listado al parser XML (NSXMLParser).
Vamos a ListadoViewController.m y en viewDidLoad añadimos lo siguiente:
- (void)viewDidLoad { [super viewDidLoad]; // Establecemos el delegate de la tabla, el dataSource y el alto de tabla. tableView.delegate = self; tableView.dataSource = self; tableView.rowHeight = 90.0; // Simulamos la respuesta del servidor con nuestro stringXML. // Lo convertimos a NSData para evitar errores de codificación. NSData *responseData = [stringXML dataUsingEncoding:NSUTF8StringEncoding]; // Si tuvieramos el XML en un archivo local se podría hacer así: // NSData *responseData = [NSData dataWithContentsOfFile:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"peliculas.xml"]] // Creamos el parser: NSXMLParser *parser = [[NSXMLParser alloc] initWithData:responseData]; // Lo establecemos como delegado, ya que nuestro ListadoViewController implementará el protocolo. [parser setDelegate:self]; // Y parseamos el XML, a falta de algún error: // - Etiquetas sin cerrar // - Mala sintaxis // - Problemas de codificación // - XML sin etiqueta raiz if(![parser parse]) NSLog(@"Error al parsear"); else NSLog(@"OK Parsing"); }
Añadimos el método que detecta las aperturas de los nodos (listado,pelicula,titulo,descripcion y anio):
// Este método detecta las etiquetas que abren. -(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict { // Comprobamos que etiqueta se ha detectado if([elementName isEqualToString:@"listado"]){ // Iniciamos el listado // En principio este objeto es nil, pero no está de más comprobar. if(peliculas == nil) { peliculas = [[NSMutableArray alloc]init]; } else { [peliculas removeAllObjects]; } } else if([elementName isEqualToString:@"pelicula"]){ // Al detectar que abre la etiqueta película creamos el objeto que contendrá los datos. auxPelicula = [[Pelicula alloc] init]; // En XMl los datos puedes formar parte de una etiqueta, siendo su atributo. Esto ahorra en tamaño para el XML cuando tenemos muchos datos. if([attributeDict objectForKey:@"id"] != nil) { // Obtenemos el id de la película. auxPelicula.id_pelicula = [[attributeDict objectForKey:@"id"]intValue]; } } }
Añadimos ahora el método que lee y almacena el contenido de las etiquetas. Este podemos dejarlo tal cual en cualquier aplicación que hagamos:
// Este método detecta contenido entre etiquetas, siempre lo dejaremos así.
-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { if(!currentElementValue) { currentElementValue = [[NSMutableString alloc]initWithString:string]; } else { [currentElementValue appendString:string]; } }
Pasamos ahora al que seguro es más importante.
// Este método detecta las etiquetas que cierran y es donde se comprueban los valores entre etiquetas para completar los objetos inicializados. -(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { // Si es el raiz, sabemos que ha terminado el XML, y por tanto es tonteria, seguir haciendo comprobaciones. Finalizamos el parser. if([elementName isEqualToString:@"listado"]){ return; // Si es el objeto el que cierra, podemos añadir el objeto al listado y finalizarlo, pues ya tendrá todas las propiedades. } else if([elementName isEqualToString:@"pelicula"]){ [peliculas addObject:auxPelicula]; auxPelicula = nil; // Si es un parametro del objeto, se lo añadimos. } else if([elementName isEqualToString:@"titulo"]) { [auxPelicula setTitulo:currentElementValue]; // Y así con todos.... } else if([elementName isEqualToString:@"descripcion"]) { [auxPelicula setDescripcion:currentElementValue]; } else if([elementName isEqualToString:@"anio"]) { [auxPelicula setAnio:[currentElementValue intValue]]; } // Es importante liberar el contenido de currentElementValue para que no siga concatenando el valor actual con el siguiente. currentElementValue = nil; }
Una vez finalizado solo hay que mostrar los resultados en la tabla, estos métodos están explicados en el correspondiente tutorial de listar datos en iOS con UITableView.
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return[peliculas count]; } -(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { // Normalmente recuperaremos del array, según la posicion de la fila.. Pelicula *aPelicula = [peliculas objectAtIndex:indexPath.row]; // Creamos la celda (o fila). UITableViewCell *cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"] ; /*-------------------------------------*/ /* TÍTULO DE PELÍCULA */ /*-------------------------------------*/ // Creamos una etiqueta para la celda. UILabel *tituloLabel = [[UILabel alloc] initWithFrame: CGRectMake( 10.0, 0.0, self.view.frame.size.width - 20.0, 20.0)]; tituloLabel.textAlignment = NSTextAlignmentCenter; // Insertamos el texto de la etiqueta. tituloLabel.text = [NSString stringWithFormat:@"%@",aPelicula.titulo]; // Añadimos el label a la celda. [cell.contentView addSubview:tituloLabel]; /*-------------------------------------*/ /* DESCRIPCIÓN DE PELÍCULA */ /*-------------------------------------*/ // Creamos una etiqueta para la celda. UILabel *descTextView = [[UILabel alloc] initWithFrame: CGRectMake( 10.0, 25.0, self.view.frame.size.width - 20.0, 40.0)]; descTextView.numberOfLines = 2; // Insertamos el texto de la etiqueta. descTextView.text = [NSString stringWithFormat:@"%@",aPelicula.descripcion]; // Añadimos el label a la celda. [cell.contentView addSubview:descTextView]; /*-------------------------------------*/ /* AÑO DE PELÍCULA */ /*-------------------------------------*/ // Creamos una etiqueta para la celda. UILabel *anioLabel = [[UILabel alloc] initWithFrame: CGRectMake( self.view.frame.size.width - 60.0, 70.0, 60.0, 20.0)]; // Insertamos el texto de la etiqueta. anioLabel.text = [NSString stringWithFormat:@"%d",aPelicula.anio]; // Añadimos el label a la celda. [cell.contentView addSubview:anioLabel]; /*-------------------------------------*/ // Y finalizamos devolviendo la celda return cell; }
Antes de terminar el código, vamos a viewDidLoad y justo después del parseo recargamos los datos de la tabla:
if(![parser parse]) NSLog(@"Error al parsear"); else NSLog(@"OK Parsing"); [tableView reloadData];
Por último queda ir al ListadoViewController.xib y arrastrar un tableView.
Le asignamos el IBOutlet de tableView, y las propiedades delegate y datasource:
Podéis probar como al iniciar al simulador.
Si pulsamos sobre el botón de Bruce Willis, nos muestra las que hemos añadido a su xml. Lo mismo pasa con Nicolas Cage.
Esperamos que os haya gustado este tutorial de recorrido y análisis de XML en iOS, pues el parseo XML es muy útil para cualquier comunicación con servidores.
FUENTE: APRENDEMOS.COM