may 23

Hoy veremos un ejemplo muy sencillo de cómo usar y para que sirve el protocolo NSKeyValueOnserving (KVO), al igual que en otros posts, lo que aquí se explica se puede usar en muchos otros casos.

Este protocolo nos sirve para detectar cambios en los valores de atributos de objetos y funciona de la siguiente manera:

  • Tenemos un objeto X del que queremos saber cuando un atributo se modifica
  • Tenemos un objeto Y que ha de realizar una acción cuando X modifica su atributo
  • El protocolo anterior nos permite que el objeto Y sea avisado de los cambios para el atributo que especifiquemos del objeto X.

Para conseguir esto sólo tenemos que indicarle a X que Y quiere ser avisado de dichos cambios usando el método addObserver:forKeyPath:options:context: definido en el protocolo y realizar las acciones oportunas usando el método observeValueForKeyPath:ofObject:change:context: en el objeto Y, ya que este es el mensaje que se le envía cuando dicho atributo cambia su valor.

Cómo ejemplo usaremos ese método para detectar cuando un usuario selecciona o deselecciona una anotación (MKAnnotation) en un mapa (MKMapView). Primero indicaré como montar el proyecto a modo de tutorial y más abajo como se usa exactamente el KVO, si ya teneis vuestro proyecto montado pasad directamente a la segunda sección.

Preparación

Primero creamos un nuevo proyecto usando la plantilla de Xcode basado en una vista. Añadimos al proyecto el Framework MapKit para poder usar el mapa y las anotaciones.
Luego modificamos el controlador de la vista para añadirle un IBoutlet que nos referencia al mapa que más adelante añadiremos. Tendremos que importar el Framework.

#import <UIKit/UIKit.h>;
#import <MapKit/MapKit.h>;
 
@interface MapKVOViewController : UIViewController  {
 
	IBOutlet MKMapView *theMapView;
}
 
@end

Abrimos con Interface Builder el archivo MapKVOViewController.xib y le añadimos un MKMapView que asignaremos al IBoutlet creado anteriormente, también indicaremos que el delegado del mapa será el propio controlador.
Para las anotaciones en el mapa necesitamos añadir otra clase a nuestro proyecto, con Xcode añadir una nueva clase a partir de un NSObject, importaremos aquí también el Framework MapKit. Esta clase ha de cumplir con el protocolo MKAnnotation así que modificamos el .h para que así sea.

#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
 
@interface MapAnnotation : NSObject {
	NSString *title;
	NSString *subtitle;
	CLLocationCoordinate2D coordinate;
}
 
@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
 
// Title and subtitle for use by selection UI.
- (NSString *)title;
- (NSString *)subtitle;
 
@end

Cómo esta clase no es más que para un test, inicializaremos las variables a valores constantes, modificar el .m para que se parezca a esto

@implementation MapAnnotation
 
@synthesize coordinate;
 
- (id)init {
	[super init];
 
	coordinate.latitude = 25;
	coordinate.longitude = 25;
	title = [NSString stringWithFormat:@"A Testing annotation"];
	[title retain];
	subtitle = [NSString stringWithFormat:@"selecting detection"];
	[subtitle retain];
	return self;
}
 
- (NSString *)title {
	return title;
}
 
- (NSString *)subtitle{
	return subtitle;
}
 
- (void)dealloc{
	[title release];
	[subtitle release];
	[super dealloc];
}
 
@end

Para acabar con la preparación del proyecto solo nos falta añadir una anotación en el mapa, por ejemplo usar el viewDidLoad para hacerlo. Acordaros de importar vuestra nueva clase para las anotaciones en el mapa (aquí MapAnnotation).

- (void)viewDidLoad {
    [super viewDidLoad];
	MapAnnotation *newAnnotation = [[MapAnnotation alloc] init];
	[theMapView addAnnotation:newAnnotation];
}

Uso de KVO

En nuestro ejemplo, el objeto X es la vista de la anotación, el pin que se ve en el mapa, es decir, tenemos por un lado la anotación en si y por otro una vista que la representa en el mapa, como la acción de seleccionar se realiza sobre el mapa, es la vista de la anotación, la que tiene un atributo llamado selected, que es el que querremos vigilar.

Por otro lado, el objeto Y, será nuestro MapKVOViewController, que es el encargado de añadir las anotaciones al mapa.

Primer paso, indicarle al objeto X que queremos observar el atributo selected, suponiendo que queramos hacer esto con todas las anotaciones del mapa, un buen sitio es el método mapView:didAddAnnotationViews: del protocolo MKMapViewDelegate, añadir este método a nuestro controlador y modificarlo para que sea como este

- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views {
	for (MKAnnotationView *anAnnotationView in views) {
		[anAnnotationView setCanShowCallout:YES];
		[anAnnotationView addObserver:self
						 forKeyPath:@"selected"
					              options:NSKeyValueObservingOptionNew
						      context:ANNOTATION_SELECTED_DESELECTED];
	}
}

Tened en cuenta que esto se hará para cada anotación, por lo que, si tenéis diferentes tipos de anotaciones, tendréis que diferenciar entre cuales queréis observar y cuales no.

El método que nos interesa es addObserver:forKeyPath:options:context:, a éste le indicamos quien será el observador, self, que atributo queremos observar, selected, con que opciones, NSKeyValueObservingOptionNew indica que queremos el nuevo valor, y un contexto, que se usa para darnos mas información, por ejemplo qué estamos observando, aquí podéis usar nil si sólo observáis una cosa, en caso de que tengáis que diferenciar, es muy útil usar constantes para cada caso, aquí usamos

static NSString* const ANNOTATION_SELECTED_DESELECTED = @"mapAnnotationSelectedOrDeselected";

definida al principio de nuestro .m

Finalmente, para poder realizar cualquier acción, una vez notificados del cambio, tenemos que sobrescribir el método observeValueForKeyPath:ofObject:change:context:

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context {
 
    NSString *action = (NSString *)context;
	if ([action isEqualToString:ANNOTATION_SELECTED_DESELECTED]) {
		BOOL annotationSelected = [[change valueForKey:@"new"] boolValue];
		if (annotationSelected) {
			NSLog(@"Annotation was selected, do whatever required");
                        // Accions when annotation selected
		}else {
			NSLog(@"Annotation was deselected, do what you must");
                        // Accions when annotation deselected
		}
	}
}

Añadir el código para realizar la acción que queráis una vez sepáis si se ha seleccionado o deseleccionado la anotación, por ejemplo reproducir un sonido.

VN:F [1.9.8_1114]
Rating: 5.0/5 (4 votes cast)
VN:F [1.9.8_1114]
Rating: +3 (from 3 votes)
Tagged with:
may 16

Hoy explicaré como conseguir reproducir un sonido cuando se pulsa un botón, de hecho, lo que explicaré se puede usar para muchas más cosas y no sólo para botones, ya que lo que haré será crear un SystemSoundID que se usan, entre otras cosas, para reproducir sonidos cortos (30 segundos o menos). Pero como ejemplo, usaré un botón.

Para poder reproducir sonidos necesitaremos añadir el Framework AudioToolbox a nuestro proyecto.

Imaginaros que contamos con un UIViewController y la vista que éste controla, añadamos un UIButton a la vista mediante Interface Builder o a base de código, como más os guste. Si aun no hemos añadido el Framework a nuestro proyecto hacedlo ahora, una vez añadido tendremos que importarlo a nuestro controlador.

En el archivo cabecera (.h) de nuestro controlador añadimos una variable de tipo SystemSoundID, y un método que será llamado cuando se pulse el botón.

#import <UIKit/UIKit.h>
#import <AudioToolbox/AudioToolbox.h>
 
@interface testKeyboardViewController : UIViewController <UITextFieldDelegate> {
 
	SystemSoundID buttonSoundID;
}
- (IBAction)buttonPressed;
 
@end

Ahora necesitamos un archivo de audio, con el sonido que queramos, por ejemplo ButtonSound.caf, y en el método viewDidLoad de nuestro controlador añadir las siguientes líneas para crear la referencia a nuestro sonido.

- (void)viewDidLoad {
    [super viewDidLoad];
	NSString *path = [[NSBundle mainBundle] pathForResource:@"ButtonSound" ofType:@"caf"];
	AudioServicesCreateSystemSoundID((CFURLRef)[NSURL fileURLWithPath:path], &buttonSoundID);	
}

Como veis en el código, necesitamos la ruta a nuestro archivo de audio, si habéis añadido el archivo a vuestro grupo Resources en Xcode, el código anterior debería funcionar. Echad un vistazo a NSBundle en caso de que no sea así para buscar otro método que os deje encontrar el archivo.

Con eso ya tenemos creada la referencia al sonido, ahora solo falta reproducir éste cuando sea necesario, en este ejemplo, queremos que se reproduzca cuando pulsamos el botón. Para ello tenemos que añadir una línea a la función que llamamos cuando se pulsa el botón.

- (IBAction)buttonPressed {	
	AudioServicesPlaySystemSound(buttonSoundID);
}

Con esto ya deberíais oír el sonido cuando pulsáis el botón.

En caso de no necesitar más el sonido en cuestión, podéis usar la siguiente función para eliminar la referencia creada, por ejemplo en el viewDidUnload de nuestro controlador.

- (void)viewDidUnload {
	AudioServicesDisposeSystemSoundID(buttonSoundID);
}

Espero que esto os sea útil para añdirle a vuestra aplicación todo tipo de sonidos cortos, para darle un poco más de vida.

VN:F [1.9.8_1114]
Rating: 5.0/5 (2 votes cast)
VN:F [1.9.8_1114]
Rating: +2 (from 2 votes)
Tagged with:
may 15

En muchas ocasiones nuestras aplicaciones están pesnados sólo para funcionar en el modo apaisanado (horziontal) del iPhone.

En muchos juegos es muy común que sólo funcionen en ese modo. Entonces que hemos de hacer para que nuestra aplicación sólo aparezca en ese modo?

Primero de todo tenemos que añadir el método shouldAutorotateToInterfaceOrientation en nuestro view controller. Generlmente el XCode lo añade automáticamente al crear el view controller, nosotros sólo tenemos que descomentarlo y añadir nuestro código.

La implementación del método debería quedar así:

- (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)o {
        return UIInterfaceOrientationIsLandscaper(o);
}

Y finalmente debemos editar nuestro fichero .plist. Abrir el fichero haciendo click con el botón dereche y eligiendo: Open As > Plain Text File. Y en el fichero abierto añadiremos estás líneas:

    <key>UIInterfaceOrientation</key>
    <string>UIInterfaceOrientationLandscapeRight</string>
</dict>
</plist>

Con esto forzamos que la apicación siempre se inicie en el modo apaisanado.

VN:F [1.9.8_1114]
Rating: 0.0/5 (0 votes cast)
VN:F [1.9.8_1114]
Rating: +1 (from 1 vote)
Tagged with:
may 14

En muchos foros y blog, se ve gente que pregunta si hay algún control en Xcode que nos permita dar puntuaciones del estilo de las cinco estrellitas que se ve por iTunes, iPhone, iPod y demás. En su día, yo mismo recurrí a Google para ver si encontraba uno, pero a parte de algunas explicaciones de como montarlo, algunas usando un slider del Xcode, con vistas o la clase UIControl, nadie daba nada.

Finalmente decidí montarme uno yo mismo, y hoy os lo traigo, modificado, para que podáis añadirlo fácilmente a vuestro proyecto y usarlo en cualquier vista dentro de vuestra aplicación. Al final del artículo, en el que explico como funciona y como usarlo, encontrareis el link para descargarlo.

Funcionamiento
Personalmente me decanté por usar una vista normal, UIView, para implementar mi “rating control”, el porqué es muy simple, aunque hay una clase especialmente pensada para controles, UIControl, no vamos a hacer grandes maravillas, lo único que nos interesa es que mientras el usuario desplaza el dedo por nuestro sistema de puntuación, se vayan iluminando las estrellas pertinentes.
Para ello uso un UIViewController, que se encarga de crear la vista, añadir el número de iconos necesarios, por norma 5 y seguir la interactuación del usuario para detectar cuando pulsa un icono en concreto, o desplaza el dedo de uno a otro, para mostrar el rating pertinente. De esta manera conseguimos una buena fluidez y el mismo efecto que cuando se usa el rating del iPod, iPhone o iTunes.

No entraré en detalla en el código en si, ya que los interesados disponen de los archivos, más abajo, para curiosear, pero intentaré dar los detalles suficientes para que sea fácil usar esta clase en vuestra aplicación.

Aquí tenéis el contenido de la cabecera de la clase RatingViewController:

//
//  RatingViewController.h
//
//  Created by www.zenbrains.com on 5/13/10.
//  Copyright 2010 Zenbrains. All rights reserved.
//
 
#import <UIKit/UIKit.h>
 
 
@interface RatingViewController : UIViewController {
 
	// Property to acces the rate given by the user
	NSInteger rate;
 
	// Arrays to store the names of the images for the icons, when active and unactive
	// initialize through the designates initializer
	NSArray *ratedIcons;
	NSArray *unratedIcons;
 
	// Instances to set up the aspect of the rating view
	// initialize through the designates initializer
	NSInteger numberOfRates;
	NSInteger ratingViewWidth;
	NSInteger ratingViewHeigth;
	NSInteger ratingIconWidth;
}
 
@property NSInteger rate;
 
// The designated initializer.
- (id)initWithRates:(NSInteger)numRates viewWidth:(NSInteger)viewWidth viewHeight:(NSInteger)viewHeight 
		  iconWidth:(NSInteger)iconWidth ratedIcons:(NSArray*)ratIcons unratedIcons:(NSArray*)unratIcons;
// To move the rating view where you want
- (void)locateRatingViewAtPoint:(CGPoint)point;
// To set the initial rate for the view in case you have a rate stored
- (void)setRate:(int)rate;
 
// You shouldn't need to call this one 
- (void)updateRatingViewWithLocation:(CGPoint)touchPosition;
 
@end

Sobre las variables, los comentarios explican para que se usa cada cosa, solo comentaros algo sobre el rate:

rate, a través de esta propiedad es como se accede al valor actual del rating, esta pensado para valores enteros de 1 a 5 (normalmente) dejando el 0 libre para cuando no tenemos aun una puntuación.

En cuanto a los métodos, también se explican por si solos, solo comentaros que con el initWithRates:viewWidth:viewHeight:iconWidth:ratedIcons:unratedIcons: es con el que se inicializan todas las variables anteriores a los valores que nos interesan, si usáis el típico initWithNibName:bundle: para inicializar esta clase, se usarán unos valores dados por defecto dentro del RatingViewController.m, pero la vista quedará situada en la esquina superior izquierda, luego podéis moverla con el método locateRatingViewAtPoint:.

Daros cuenta de que se ha sobrescrito el método setRate: para que no solo le de el valor a la variable rate sino para que actualice también, el aspecto de los iconos en la vista.

Como usarlo en vuestra aplicación

Bien, una vez ya sabemos cómo funciona, solo queda dar un ejemplo de como usarlo en vuestra aplicación. En el zip de más abajo se incluyen los archivos RatingViewController.h y .m, junto con un set de imágenes que son las que se usan por defecto al llamar el initWithNibName:bundle:. Incluir todos estos archivos a vuestro proyecto. Ahora, desde cualquier otra clase podéis incluir esta vista, por ejemplo desde el viewDidLoad de vuestra vista principal.

- (void)viewDidLoad {
    [super viewDidLoad];
 
	// 3 rates stars
	NSArray *anArray = [NSArray arrayWithObjects:@"starFL.png",@"starFC.png",@"starFR.png",nil];
	NSArray *otherArray = [NSArray arrayWithObjects:@"starEL.png",@"starEC.png",@"starER.png",nil];
	ratingViewController = [[RatingViewController alloc] initWithRates:3 viewWidth:90 viewHeight:30 iconWidth:30 ratedIcons:anArray unratedIcons:otherArray];
	[ratingViewController locateRatingViewAtPoint:CGPointMake(10, 50)];
	[ratingViewController setRate:1];
 
	// 5 rates stars
	anArray = [NSArray arrayWithObjects:@"starFL.png",@"starFC.png",@"starFC.png",@"starFC.png",@"starFR.png",nil];
	otherArray = [NSArray arrayWithObjects:@"starEL.png",@"starEC.png",@"starEC.png",@"starEC.png",@"starER.png",nil];
	RatingViewController *rateViewA= [[RatingViewController alloc] initWithRates:5 viewWidth:300 viewHeight:60 iconWidth:60 ratedIcons:anArray unratedIcons:otherArray];
	[rateViewA locateRatingViewAtPoint:CGPointMake(10, 110)];
	[rateViewA setRate:3];
 
	// 6 rates leafs
	anArray = [NSArray arrayWithObjects:@"leafR.png",@"leafB.png",@"leafY.png",@"leafYG.png",@"leafG.png",@"leafG.png",nil];
	otherArray = [NSArray arrayWithObjects:@"leafE.png",@"leafE.png",@"leafE.png",@"leafE.png",@"leafE.png",@"leafE.png",nil];
	RatingViewController *rateViewB = [[RatingViewController alloc] initWithRates:6 viewWidth:240 viewHeight:40	iconWidth:40 ratedIcons:anArray unratedIcons:otherArray];
	[rateViewB locateRatingViewAtPoint:CGPointMake(10, 210)];
	[rateViewB setRate:30];
 
	// 10 rates leafs
	anArray = [NSArray arrayWithObjects:@"leafR.png",@"leafB.png",@"leafY.png",@"leafYG.png",@"leafG.png",@"leafR.png",
			   @"leafB.png",@"leafY.png",@"leafYG.png",@"leafG.png",nil];
	otherArray = [NSArray arrayWithObjects:@"leafE.png",@"leafE.png",@"leafE.png",@"leafE.png",@"leafE.png",@"leafE.png",
				  @"leafE.png",@"leafE.png",@"leafE.png",@"leafE.png",@"leafE.png",@"leafE.png",nil];
	RatingViewController *rateViewC = [[RatingViewController alloc] initWithRates:10 viewWidth:300 viewHeight:30 iconWidth:30 ratedIcons:anArray unratedIcons:otherArray];
	[rateViewC locateRatingViewAtPoint:CGPointMake(10, 310)];
	[rateViewC setRate:-5];
 
	[[self view] addSubview:[ratingViewController view]];
	[[self view] addSubview:[rateViewA view]];
	[[self view] addSubview:[rateViewB view]];
	[[self view] addSubview:[rateViewC view]];
 
}

En este ejemplo estoy añadiendo cuatro “rating views” a mi vista, normalmente solo usareis uno, para mostrar lo que se puede hacer con esta clase, crear vuestros propios iconos, calcular como ha de ser de grande la vista (podéis usar Interface Builder para encontrar los pixeles) y solo tenéis que crear un objeto de esta clase, inicializarlo y ponerlo donde queráis. Con el código anterior obtenemos lo siguiente

Bueno, espero que os sea de utilidad y podáis usar esta vista en vuestras aplicaciones, sentiros libres de usar el código con o sin modificaciones, pero si tenéis un hueco en el código o los créditos de vuestra aplicación, siempre podéis mencionar nuestro nombre como agradecimiento.

Aquí os dejo los archivos. RatingViewController

VN:F [1.9.8_1114]
Rating: 3.9/5 (8 votes cast)
VN:F [1.9.8_1114]
Rating: +1 (from 1 vote)
Tagged with:
may 09

En este artículo vamos a ver un simple ejemplo de como acceder a una base de datos sqlite desde nuestro código.

Las bases de datos sqlite se usan mucho en las aplicaciones para guardar datos de forma organizada localmente.

Al final del artículo vamos a tener dos funciones creados que podremos reutilizar en nuestros proyectos (el link de descarga abajo de todo).
Entonces, vamos a ver los pasos a seguir para acceder a nuestra base de datos.

Antes de empezar con el código tenemos que añadir la librería de sqlite a nuestro proyecto. Para esto hacemos click con el botón derecho en la carpeta/grupo de frameworks de nuestro proyecto y seleccionamos Add > Existing Frameworks…

Esto nos abrirá una ventana de selección donde tenemos que buscar y seleccionar el framework/librería de sqlite.  Buscamos el libsqlite3.dylib y lo añadimos al nuestro proyecto.

En el fichero donde vayamos a usar las funciones de sqlite para consultar nuestra base de datos tenemos que incluir el sqlite3.h:

#import "sqlite3.h"

Con esto ya estamos preparados para llamar a funciones de la api de sqlite.

La interfaz de acceso a sqlite está hecho en C por lo que podemos acceder a esas funciones sin problemas directamente desde Objective-C. Hay muchas funciones en la api de sqlite que no vamos a usar en los ejemplos de este artículo pero pueden ser indespensables para otros escenarios. Podeís consultar la documentación completa de la api de sqlite aquí.

Vamos a diferenciar dos tipos de consultas: consultas de selección de datos (SELECT) y consultas de modificación (INSERT, UPDATE, DELETE).

IMPORTANTE: no podemos hacer consultas de modificación a la base de datos si está se encuetra entre nuestros ficheros de recursos ya que allí no tenemos permisos de escritura. Para eso lo que se suele hacer es copiar la base de datos desde recursos a la carpeta Documents de nuestra aplicación cuando ejecutamos por primera vez nuestra app.

A continuación vamos a ver un ejemplo de un método que recibe una consulta de selección y nos devuelve un array donde cada elemento de ese array es un registro. Ese registro a su vez es un array donde cada elemento representa a un campo de ese registro. En los comentarios del ejemplo pueden ver las eplicaciones de las funciones usadas:

+(NSArray *)executeSelect:(NSString *)query{
 
	//Declaramos el puntero de la base de datos
	sqlite3 *db;
 
	//Obtenemos el path de nuestra base de datos
	NSString *path = [NSString stringWithFormat:@"%@/Documents/nuestraBaseDeDatos.sqlite", NSHomeDirectory()];
 
	//Intentamos abrir nuestra base de datos
	int r = sqlite3_open([path UTF8String], &amp;db);
 
	if (r != SQLITE_OK){
		NSLog(@"Error al abrir la base de datos");
		return nil;
	}
 
	//sqlite3_stmt representa nuestra consulta
	sqlite3_stmt *statement;
 
	//Creamos nuestro array que contendrá los resultados de nuestra consulta
	NSMutableArray *ar = [[NSMutableArray alloc] initWithCapacity:10];
 
	//Con esta línea se compila la consulta
	if (sqlite3_prepare_v2(db, [query UTF8String], -1, &amp;statement, NULL) == SQLITE_OK) {
		//Obtenemos el número de columnas/campos que van a tener los registros devueltos por nuestra consulta
		int columns = sqlite3_column_count(statement);
 
		//sqlite3_step devuelve un registro, entonces miestras hay registos los
		//añadimos a nuestro array.
		while (sqlite3_step(statement) == SQLITE_ROW) {
			//Declaramos el array donde guardaremos los datos del registro actual
			NSMutableArray *arc = [[NSMutableArray alloc] initWithCapacity:columns];
 
			//Por cada campo del registro obtenido comprobamos si es NULL.
			//Si es nullo añadimos un texto vacio, sino, el texto del regitstro
			for(int i=0; i &lt; columns; i++){
				if (sqlite3_column_text(statement, i) == NULL)
					[arc addObject:@""];
				else
					[arc addObject:[NSString stringWithCString:(char *)sqlite3_column_text(statement, i)
													  encoding:NSUTF8StringEncoding]
					 ];
 
			}
			//Añadimos el registro actual a nuestro array de registros a devolver
			[ar addObject:arc];
			//libermos la memoria del registro actual
			[arc release];
		}
 
	}
	//con esto liberamos la memoria usada por nuestra consulta
	sqlite3_finalize(statement);
 
	//cerramos la base de datos.
	sqlite3_close(db);
 
	[ar autorelease];
 
	return ar;
 
}

Visto el ejemplo de código para una consulta de selección vamos a ver otro de ejemplo para las consultas de modificación que es mucho más simple.

//Sirve para ejecutar comandos sql que no devuelven resultados
+(void)executeQuery:(NSString *)query{
	sqlite3 *db;
 
	NSString *path = [NSString stringWithFormat:@"%@/Documents/miBaseDeDatos.sqlite", NSHomeDirectory()];
 
	int r = sqlite3_open([path UTF8String], &amp;db);
	if (r != SQLITE_OK){
		NSLog(@"Error al abrir la base de datos");
		return;
	}
 
	sqlite3_stmt *statement;
 
	if (sqlite3_prepare_v2(db, [query UTF8String], -1, &amp;statement, NULL) == SQLITE_OK) {
 
		sqlite3_step(statement);
 
	}else{
		NSLog(@"******** Error DB: %@", query);
	}
 
	sqlite3_finalize(statement);
	sqlite3_close(db);
}

Para que estos ejemplos funciones tenéis que poner los paths correctos hacía vuestros bases de datos.
También hay que tener en cuenta que estos métodos no son muy óptimos ya que por cada llamada se ha de conectar a la base de datos y volver a desconectarse, tampoco está muy optimizado la forma de obtener los registros. Pero el código funciona muy bien en escenarios donde no se hace un uso muy intensivo de la base de datos y sirve como un buen ejemplo para empezar con sqlite.
Si no queréis trabajar con la api C de sqlite también se pueden usar los numerosos wrappers que existen para facilitar el acceso a la base de datos. Un de estos que nos gusta es el FMDB.

Descargar ficheros del ejemplo.

VN:F [1.9.8_1114]
Rating: 5.0/5 (2 votes cast)
VN:F [1.9.8_1114]
Rating: +1 (from 1 vote)
Tagged with:
may 05

Es una de las preguntas más frecuentes en los foros de programación iPhone.

Por qué cuando ponemos <img src=”miFoto.jpg” /> no sale nada si tenemos miFoto.jpg entre nuestros ficheros de recursos añadidos en XCode?

Supongamos que el código que usamos para cargar el html del nuestro UIWebView es el siguiente:

NSString *html = @"<html><body><img src='miFoto.jpg' /></body></html>";
[miUIWebView loadHTMLString:html baseURL:nil];

Con esto vemos que no se muestra ninguna imagen. El problema está en que UIWebView no sabe donde buscar la foto. Nosotros le hemos dicho la foto que queremos pero no hemos dicho donde tiene que buscarlo, para eso tenemos el parámetro baseURL.

Nuestra imagen está entre nuestros ficheros de recursos por lo que la UIWebView tiene que ir a buscar la imagen entre los ficheros de recursos y para eso tenemos que pasar el path de la carpeta de recursos vía el parámetro baseURL.

Entonces, la versión correcta del código sería el siguiente:

NSString *path = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
NSString *html = @"<html><body><img src='miFoto.jpg' /></body></html>";
[miUIWebView loadHTMLString:html baseURL:path];

Y todo esto que se ha explicado también es válido en el caso de que queramos incluir scripts externos o ficheros CSS.

Sorprendentemente hay gente que no conoce esta solución tan simple y hacen “cosas raras” sólo para poder mostrar fotos.

Una forma poco común de mostrar imágenes es convertirlos a base64 y usar la esquema data:URI para mostrarlos en una página web.

Ejemplo:

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoA
AAAKCAYAAACNMs+9AAAABGdBTUEAALGPC/xhBQ
AAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0S
U1FB9YGARc5KB0XV+IAAAAddEVYdENvbW1lbnQAQ3Jl
YXRlZCB3aXRoIFRoZSBHSU1Q72QlbgAAAF1JREFUGNO9zL
0NglAAxPEfdLTs4BZM4DIO4C7OwQg2JoQ9LE1exdlYvBBeZ7
jqch9//q1uH4TLzw4d6+ErXMMcXuHWxId3KOETnnXXV6M
Jpcq2MLaI97CER3N0vr4MkhoXe0rZigAAAABJRU5ErkJggg==" alt="Red dot" />

Esto en la mayoría de los casos no se usa pero también tiene su utilidad en diferentes entornos. Por ejemplo antes de la versión 3.0 de iPhone OS no podíamos adjuntar imágenes a los emails desde nuestro código y muchos lo solucionaron parcialmente pasando la imagen como texto (en base 64).

VN:F [1.9.8_1114]
Rating: 5.0/5 (1 vote cast)
VN:F [1.9.8_1114]
Rating: 0 (from 0 votes)
Tagged with:
abr 30

Core Data nos simplifica mucho el trabajo cuando queremos trabajar con una base de datos ya que tiene implementadas muchas de las tareas comunes asociadas al ciclo de vida de objetos. En este post no quiero dar una guía sobre Core Data, simplemente un ejemplo de como manejar, desde nuestra aplicación, los elementos mínimos requeridos para trabajar con Core Data y así tener una base con la que empezar a usar este Framework. En la documentación de Apple encontrareis muchísima información al respecto (Core Data Programming Guide).

Para empezar necesitamos tres objetos diferentes, un NSManagedObjectModel, un NSManagedObjectContext y un NSPersistentStoreCoordinator.
El primero es el que contiene la información que describe el modelo de nuestra base de datos, las entidades y sus atributos así como relaciones entre ellas… El segundo se ocupa de controlar un conjunto de objetos, entidades de nuestra base de datos, vigilando los cambios que se realizan sobre ellos, dando la opción de deshacer cambios y validación de estos entre otras funciones, finalmente el tercero nos hace de enlace entre el segundo y la base de datos física, guardando los posibles cambios realizados en el contexto.

Cuando creamos un nuevo proyecto en XCode, en algunas plantillas, nos da la opción de usar Core Data,

si marcamos la casilla indicada, XCode nos creará el proyecto y en nuestro AppDelegate tendremos tres variables, los tres objetos mencionados anteriormente.

@interface TestAppDelegate : NSObject <UIApplicationDelegate> {
 
    NSManagedObjectModel *managedObjectModel;
    NSManagedObjectContext *managedObjectContext;	    
    NSPersistentStoreCoordinator *persistentStoreCoordinator;
 
    UIWindow *window;
}
 
@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
 
@property (nonatomic, retain) IBOutlet UIWindow *window;
 
- (NSString *)applicationDocumentsDirectory;
 
@end

Si miramos en el grupo “Resources” de nuestro proyecto, veremos un archivo con extensión “.xcdatamodel”, es aquí donde creamos las entidades de nuestra base de datos con sus atributos y relaciones.

Ahora bien, alguno se estará preguntando como realizamos búsquedas en nuestra base de datos, para mostrar información en una vista, o para llenar una tabla. Para eso necesitamos una cuarta clase, NSFetchRequest.

Si queremos centralizar las consultas sobre la base de datos en un solo objeto de nuestra aplicación y evitar tener que ir pasando estos cuatro elementos, os recomiendo lo siguiente. En nuestro AppDelegate añadimos las siguientes funciones:

// Returns the AppDelegate (self) so it is accesible from all classes
+ (TestAppDelegate *)sharedAppDelegate;
 
// Methods for fetching data from the data store
- (NSArray *)allObjectsForEntity:(NSString *)entity orderedBy:(NSString *)atribute;

La función de clase nos devolverá el AppDelegate (si mismo), de manera que a través de él podamos acceder a los tres objetos que nos controlan la base da datos, para que no sea posible crear más de un AppDelegate y éste no pueda ser modificado lo que haremos será crearnos una variable estática, y la inicializaremos en nuestro init, en el AppDelegate.m añadimos

static TestAppDelegate *sharedInstance;
 
@implementation TestAppDelegate
 
- (id) init
{
	if (sharedInstance) {
		NSLog(@"Error: You are creating a second AppController");
	}
 
	self = [super init];
	if (self != nil) {
		sharedInstance = self;
	}
	return self;
}
 
...
 
/**
 Returns itself for easy acces to the appController
 */
+ (TestAppDelegate *) sharedAppDelegate {
	return sharedInstance;
}

mientras que la función de instancia, será la encargada de hacer la búsqueda.

/**
 Returns an array with all the objects for the given entity name sorted by the given atribute
 */
- (NSArray *)allObjectsForEntity:(NSString *)entity orderedBy:(NSString *)atribute {
	/*
	 Set up the fetched results controller.
	 */
	// Create the fetch request for the entity.
	NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
	// Edit the entity name as appropriate.
	NSEntityDescription *entityDescription = [NSEntityDescription entityForName:entity 
                                                             inManagedObjectContext:[self appManagedObjectContext]];
	[fetchRequest setEntity:entityDescription];
 
	// Set the batch size to a suitable number.
	//[fetchRequest setFetchBatchSize:20];
 
	// Edit the sort key as appropriate.
	NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:atribute ascending:YES];
	NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
 
	[fetchRequest setSortDescriptors:sortDescriptors];
 
	// Edit the section name key path and cache name if appropriate.
    // nil for section name key path means "no sections".
	NSError *error;
	NSArray *result = [[self appManagedObjectContext] executeFetchRequest:fetchRequest error:&error];
	if (error) {
		// TODO: Manage error executing fetch
		[error release];
	}
 
	[fetchRequest release];
	[sortDescriptor release];
	[sortDescriptors release];
	return result;
}

esta función nos devuelve un array con todos los objetos del tipo que le hemos indicado “entity” ordenados ascendentemente por el “atribute” indicado, con este array ya podemos obtener la información que queramos mostrar, o llenar una tabla. Tened en cuenta que este array esta lleno de NSManagedObject, y que hay que usar la función valueForKey: para obtener el valor de cada atributo.

Usando de base esta última función, podemos añadir a nuestro AppDelegate otras funciones para realizar otro tipo de búsquedas según nos sea necesario y centralizar así, la interacción con la base de datos en nuestro AppDelegate.

Por ejemplo, en otra clase que requiera información de la base de datos, podemos inicializar un vector de datos en la función “viewDidLoad” de la siguiente manera.

- (void)viewDidLoad {
    [super viewDidLoad];
   TestAppDelegate *appDelegate = [TestAppDelegate sharedAppDelegate];
   NSArray *dataArray = [appDelegate allObjectsForEntity:@"Test" orderedBy:@"date"];	
}

Espero que os sea de utilidad para empezar a usar Core Data, aunque solo sea una forma muy básica de hacerlo.

VN:F [1.9.8_1114]
Rating: 4.5/5 (4 votes cast)
VN:F [1.9.8_1114]
Rating: +3 (from 5 votes)
Tagged with:
abr 29

En este arículo/tutorial vamos a ver en detalle cómo obtener la posición GPS del iPhone, que problemas podemos tener durante la obtención de las coordenadas y cómo podemos solucionar estos problemas.

Primero de todo si queremos usar el GPS en nuestras aplicaciones tenemos que añadir el CoreLocation a nuestro proyecto. Para eso hacemos click derecho en el grupo “Frameworks” del nuestro proyecto y seleccionamos “Add > Existing Frameworks…”.

Esto nos abrirá la ventana para seleccionar el framework a añadir. Buscamos el “CoreLocation.framework” y lo añadimos al proyecto.

Con esto tenemos todo configurado para empezar a picar código que use el GPS.

En nuestro código tenemos que incluir las cabeceras de las funciones GPS:

#import <CoreLocation/CoreLocation.h>
#import <CoreLocation/CLLocationManagerDelegate.h>

asdad—————————————-

Para obtener las coordenadas tenemos que usar el “CLLocationManager” e implementar el protocolo “CLLocationManagerDelegate“.

El CLLocationManager se inicializaría así:

CLLocationManager *gpsManager = [[CLLocationManager alloc] init];
[gpsManager setDelegate:self];
[gpsManager setDesiredAccuracy:kCLLocationAccuracyBest];
[gpsManager startUpdatingLocation];

Con startUpdatingLocation decimos al location manager que empiece a leer del dispositivo GPS.

Ahora veamos los métodos del protocolo a implementar. Nuestro gpsManager llamará a estos métodos cuando obtenga coordenadas nuevas.

//Se llama cada vez que hay una nueva coordenada
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation{
    //Hacer algo con newLocation (mostrar la posición en el mapa o lo que sea
}
 
//Se llama cuando se produce algún error en la obtención de datos GPS
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
   if ([error code]==kCLErrorDenied)
       NSLog(@"El usuario ha denegado el acceso a GPS");
  else
       NSLog(@"Se ha producido algún error interno");
}

Con estos métodos ya tenemos todo para leer las coordenadas. En el parámetro newLocation del primer método nuestro “Location Manager” enviará las coordenadas nuevas y nosotros las usuaríamos para hacer X en nuestro programa.

El segundo método se lamará cuando haya algún error. El error puede ser debido a que el usuario haya denegado el acceso al GPS o por otro motivo (ver la documentación para ver los posibles constantes de error existentes).

Hasta aquí lo más básico.

En una aplicación real con sólo esto no sería suficiente.

El primer problema está en la precisión de las coordenadas obtenidas. A la que se pide al locaton manager que empiece a obtener coordenadas (stratUpdatingLocation) este comienza a enviar las coordenadas que obtiene mediante triangulación de las antenas de telefonía móvil y mientras tanto intenta sincronizarse con los satélites para obtener coordenadas más precisas.

Esas primeras coordenadas que obtiene pueden ser muy imprecisas (pueden a llegar a tener un error de más de 10Km) y muchas veces es mejor que nuestro programa los ignore para no engañar al usuario. Para eso en el método donde leemos las nuevas coordenadas tenemos que filtrar los que no tienen la precisión suficiente para nuestro programa.

Para eso se usa la propiedad horizontalAccuracy (que se da en metros) del nuestro objeto de  tipo CLLocation.

Veamos un ejemplo:

- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation{
	//Si el error de la nueva coordenada es mayor o igual a 100 metros usar la coordenada
	if (newLocation.horizontalAccuracy &lt;= 100.0 ){
               ;//Usar la nueva coordenada
	}
 
}

Con esto solucionamos el posible problema que pudieramos tener con la precisión.

Otro problema que puede haber tiene que ver con la cache de coordenadas que mantiene el sistema operativo.
El location manager nos puede enviar, al principio, coordenadas que ha obtenido hace muchas horas. Entonces también tenemos que controlar eso si queremos tener siempre datos frescos. Para eso tenemos que medir el tiempo que ha pasado desde que se ha obtenido una coordenada. El método anterior con este arreglo se quedaría así:

- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation{
        //Obtenemos la fecha de obtención de la coordenada
	NSDate* newLocationeventDate = newLocation.timestamp;
 
        //Obtenemos el tiempo (en segundos) que ha pasado desde que se obtenido nuestra coordenada
	NSTimeInterval howRecentNewLocation = [newLocationeventDate timeIntervalSinceNow];
 
        //Si el error es menor u igual a 100 metros y la coordenada se ha obtenido en los útlimos 20 segundos
	if (newLocation.horizontalAccuracy &lt;= 100.0 &amp;&amp; howRecentNewLocation &lt; -0.0 &amp;&amp; howRecentNewLocation &gt; -20.0){
		;//Usar coordenada
	}
 
}

Con esto solucionamos el problema de la cache. Pero hay un detalle más a tener en cuenta y es que puede que el GPS manager nunca llegue a obtener las coordenadas con la precisión que queremos nosotros y hacer que nos quedemos a esperar a esa coordenada para siempre.
Para eso se ha de programa un timer. No voy a explicar detalles de como programarlo pero la idea consiste en que un timer llame a un método pasado N segundos (nuestro timeout definido para el gps). Ese método que se ejecutará N segundos después y comprobará si en esos N segundos se ha obtenido alguna coordenada si es que si entonces no hay problema, si es que no, entonces debería notificar al usuario o a algún subsistema del programa que deje de esperar al gps o que cambie la precisión requerida o cualquier otra cosa más que se nos ocurra.
Aquí va un pseudocódigo:

...
[NSTimer scheduledTimerWithTimeInterval: 13.0 target:self selector:@selector(gpsTimeOut:) userInfo:nil repeats: NO];
...
-(void) gpsTimeOut: (NSTimer *) theTimer {
	if ([self seHaObtenidoCoordenada] != YES){
               //Hacer lo que sea si en 13 segundos no se ha obtenido una coordenada
       }
}

Con esto cerramos este minitutorial sobre el uso de GPS. Si tenéis preguntas no dudéis en formularlos en los comentarios.

VN:F [1.9.8_1114]
Rating: 5.0/5 (4 votes cast)
VN:F [1.9.8_1114]
Rating: +4 (from 4 votes)
Tagged with:
abr 27

En este artículo vamos a explicar como encontrar el identificador (no confundirlo con el número de serie) del iPhone (iPod y iPad)  también conocido como “device id”.

Tenemos que seguir los siguientes pasos:

  1. Conectar nuestro dispositivo al ordenador (Mac o Windows).
  2. Abrir el iTunes.
  3. Seleccionar nuestro dispositivo en la lista de dispositivos.

    .
  4. Seleccionar la pestaña Resumen y hacer click sobre el texto “Número de serie”. Al hacer esto el número de serie cambiará y mostrará el identificador.

    Después del click en “Número de serie” debería ver el identificador:


Finalmente, si queremos copiar el identificador vamos al menú: Edición > Copiar (mientras el identificador este visible).

Si estamos en un sitio donde no tenemos acceso a nuestro ordenador pero si tenemos acceso a Internet podemos conectarnos a AppStore y descar una de las aplicaciones especializadas que nos muestran nuestro device id y permiten enviarlo fácilmente. Dos de esas aplicaciones son: Info – UDID y Info – UDID Sender.

Si tiene preguntas no dude en contactar con nosotros.

VN:F [1.9.8_1114]
Rating: 3.7/5 (3 votes cast)
VN:F [1.9.8_1114]
Rating: +2 (from 2 votes)
Tagged with:
abr 27

In order to find your device id you have to:

  1. Connect the device to your computer (Mac or Windows).
  2. Open the iTunes.
  3. Select your device.

    .
  4. Go to summary tab and click on “Serial Number”. You’ll notice that the “Serial Number” changes to “Identifier”.

    After clicking on the “Serial Number” we must see something like this:

Finally if you want to copy the id: click on Edit > Copy (while the Identifier is visible).

If you haven’t access to your computer but you have Internet connection you can download some free specialized apps (Info – UDID or Info – UDID Sender) from AppStore which can provide you with your device id information.

VN:F [1.9.8_1114]
Rating: 3.0/5 (2 votes cast)
VN:F [1.9.8_1114]
Rating: 0 (from 0 votes)
Tagged with:
preload preload preload