[iOS 02] Programación para iOS, el UIKit Framework y Xcode.

0. Lectura Obligada!

1. Creando un proyecto en XCode: Calculadora RPM

2. La capa de modelo y sus Unit Tests

CalculadoraTests.h

1
2
3
4
5
6
7
8
9
#import <SenTestingKit/SenTestingKit.h>
#import "CalcModel.h"

@interface CalculadoraTests : SenTestCase {
@private
    CalcModel *_calc;
}

@end

CalculadoraTests.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#import "CalculadoraTests.h"
#import <SenTestingKit/SenTestCase.h>

@implementation CalculadoraTests

- (void)setUp{
    [super setUp];
    _calc = [[CalcModel alloc] init];
    [_calc retain];
}

- (void)tearDown{
    [_calc release];
    [super tearDown];
}

- (void)testSumas{
    [_calc reset];

    [_calc enter:3.0];
    STAssertEquals([_calc add], 3.0, @"El total debería ser 3.0");

    [_calc enter:2.0];
    [_calc enter:4.0];
    STAssertEquals([_calc add], 6.0, @"El total debería ser 6.0");
    STAssertEquals([_calc add], 9.0, @"El total debería ser 9.0");    
}

- (void)testRestas{
    [_calc reset];

    [_calc enter:3.0];
    STAssertEquals([_calc sub], -3.0, @"El total debería ser -3.0");

    [_calc enter:2.0];
    [_calc enter:4.0];
    STAssertEquals([_calc sub], -2.0, @"El total debería ser -2.0");
    STAssertEquals([_calc sub], -1.0, @"El total debería ser -1.0");    
}

@end

CalcModel.h

1
2
3
4
5
6
7
8
9
10
11
12
#import <Foundation/Foundation.h>

@interface CalcModel : NSObject {
    NSMutableArray* _stack;
}

-(void)reset;
-(void)enter:(double)val;
-(double)add;
-(double)sub;

@end

CalcModel.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#import "CalcModel.h"
#import "NSMutableArray+Stack.h"

@implementation CalcModel

- (id)init {
    self = [super init];
    if (self) {
        _stack = [[NSMutableArray alloc] init];
    }
    return self;
}

- (void)dealloc {
    [_stack release];
    [super dealloc];
}

-(void)reset{
    [_stack removeAllObjects];
}

-(void)enter:(double)val{
    NSNumber* num = [NSNumber numberWithDouble:val];
    [_stack addObject:num];
}

-(double)add{
    double val1 = [(NSNumber*)[_stack pop] doubleValue];
    double val2 = [(NSNumber*)[_stack pop] doubleValue];
    double result = val1 + val2;
    [_stack push:[NSNumber numberWithDouble:result]];

    return result;
}

-(double)sub{
    double val1 = [(NSNumber*)[_stack pop] doubleValue];
    double val2 = [(NSNumber*)[_stack pop] doubleValue];
    double result = val2 - val1;
    [_stack push:[NSNumber numberWithDouble:result]];

    return result;
}

@end

3. AppDelegate, el punto de entrada de la aplicación.

CalculadoraAppDelegate.h

1
2
3
4
5
6
7
8
9
@interface CalculadoraAppDelegate : NSObject <UIApplicationDelegate> {
    CalcController *_calcController;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) CalcController *calcController;


@end

CalculadoraAppDelegate.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#import "CalculadoraAppDelegate.h"

@implementation CalculadoraAppDelegate

@synthesize window=_window;
@synthesize calcController=_calcController;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.calcController = [[CalcController alloc] initWithNibName:@"CalcView" bundle:nil];
    [self.window addSubview:_calcController.view];

    [self.window makeKeyAndVisible];
    return YES;
}

- (void)dealloc
{
    [_calcController release];
    [_window release];
    [super dealloc];
}

@end

4. La capa de controlador y la capa de vista.

CalcController.h

1
2
3
4
5
6
7
8
9
10
11
#import <UIKit/UIKit.h>
#import "CalcModel.h"

@interface CalcController : UIViewController {
    UILabel *_pantalla;
    CalcModel *_calcModel;
}

@property (nonatomic, retain) IBOutlet UILabel *pantalla;

@end

CalcController.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#import "CalcController.h"

@implementation CalcController

@synthesize pantalla=_pantalla;

#pragma mark - Controller lifecycle

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        _calcModel = [[CalcModel alloc] init];
    }
    return self;
}

- (void)dealloc{
    [_calcModel release];
    [_pantalla release];
    [super dealloc];
}

#pragma mark - Plomería

-(void)numero:(int)num{
    if([[_pantalla text] isEqualToString:@"0.0"]){
        [_pantalla setText:[NSString stringWithFormat:@"%i",num]];
    }
    else{
        [_pantalla setText:[NSString stringWithFormat:@"%@%i",[_pantalla text],num]];
    }
}

-(void)mas{
    double val = [_calcModel add];
    [_pantalla setText:[NSString stringWithFormat:@"%f",val]];
}

-(void)menos{
    double val = [_calcModel sub];
    [_pantalla setText:[NSString stringWithFormat:@"%f",val]];
}

-(void)enter{
    double val = [[_pantalla text] doubleValue];
    [_calcModel enter:val];
    [_pantalla setText:@"0"];
}

-(void)cero{
    [_pantalla setText:@"0.0"];
    [_calcModel reset];
}

-(IBAction)tappedButton:(id)sender{
    int tag = [(UIButton*)sender tag];
    if(tag >=0 && tag <=9){
        [self numero:tag];
    }
    else if(tag==101){ // +
        [self mas];
    }
    else if(tag==102){ // -
        [self menos];
    }
    else if(tag==103){ // enter
        [self enter];
    }
    else if(tag==104){ // C
        [self cero];
    }
}

@end

5. Una aplicación con múltiples Views, UITabBarController y UINavigationController

ColeccionDeCosasAppDelegate.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#import <UIKit/UIKit.h>

@interface ColeccionDeCosasAppDelegate : NSObject <UIApplicationDelegate> {
    UITabBarController* _tabBarController;

    UINavigationController* _acercaDeNavController;
    UINavigationController* _cosasNavController;
    UINavigationController* _favoritosNavController;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) UITabBarController* tabBarController;
@property (nonatomic, retain) UINavigationController* acercaDeNavController;
@property (nonatomic, retain) UINavigationController* cosasNavController;
@property (nonatomic, retain) UINavigationController* favoritosNavController;

@end

ColeccionDeCosas.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#import "ColeccionDeCosasAppDelegate.h"
#import "CosasController.h"
#import "FavoritosController.h"
#import "AcercaDeController.h"

@implementation ColeccionDeCosasAppDelegate

@synthesize window=_window;
@synthesize tabBarController=_tabBarController;
@synthesize acercaDeNavController=_acercaDeNavController;
@synthesize cosasNavController=_cosasNavController;
@synthesize favoritosNavController=_favoritosNavController;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
    // Crea instancia de cosas controller, lo pone en un navigation controller:
    CosasController* cosasController = [[CosasController alloc] initWithStyle:UITableViewStylePlain];
    self.cosasNavController = [[UINavigationController alloc] initWithRootViewController:cosasController];
    self.cosasNavController.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"Cosas" image:[UIImage imageNamed:@"campaings-icon.png"] tag:0];
    [cosasController release];

    // Crea instancia de favoritos controller, lo pone en un navigation controller:
    FavoritosController* favoritosController = [[FavoritosController alloc] initWithNibName:@"FavoritosView" bundle:nil];
    self.favoritosNavController = [[UINavigationController alloc] initWithRootViewController:favoritosController];
    self.favoritosNavController.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"Favoritas" image:[UIImage imageNamed:@"donate-icon.png"] tag:0];
    [favoritosController release];

    // Crea instancia de favoritos controller, lo pone en un navigation controller:
    AcercaDeController* acercaDeController = [[AcercaDeController alloc] initWithNibName:@"AcercaDeView" bundle:nil];
    self.acercaDeNavController = [[UINavigationController alloc] initWithRootViewController:acercaDeController];
    self.acercaDeNavController.tabBarItem = [[UITabBarItem alloc] initWithTitle:@"Acerca De" image:[UIImage imageNamed:@"alerts-icon.png"] tag:0];
    [acercaDeController release];

    // Crea el tab bar y agrega los navControllers a él:
    self.tabBarController = [[UITabBarController alloc] initWithNibName:nil bundle:nil];
    self.tabBarController.viewControllers = [NSArray arrayWithObjects:self.cosasNavController, self.favoritosNavController, self.acercaDeNavController, nil];

    //Agrega el view de tabBarController a la ventana y la muestra
    [self.window addSubview:self.tabBarController.view];
    [self.window makeKeyAndVisible];
    return YES;
}

- (void)dealloc{
    [_window release];
    [_tabBarController release];
    [_acercaDeNavController release];
    [_cosasNavController release];
    [_favoritosNavController release];
    [super dealloc];
}
@end

CosasController.h

1
2
3
4
5
6
7
8
9
#import <UIKit/UIKit.h>


@interface CosasController : UITableViewController {
    NSArray* _listaDeCosas;
}

@property(nonatomic,retain) NSArray* listaDeCosas;
@end

CosasController.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#import "CosasController.h"
#import "CosaController.h"

@implementation CosasController

@synthesize listaDeCosas=_listaDeCosas;

- (id)initWithStyle:(UITableViewStyle)style
{
    self = [super initWithStyle:style];
    if (self) {
        self.title = @"Cosas";

        NSString* path = [[NSBundle mainBundle] pathForResource:@"CosasDB" ofType:@"plist"];
        NSDictionary* dict = [NSDictionary dictionaryWithContentsOfFile:path];
        self.listaDeCosas = [dict objectForKey:@"cosas"];
    }
    return self;
}

- (void)dealloc
{
    [self.listaDeCosas release];
    [super dealloc];
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

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

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

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    }

    cell.textLabel.text = [[self.listaDeCosas objectAtIndex:indexPath.row] objectForKey:@"nombre"];
    return cell;
}

#pragma mark - Table view delegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    NSDictionary* dict = [self.listaDeCosas objectAtIndex:indexPath.row];
    CosaController *cc = [[CosaController alloc] initWithNibName:@"CosaView" bundle:nil cosaDict:dict];
    [self.navigationController pushViewController:cc animated:YES];
    [cc release];
}

@end

CosasDB.plist:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>cosas</key>
  <array>
    <dict>
      <key>descripcion</key>
      <string>Las Psitácidas (Psittacidae) son una familia de aves psitaciformes llamadas comúnmente loros o papagayos, e incluye a los guacamayos, las cotorras, los periquitos, los agapornis y formas afines. Las cacatúas, caracterizadas por presentar una cresta de plumas eréctiles en la cabeza, pertenecen a otra familia (Cacatuidae), dentro del mismo orden (Psittaciformes).</string>
      <key>nombre</key>
      <string>Loro</string>
    </dict>

        <dict>
      <key>descripcion</key>
      <string>Macaronesia es el nombre colectivo de varios archipiélagos del Atlántico Norte, más o menos cercanos al continente africano. El término procede del griego μακάρων νη̂σοι, makárôn nêsoi, 'islas alegres o afortunadas', en alusión a las islas de la mitología griega que eran morada de los héroes difuntos y se suponían situadas en los confines de Occidente. Comprende cinco archipiélagos: Azores, Canarias, Cabo Verde, Madeira e Islas Salvajes.</string>
      <key>nombre</key>
      <string>Macaronesia</string>
    </dict>

        <dict>
      <key>descripcion</key>
      <string>La laurisilva (latín: laurus+silva, 'bosque de laurel' )? es un tipo de bosque nuboso subtropical o selva alta, propio de lugares húmedos, cálidos, y con leves heladas o sin ellas, con grandes árboles, bejucos y lianas cuyas hojas se parecen a las del laurel, de lo cual toma el nombre. La laurisilva se da en regiones de clima templado húmedo y cálido.</string>
      <key>nombre</key>
      <string>Laurisilva</string>
    </dict>

        <dict>
      <key>descripcion</key>
      <string>El Dirigible LZ 126, más tarde renombrado ZR-3 USS Los Angeles cuando estuvo al servicio de la Armada de los Estados Unidos, fue una aeronave construida entre 1923 y 1924 en Friedrichshafen. Hacia el final de la Primera Guerra Mundial limitaron a los alemanes la construcción de pequeñas aeronaves y les prohibieron por completo la producción de aeronaves militares. Solo los Estados Unidos, que no habían ratificado el Tratado de Versalles, siguieron trabajando en la zona con los alemanes. En los Estados Unidos se consideraba que el porvenir del ejército como la marina pasaría por el empleo de dirigibles en misiones de reconocimiento. El presidente de la compañía Luftschiffbau Zeppelin, Hugo Eckener, recibió el encargo del gobierno estadounidense de construir un gran dirigible. Antes había sido Inglaterra la que recibió el encargo de otro dirigible, el R38 (destinado a ser el ZR-2), pero en agosto de 1921 sufrió un accidente en un vuelo de reconocimiento poco antes de su entrega. La fabricación del dirigible se enmarcaba en las reparaciones de la Primera Guerra Mundial, que Alemania debía pagar. Los Estados Unidos pidieron en principio 3.2 millones de marcos, a pagar en forma de dos dirigibles para la marina. El encargo del LZ 126 fue asignado a la Luftschiffsbau Zeppelin GmbH. Zeppelin tenía, entre otros requisitos, que entregar la aeronave con una tripulación alemana en América.
            </string>
      <key>nombre</key>
      <string>USS Los Angeles (ZR-3)</string>
    </dict>
  </array>
</dict>
</plist>

CosaController.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import <UIKit/UIKit.h>


@interface CosaController : UIViewController {
    NSDictionary* _cosaDict;
    UILabel* _titulo;
    UILabel* _descripcion;
}

@property(nonatomic,retain) NSDictionary* cosaDict;
@property(nonatomic,retain) IBOutlet UILabel* titulo;
@property(nonatomic,retain) IBOutlet UILabel* descripcion;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil cosaDict:(NSDictionary*)aCosaDict;

@end

CosaController.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#import "CosaController.h"

@implementation CosaController

@synthesize cosaDict=_cosaDict;
@synthesize titulo=_titulo;
@synthesize descripcion=_descripcion;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil cosaDict:(NSDictionary*)aCosaDict{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        self.cosaDict = aCosaDict;
        self.title = [self.cosaDict objectForKey:@"nombre"];
    }
    return self;
}

- (void)dealloc{
    [self.cosaDict release];
    [self.titulo release];
    [self.descripcion release];
    [super dealloc];
}

- (void)didReceiveMemoryWarning{
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];

    // Release any cached data, images, etc that aren't in use.
}

#pragma mark - View lifecycle

- (void)viewDidLoad{
    [super viewDidLoad];

    self.titulo.text = [self.cosaDict objectForKey:@"nombre"];
    self.descripcion.text = [self.cosaDict objectForKey:@"descripcion"];
}

- (void)viewDidUnload{
    [super viewDidUnload];
    self.titulo=nil;
    self.descripcion=nil;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

@end

AcercaDeController.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#import "AcercaDeController.h"


@implementation AcercaDeController

@synthesize webView=_webView;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        self.title = @"Acerca De";
    }
    return self;
}

- (void)dealloc
{
    [_webView release];
    [super dealloc];
}

- (void)didReceiveMemoryWarning
{
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];

    // Release any cached data, images, etc that aren't in use.
}

#pragma mark - View lifecycle

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSString* filePath = [[NSBundle mainBundle] pathForResource:@"acerca" ofType:@"html"];
    NSString* html = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
    NSURL* url = [NSURL fileURLWithPath:filePath];
    [self.webView loadHTMLString:html baseURL:url];

}

- (void)viewDidUnload
{
    [super viewDidUnload];
    self.webView = nil;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

@end

6. Ejercicios

Extienda la aplicación Colección de Cosas con las siguientes características:

  • Las Cosas se deben poder marcar/desmarcar como favoritas.
  • El controlador FavoritosController debe mostrar una tabla con las
    Cosas que se han marcado como favoritas.
  • Cuando se toque una Cosa en la lista de favoritas, se debe mostrar
    CosaView con el título y la descripción adecuadas.
  • Cree un nuevo controlador y view NuevaCosa que permita crear Cosas
    nuevas. Las nuevas Cosas creadas deben persistir por medio de un
    archivo plist en el sistema de archivos del dispositivo.

Pistas:

  • iOS solo permite almacenar archivos de aplicación en el directorio
    /Documents.

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];

  • Un NSDictionary se puede almacenar como archivo fácilmente:

    [NSDictionary writeToFile:(NSString *)path atomically:(BOOL)flag];

ColecciónDeCosas.zip

2 thoughts on “[iOS 02] Programación para iOS, el UIKit Framework y Xcode.

  1. I read a lot of interesting articles here. Probably
    you spend a lot of time writing, i know how to save you a lot of time,
    there is an online tool that creates unique, SEO friendly articles
    in seconds, just search in google - laranitas free content source

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *

*

Puedes usar las siguientes etiquetas y atributos HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>