Proyecto Padawans Curso Fundamentos iOS

Descripción del Proyecto

Se trata de un proyecto muy sencillo, de estructura similar al de Star Wars que se realiza durante el Curso Fundamentos iOS, que permite repasar y afianzar los conceptos explicados.

Es para iPad y tendrá un UISplitViewController. Dentro de éste tendremos dos UINavigationControllers que alojan a dos controladores que crearemos nosotros (uno de ellos un UITableViewController).

Muestra una lista en orden alfabético de palabras inglesas y cuando el usuario toca sobre una de ellas, muestra su definición, obteniendo ésta del diccionario Webster Online.

Vista vertical de la app, con el Table View dentro de un popup.

 

 

Componentes

Los componentes serán los siguientes:

Un UISplitViewController
Dos UINavigationControllers
Un WordsTableViewController (la tabla) y su correspondiente vista y modelo
Un DefinitionViewController (la definición de la palabra) con su vista y modelo correspondientes.

Tendremos por lo tanto que crear 2 MVCs:

  1. El WordsTableViewController, su modelo y su vista
  2. El DefinitionViewController, su modelo  y su vista

Primer MVC: WordsTableViewController, WordsModel y la vista

El objetivo de este MVC es presentar en pantalla la lista de palabras, en orden alfabético y con cada letra representada por una sección (ver imagen).

Para ello crearemos dos clases nuevas:

  1. WordsModel (el modelo)
  2. WordsTableViewController (el controlador)

Empecemos por el modelo.

WordsModel (el modelo)

Tiene que representar un conjunto de listas de palabras, cada conjunto identificado por una letra. Es decir, la lista de palabras que empiezan por la, la lista de palabras que empiecen por la b, etc… Cada una de estas listas está identificada por la letra por la cual empiezan las palabras.

Aunque podríamos tener varios NSArrays de palabras (NSStrings), uno para cada letra, existe una solución mejor: NSDictionary.  NSDictionary relaciona objetos con una clave (una cadena) y podemos fácilmente al objeto en cuestión facilitándole la clave.

Uso de NSDictionary

Supongamos que queremos crear una agenda sencilla de personas y sus nicks de twitter:

// Creamos una agenda de twitters:
// Es decir, los nicks de twitter están indexados (identificados) por el nombre
// de la persona
NSDictionary *agenda = [NSDictionary dictionaryWithObjectsAndKeys:
@"Fernando", @"frr149",
@"Malena Costa", @"@Malenacosta7",
@"Lady Gaga", @"@ladygaga",
@"Belén Esteban", @"@BelenEstebanM",
@"Osama Bin Laden", @"@Real_Bin_Laden",
@"Santiago Segura", @"@SSantiagosegura",
@"Erika Sawajiri", @"@ERlKASAWAJIRI", nil];

// Obtener un nick de twitter
NSString *tuit = [agenda objectForKey:@"Fernando"];
NSLog(@"%@", tuit); // imprimirá @frr149

// Obtener la lista de todas las claves
NSArray *claves = [agenda allKeys];
NSLog(@"Todas las claves: %@", claves);

// Obtener la lista de todos los objetos
NSArray *objetos = [agenda allValues];
NSLog(@"Todos los objetos: %@", objetos);

¡Ojo, los arrays de claves y objetos no están ordenados!

NSDictionary no preserva el orden en que metes los objetos, si lo quieres en orden alfabético, tendrás que ordenar el array.

Definición de WordsModel

@interface WordsModel : NSObject

@property (strong) NSDictionary *words;

-(NSArray *) letters;
-(NSString *) letterAtIndex: (NSInteger) aLetterIndex;
-(NSString *) wordAtIndex:(NSInteger) aWordIndex inLetterAtIndex:(NSInteger) aLetterIndex;
-(NSArray *) wordsAtIndex:(NSInteger) anIndex;
@end
letters

Devuelve un NSArray ordenado de letras, Es decir, devuelve el alfabeto, de la a a la z.

letterAtIndex:

Devuelve la letra del alfabeto que está en la posición pedida. Es decir, si mandamos el mensaje letterAtIndex:0, nos devolverá una NSString con la letra a: @”a”.

wordsAtIndex:

Devuelve un NSArray ordenado de palabras que empiezan por aquella letra que está en la posición pedida. Por ejemplo, si mandamos el mensaje wordsAtIndex:0, nos devolverá un array ordenado con todas aquellas palabras que empiezan por la a.

wordAtIndex:(NSInteger) aWordIndex inLetterAtIndex:(NSInteger) aLetterIndex:

Devuelve la palabra que está en la posición aWordIndex dentro de la lista de palabras que empiezan por la letra que está en la posición aLetterIndex. Por ejemplo, si mandas el mensaje wordAtIndex:0 inLetterAtIndex:1 obtendrás la primera palabra que empieza por la b.

Inicialización del modelo

Para evitar tener que escribir todas las palabras, usaremos un método de NSDictionary que permite cargarlo de un fichero, siempre y cuando está en el formato de “property list”. Para ello, lo primero que tenéis que hacer es importar el fichero Vocabwords a vuestro proyecto.

Una vez hecho, inicializaríais el NSDictionary de la siguiente forma:

NSURL *urlToFile = [[NSBundle mainBundle] URLForResource:@"vocabwords"
withExtension:@"txt"];

NSDictionary *words = [NSDictionary dictionaryWithContentsOfURL:urlToFile];

Como el modelo implica usar una clase nueva y un concepto nuevo (ordenar arrays de palabras), os pongo también su implementación:

@implementation WordsModel

#pragma mark - Properties
@synthesize words;

#pragma mark - Init
-(id) init{

if (self = [super init]) {

NSURL *urlToFile = [[NSBundle mainBundle] URLForResource:@"vocabwords"
withExtension:@"txt"];

self.words = [NSDictionary dictionaryWithContentsOfURL:urlToFile];

}
return self;
}

#pragma mark -
-(NSArray *) letters{

return [[self.words allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
}

-(NSString *) letterAtIndex: (NSInteger) aLetterIndex{
return [[self letters] objectAtIndex:aLetterIndex];
}

-(NSString *) wordAtIndex:(NSInteger) aWordIndex
inLetterAtIndex:(NSInteger) aLetterIndex{

NSString *letter = [self letterAtIndex:aLetterIndex];
NSArray *wordsThatStartWithLetter = [self.words objectForKey:letter];
return [wordsThatStartWithLetter objectAtIndex:aWordIndex];
}

-(NSArray *) wordsAtIndex:(NSInteger)anIndex{
NSString *letter = [[self letters] objectAtIndex:anIndex];
return [[self.words objectForKey:letter] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
}
@end

Definición de WordsTableView

WordsTableView tiene 2 funciones:

  1. Mostrar el modelo en una tabla
  2. Avisar a DefinitionViewController que el usuario ha tocado sobre una palabra para que éste muestre su definición.

Nos centraremos de momento en la primera tarea. Para ello WordsTableView necesitará una propiedad para almacenar el modelo y un inicializador propio en el cual le pasaremos el modelo.

@property (strong) WordsModel* wordsModel;
-(id) initWithModel: (WordsModel *) aModel;

Para poder avisar a DefinitionViewController que el usuario ha tocado una palabra, WordsTableViewController tendrá que definir un protocolo de delegado, tener una propiedad que apunte a su delegado, y DefinitionViewController tendrá que ser su delegado.

La definición de WordsTableViewController quedaría de la siguiente forma:

@class WordsTableViewController;

// protocolo de delegado que ha de implementar DefinitonViewController
@protocol WordsTableViewControllerDelegate
-(void) wordsTableViewController: (WordsTableViewController *) sender
didClickOnWord: (NSString *) aWord;
@end

@interface WordsTableViewController : UITableViewController

@property (strong) WordsModel* wordsModel;
// Las propiedades de delegate son siempre weak
@property (weak) id delegate;

-(id) initWithModel: (WordsModel *) aModel;

@end

Segundo MVC: DefinitionViewController

Su objetivo es el mostrar la definición de una palabra (el modelo) en pantalla, obteniendo dicha definición del Webster online.

Modelo: Es una NSString con la palabra.

Vista: La creamos con IB y consiste simplemente en una UIWebView que muestra la página del Webster con la definición, y sobre ella unaUIActivityView que indica que se está bajando la página de la web hasta que se termina de cargar. En ese momento, la UIActivityView desaparece.

Controlador: DefinitionViewController. Se encarga de crear la NSURLRequest correcta para la palabra y se la pasa a la UIWebView.

Requisitos de DefinitionViewController

Necesita dos properties de tipo IBOutlet para referirse a la UIWebView y a la UIActivityView. También necesita una property NSString para guardar el modelo y un inicializador que acepte dicho modelo:

@interface DefinitionViewController : UIViewController
@property (strong) IBOutlet UIWebView *browser;
@property (strong) IBOutlet UIActivityIndicatorView *activityView;
@property (copy) NSString *wordModel;

-(id) initWithModel:(NSString *) aModel;

@end

También necesita un método que a partir de la palabra, obtenga la NSURLRequest de la definición el Webster’s:

-(NSURLRequest *) definitionRequestForWord: (NSString *) aWord{
NSURL *url = [NSURL URLWithString:
[NSString stringWithFormat:@"http://www.merriam-webster.com/dictionary/%@", aWord]];

NSURLRequest *request = [NSURLRequest requestWithURL:url];
return request;
}
UIActivityView

La UIActivityView la creamos con el Interface Builder en el xib y la situamos en el centro de la vista superior (una UIWebView que habremos creado previamente). Acordaos de asignar correctamente los “muelles” y las “anclas”, para que la UIActivityView esté siempre en el centro.

“Anclas” y “muelles” para indicar cómo se debe de comportar una vista al cambiarle el tamaño.

En el mismo Interface Builder, a la UIActivityView le asignamos las siguientes propiedades:

Animating
Hides when stopped

Propiedades de UIActivityView

Como la activity view se oculta cuando deja de animar, lo único que tendremos que hacer, cuando la página haya terminado de cargarse, es mandarle el mensaje stopAnimating.

Delegados

Para que la aplicación funcione, tienen que existir las siguientes relaciones de delegado. Recordad que el delegado es un ayudante que utiliza un objeto para llevar a cabo sus tareas.

En una cocina el pinche sería el delegado del chef.

UISplitView

Para que el UISPlitView funcione correctamente cuando se cambia la orientación del dispositivo, es fundamental que el controlador de la derecha, es decir, el DefinitionViewController sea su delegado y que implemente los siguientes mensajes:

#pragma mark - UISplitViewControllerDelegate
-(void) splitViewController:(UISplitViewController *)svc
willHideViewController:(UIViewController *)aViewController
withBarButtonItem:(UIBarButtonItem *)barButtonItem
forPopoverController:(UIPopoverController *)pc{

//¿Qué había que poner aquí?
}

-(void) splitViewController:(UISplitViewController *)svc
willShowViewController:(UIViewController *)aViewController
invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem{

// ¿Y aquí?

}

UIDefinitionViewController

Para que DefinitionViewController se pueda enterar de que la UIWebView ha terminado de cargar la página de definición, tiene que ser delegado de UIWebView e implementar el mensaje

-(void) webViewDidFinishLoad:(UIWebView *)webView

En ese momento es cuando debe de mandar el mensaje stopAnimating a la UIActivityView.

Además, DefinitionViewController también tiene que enterarse cuando el usuario toca una palabra en la tabla. Para ello tiene que ser delegado de WordsTableViewController.  Como esta clase la hemos hecho nosotros, también le tenemos que añadir el protocolo de delegado que DefinitionViewController ha de implementar.

Para ese protocolo basta un sólo método que informe de la palabra sobre la cual se ha tocado. Teneis que crear ese protocolo en el WordsTableViewController, antes del @interface. Si dentro del protocolo usais alguna referencia a la clase WordsTableViewController, recordad que teneis que poner antes un

@class WordsTableViewController;

Para que el compilador no os dé la murga.

En resumen, DefinitonViewController tiene que ser delegado de:

  1. UISplitViewController
  2. WordsTableViewController
  3. UIWebView

El esquema quedaría de ésta forma:

DefinitionViewController es el delegado de casi todo el mundo. Esto es común. Es como si fuese un becario que recibe marrones de los otros tres objetos.

Cómo abordar el ejercicio

Si os surgen dudas, repasad el proyecto Star Wars que ya tenéis. Si aún así no os aclaráis, podéis ver el proyecto terminado en los enlaces más abajo. Sin embargo, es fundamental que lo intentéis antes de ver la solución.

Ejercicio Resuelto

Lo tenéis en este Zip.

¿Dudas?

Si tenéis dudas o no os sale, dadnos un toque.

Comments are closed, but trackbacks and pingbacks are open.

Cookie use

KeepCoding uses cookies to ensure that we give you the best experience on our website. If you continue we assume that you consent to receive all cookies on all KeepCoding websites. More info here.

ACEPTAR
Aviso de cookies