May 23

Today we will see a very simple example of how to use and for what it’s use the NSKeyValueOnserving protocol (KVO), as in other posts, what is explained here can be used in many other cases.

This protocol allows us to detect changes in property values of objects and works as follows:

  • We have an object X of which we want to know when an attribute is modified
  • We have and object Y which will perform an action when X changes its attribute
  • The previous protocol allows the object Y to be notified of changes, for the attribute we specify, of the object X.

To achieve this we only need to tell X that Y wants to be notified of such changes using the method addObserver:forKeyPath:options:context: defined in the protocol and perform the appropriate actions using the method observeValueForKeyPath:ofObject:change:context: on the object Y, since this is the message that is sent when the attribute changes its value.

How example we use this method to detect when a user selects or deselects an annotation (MKAnnotation) on a map (MKMapView). First I will show how to set up the project as a tutorial and below how to used exactly the KVO, if you already have your project, go directly to the second section.

Preparation

First create a new project using the Xcode template based on a view. We add to the project the MapKit Framework to use the map and annotations.
Then modify the view controller to add an IBOutlet as reference the map that will be added later. We will have to import the Framework.

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

We open the file MapKVOViewController.xib with Interface Builder and add a MKMapView to assign the previously created IBoutlet, also indicate that the delegate of the map will be the controller.
For annotations on the map we need to add another class to our project, in Xcode add a new class from a NSObject, we import here also the MapKit Framework. This class has to comply with the protocol so modify the MKAnnotation.h to do so.

#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

How this class is only for a test, initialize variables at constant values, modify the .m to look like this.

@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

To end the project preparation we only need to add an annotation on the map, for example use the viewDidLoad to do so. Remember you have to import your new class to use annotations on the map (here MapAnnotation).

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

Using KVO

In our example, object X is the view of the annotation, the pin you see on the map, in one hand we have the annotation itself and in another the view that represents it on the map, as the action of selection is made on the map, is the view of the annotation which has an attribute called selected, which is what we will want to monitor.

The object Y, will be our MapKVOViewController, which is responsible for adding annotations to the map.

First step, tell the object X we want to observe the selected attribute, if we want to do this with all the entries in the map, a good place is the method MapView:didAddAnnotationViews: in MKMapViewDelegate protocol, add this method to our controller and modify it to be like this

- (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];
	}
}

Please note that this will be done for each annotation, so if there are different types of annotations, you have to differentiate between which one you want to be notified and which ones not.

The method that interests us is addObserver:forKeyPath:options:context:, to the latter we will indicate who will be the observer, self, which attribute to observe, selected, with which options, NSKeyValueObservingOptionNew indicates that we want the new value, and a context that is used to give us more information, such as what we are seeing, here you can use nil if you observe only one thing, if you have to distinguished it is useful to use constants for each case, here we use

static NSString* const ANNOTATION_SELECTED_DESELECTED = @"mapAnnotationSelectedOrDeselected";

defined at the beginning of our .m

Finally, in order to take any action, once notified of the change, we need to overwrite the method 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
		}
	}
}

Add the code to perform the action once we know if the annotation has been selected or deselected, for example, play a sound.

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

Today I will explain how we can play a sound when a button is pressed, in fact, what I will explain can be used in many other situations and not only for buttons, because what I will do is to create a SystemSoundID which is used, among another things, to play short (30 seconds or less) sounds. But as an example, I will use a button.

To be able to reproduce sounds we will need to add the AudioToolbox Framework to our project.

Imagine we have a UIViewController and the view it is controlling, add a UIButton to the view using Interface Builder or do it programmatically, as you like most. If you haven’t added yet the Framework to the project, do it now, once you have done it we have to import it to our view controller.

In the header file (.h) of our controller add a variable of type SystemSoundID and a method which will be called when the button is pressed.

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

Now we need a sound file, with the sound we want, for example call it ButtonSound.caf, now, and in the viewDidLoad of our controller add the following lines to create the reference to our sound.

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

As you can see in the code, we need the path for our file, if you have add the file to your group Resources in Xcode, the code above should work for you. Take a look at NSBundle if that doesn’t work to look for another method that allows you to find the path to the file.

With this we have the reference to the sound, now we only need to play it when we need, in this case, we want to play it when the button is pressed. Add to the method that is called when the button is pressed, the following lines

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

Now you should be hearing the sound when you press the button.

When you don’t need anymore the sound, you can use the following function to remove the reference, for example in the viewDidUnload of our controller.

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

I hope that this post is useful and now you are able to add all kind of short sounds to your application to give it a bit more of life.

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

What if our application only makes sense in landscape mode?
We can force it.
First we have to tell to our view controller only allow autorotation to landscape orientations:

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

In the Info plist for that project we can specify that the device should be initialized to a particular orientation. Right click (control click) on the plist file and chose Open As -> Plain Text File. Just before the bottom of that file, insert the necessary key-value pair:

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

No when you run your app regardless how you rotate the device, it should only appear in landscape mode.

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:
Apr 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:
Apr 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:
Apr 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:
Apr 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