Amazon S3 en mi iPhone (parte II)
En el post anterior intenté describir a grandes rasgos algunos aspectos básicos de los Amazon Web Services. En este post estará centrado en los detalles, mostrando como podemos crear un Bucket o cómo subir una imagen a uno de ellos. Para hacerlo un poco más ameno, el código de ejemplo estará basado en una característica nueva de IOS 5 -> Storyboard.
Tengo que confesar que solo he cacharreado con esta funcionalidad y que al principio me parecía un poco aburrido, pero la realidad es que según voy conociendo un poco más los detalles creo que es bastante “productivo”.
El StoryBoard de este ejemplo tiene esta apariencia:
Y el modo de mostrar la información de los buckets y sus objetos, ha sido a través de una serie de TableViews desde las cuales podemos añadir nuevos elementos. No he incluido dentro del proyecto la opción de borrar en ninguna de las vistas, por acotar el alcance del ejemplo, no entraña ninguna dificultad. Dejo en este enlace el código fuente:
https://github.com/Byjuanamn/codewriter/tree/master/EjemploS3enIOS
Mostrando los buckets
Al iniciar esta app queremos ver la lista de los buckets que tengo en mi suscripción de AWS. Lo primero que tenemos que hacer es crearnos una instancia de la clase "AmzonS3Client", esta clase nos proporcionará el punto de entrada y ejecución de todas las acciones que deseemos realizar, como por ejemplo consultar los buckets. El código podría tener esta forma:
AmazonS3Client *s3Client = [[AmazonS3Client alloc] initWithAccessKey:kACCESS_KEY
withSecretKey:kSECRET_KEY];
NSArray *listaBuckets;
@try {
listaBuckets = [s3Client listBuckets];
for (S3Bucket *bucket in listaBuckets) {
NSLog(@"Buckets : %@",bucket.name);
}
}
@catch (AmazonClientException *exception) {
NSLog(@"%@",exception.description);
}
Cuando iniciamos una instancia de la clase AmazonS3Client lo hacemos con el metodo "initWithAccessKey:withSecretKey:" pasando los valores proporcionado por Amazon, en este caso interesa crear un par de macros para almacenarlos, ya que los usaremos constantemente.
Otro detalle del código es el uso de excepciones, en este SDK siempre se usa siguiendo esta plantilla, es decir, el bloque del try/catch enmarca la llamada a los metodos que acceden a los servicios, en este caso al metodo listBuckets.
Como vemos en el código el mensaje [s3Client listBuckets] nos devuelve un NSArray de objetos de tipo S3Bucket. Esto es otra noticia, el SDK de AWS tiene un mapeo de clases súper atomizado de componentes de los servicios, hay clases para cada uno de los servicios.
Para mostrar el array de buckets en un UITableView podríamos dar los siguientes pasos:
1 Enlazaremos el array como datasouce del tableview, para esto primero crearemos en el editor de Storyboard un UITableViewController y lo conectaremos con un Navigation Controller. Debería tener un parecido similar al de la siguiente imagen:
En el Attribute inspector, teniendo seleccionado el Navigation Controller, tenemos que marcar en la sección "View Controller" la opción "Initial Scene", para asegurarnos que esta sera la escena inicial de la app.
Antes de configurar el Table View, añadiremos una nueva clase de tipo UITableViewController para desarrollar el comportamiento de la misma. Desde el Navigator de xCode con botón derecho, seleccionaremos "New File->Cocoa Touch->Objective-C class", llamaremos a la nueva clase "S3ViewController" y recordad que está será una subclase de tipo UITableViewController (tal y como se muestra en la siguiente imagen)
De nuevo regresamos al Storyboard para "asociar" nuestra nueva clase con el table view que hemos dibujado anteriormente, esta asociación se hace cambiando la "Custom Class" por defecto a la Clase S3ViewController:
Cambiamos el estilo de la celda y le daremos un identificador que usaremos un poco más adelante.
Vamos ahora al fichero S3ViewController.h para incluir algunos imports y declarar una propiedad que utilizaremos para guardar nuestros Buckets como un array de objetos S3Bucket, esta propiedad será también el data source del tableview.
#import <AWSiOSSDK/S3/AmazonS3Client.h>
#import "KeyS3.h" //OJO este fichero no esta incluido en el ejemplo
@interface S3ViewController : UITableViewController
@property (nonatomic, strong) NSMutableArray *arrayBuckets;
En el .m incluiremos los siguientes cambios
@synthesize arrayBuckets = _arrayBuckets;
para sintetizar nuestra propiedad y los estos para dar soporte a nuestro table view:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.arrayBuckets count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"buckectObjectCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Mostraremos el nombre del buckect y la fecha de crecion
cell.textLabel.text = [[self.arrayBuckets objectAtIndex:indexPath.row]valueForKey:@"name"] ;
cell.detailTextLabel.text = [[self.arrayBuckets objectAtIndex:indexPath.row]valueForKey:@"creationDate"];
return cell;
}
Y ahora incluiremos el siguiente código en el método DidFinishLaunchingWithOptions de nuestro AppDelegate:
AmazonS3Client *s3Client = [[AmazonS3Client alloc] initWithAccessKey:kACCESS_KEY
withSecretKey:kSECRET_KEY];
NSArray *listaBuckets;
@try {
listaBuckets = [s3Client listBuckets];
}
@catch (AmazonClientException *exception) {
NSLog(@"%@",exception.description);
}
UINavigationController *navigationController = (UINavigationController *) self.window.rootViewController;
S3ViewController *s3ViewController = [[navigationController viewControllers] objectAtIndex:0];
s3ViewController.arrayBuckets = [[NSMutableArrayalloc]initWithCapacity:[listaBuckets count]];
for (S3Bucket *bu in listaBuckets) {
[s3ViewController.arrayBucketsaddObject:bu];
}
Con este código además de crear la navegación de la app, estamos pidiendo nuestros buckets y asignadolos a la propiedad "arrayBuckets" de la clase S3ViewController.
En este punto podríamos compilar el proyecto y ejecutarlo en el simulador, si tenemos algún bucket debería aparecer en la lista, pero nuestra app no hace nada mas que esto, vamos a dotar de un poco más de funcionalidad añadiendo la posibilidad de crear nuevos buckets.
Ya que estamos en el Storyboard continuaremos con el pinta y colorea :-)
Incluiremos en nuestro table view un botón de tipo UIBarButtonItem, en el Attributes Inspector, cambiaremos un poco su estilo modificando el atributo Identifier que por defecto es custom a Add.
Los Segues entran en acción
Cuando pulsemos el botón de añadir vamos a mostrar otra vista en la que capturaremos el nombre del nuevo Bucket, para conseguir este resultado añadiremos al Storyboard un nuevo table view, para que sea navegable tenemos que asociarlo a un navigation controller, esto se consigue seleccionando en el menú Editor
Para completar esta "asociación" repetiremos el mismo proceso creando una nueva clase llamada "BuckectViewController", al igual que la clase S3ViewController también será una subclase de tipo UITableViewController. Recordad cambiar desde el Storyboard la "custom class" para vincular nuestra nueva clase con el "dibujo" de table view que acabamos de hacer.
En esta vista vamos a usar el table view para capturar el nombre del nuevo bucket, podría ser tan sencillo como usar un UITextField y poco más. En este caso he jugado un poco con el tableview para usarlo como punto de entrada, esto es opcional y como decía se puede hacer mucho más simple.
Añadiremos desde Storyboard dos botones UIBarButtonItem y en el Attributes Inspector cambiaremos el identifer, uno será "Done" y el otro "Cancel".
Ahora seleccionaremos el table view, lo dejaremos "agrupado" y para que solo tenga una fila, tal y como aparece en la siguiente imagen.
Ahora en la celda del table view arrastraremos un control de tipo UITextField. En este punto deberíamos realizar las conexiones de nuestro diseño con la clase BuckectViewController, para hacer esto activaremos el Asistant editor y dejando pulsado la tecla control arrastraremos el UITextField en la definición de la clase. Al soltarlo nos aparecerá el siguiente diálogo:
Ahora estamos creando una propiedad que estará vinculada al TextField, repetiremos lo mismo con los dos botones que también hemos añadido, pero con una diferencia, estos no serán propiedades sino IBActions.
Sin salir del Story Board vamos a unir el paso de una vista con la otra, marcaremos el botón "+" del Tableview que implementa la clase S3ViewController y pulsado la tecla CTRL lo arrastremos hasta el Tableview de la clase BuckectsViewController. Al soltar nos aparecerá un popup en el que podremos elegir el tipo de Segue (Push, Modal o Custom). Elegiremos Modal, será la misma elección en el resto de los segues del ejemplo.
En el Attributes Inspector, seleccionando el segue, cambiaremos el valor de la propiedad identifier por "AddBucket". Debería quedar más o menos como en el imagen:
Para hacer que la navegación entre vistas sea posible vamos a crear un nuevo delegare-protocol que usaremos para comunicar las vistas cuando los botones sean pulsados. El fichero BucketViewController.h debería quedar de la siguiente manera:
@class BuckectViewController;
@protocol BuckectViewControllerDelegate <NSObject>
- (void)buckectViewControllerDidCancel:(BuckectViewController*) controller;
- (void)buckectViewController:(BuckectViewController*) controller bucketName:(NSString*)theBucketName;
@end
@interface BuckectViewController : UITableViewController
@property (nonatomic, weak) id <BuckectViewControllerDelegate> delegate;
- (IBAction)cancel:(id)sender;
- (IBAction)done:(id)sender;
@property (strong, nonatomic) IBOutlet UITextField *nameBucketTextField;
@end
Nos toca implementar esta clase. Añadiremos los siguientes cambios en el .m:
@synthesize nameBucketTextField;
@synthesize delegate;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 1) {
[self.nameBucketTextField becomeFirstResponder];
}
}
-(IBAction)cancel:(id)sender{
[self.delegate buckectViewControllerDidCancel:self];
}
- (IBAction)done:(id)sender{
[self.delegate buckectViewController:self bucketName:self.nameBucketTextField.text];
}
Y ahora prepararemos el "segue" para que reaccione a las acciones de los botones. En el S3ViewController.h vamos a añadir el protocolo que acabamos de crear:
#import "BuckectViewController.h"
@interface S3ViewController : UITableViewController <BuckectViewControllerDelegate>
y en la implementación de S3ViewController:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([segue.identifierisEqualToString:@"AddBucket"]) {
UINavigationController *navigationController = segue.destinationViewController;
BuckectViewController *bucketView = [[navigationController viewControllers]objectAtIndex:0];
bucketView.delegate = self;
}
Un detalle interesante es la manere de saber cual es el segue que ha disparado el metodo prepareForSegue, el parámetro "segue" tiene el identificador del mismo....ese que en el Storyboard habíamos puesto anteriormente -> "AddBucket".
#pragma mark - buckectViewControllerDelegate
- (void)buckectViewControllerDidCancel:(BuckectViewController*) controller{
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)buckectViewController:(BuckectViewController*) controller bucketName:(NSString *)theBucketName{
[self dismissViewControllerAnimated:YES completion:nil];
}
Ahora podríamos ejecutar nuestra app y al menos la navegación entre vistas funcionaria. Vamos a insertar un poco más de código para llamar al sdk de Amazon y repintar la lista con todos los buckets, incluyendo el último creado. En la implementación del S3ViewController:
#pragma mark - Amazon S3 Methods
- (void)createMyBucket:(NSString*)theBucketName{
AmazonS3Client *s3Client = [[AmazonS3Client alloc] initWithAccessKey:kACCESS_KEY
withSecretKey:kSECRET_KEY];
@try {
[s3Client createBucket:[[S3CreateBucketRequestalloc]initWithName:theBucketName andRegion:[S3RegionEUIreland]]];
}
@catch (AmazonClientException *exception) {
NSLog(@"%@",exception);
}
[self getMyBuckets];
[self.tableView reloadData];
}
Un detalle interesante: cuando creamos un bucket con CreateBucket:initWithName:andRegion: podemos elegir el data center en el que será creado. En este caso he elegido el de Irlanda, tenemos disponibles dos en EEUU y otros 2 en Asia.
Con el método getMyBuckets obtenemos la lista de buckets y la cargamos en la propiedad arrayBuckets, para recargar el tableview al final.
- (void)getMyBuckets{
AmazonS3Client *s3Client = [[AmazonS3Client alloc] initWithAccessKey:kACCESS_KEY
withSecretKey:kSECRET_KEY];
@try {
NSArray *listaBuckets = (NSMutableArray *)[s3Client listBuckets];
if (_arrayBuckets == nil) {
self.arrayBuckets = [[NSMutableArrayalloc]initWithCapacity:[listaBuckets count]];
} else {
[self.arrayBucketsremoveAllObjects];
}
for (S3Bucket *buckect in listaBuckets) {
[self.arrayBucketsaddObject:buckect];
}
}
@catch (AmazonClientException *exception) {
NSLog(@"%@",exception);
}
Y para terminar, añadiríamos el mensaje "[self createMyBucket:theBucketName]":
- (void)buckectViewController:(BuckectViewController*) controller bucketName:(NSString *)theBucketName{
[self createMyBucket:theBucketName];
[self dismissViewControllerAnimated:YES completion:nil];
}
En el siguiente y último post de esta mini-serie, mostraré como obtener los objetos de un bucket y como agregar nuevos.














