thxou

Ama la sabiduría. Desea el conocimiento.

iOS: Usando NSUserDefaults Para Asignar Configuraciones Por Defecto

| Comments


Cada aplicación tiene un sistema de almacenamiento por defecto para cada usuario llamado User Default System (Desde ahora UDS), el cual está compuesto por una base de datos en la que, a través de unos parámetros y métodos, podemos almacenar y recuperar ciertos valores, que suelen ser pequeñas cantidades de datos que usamos comúnmente en nuestra aplicación. A estos valores por defecto se les llama: preferencias del usuario. Por ejemplo, podríamos querer permitirle a los usuarios elegir cuán periódicamente sincronizar ciertos datos con iCloud, este valor de tiempo lo podemos almacenar en el UDS y recuperarlo al inicio de la aplicación.

Podemos clasificar las preferencias de usuario en 2 categorías: las que cambian frecuentemente y las que no. En esta oportunidad trabajaremos con las que cambian frecuentemente, la otra categoría la veremos en artículos posteriores ya que requiere un poco más de profundidad.

La clase NSUserDefaults nos permite interactuar con el UDS, proveyendo diversos métodos para guardar y recuperar datos desde esta base de datos por defecto.

Algunas características de esta clase

Solo existe una única instancia de esta clase por aplicación.

Una ventaja es el almacenamiento en caché de la información. Un sistema como éste implicaría abrir la base de datos constantemente, en concreto cada vez que el usuario pida información. Para evitar esta constante apertura, la clase almacena los datos en caché. El método synchronize es invocado periódicamente y se encarga de escribir los datos nuevos en caché y de actualizar los ya existentes, y esto lo hace de forma transparente al usuario. No obstante si no deseas esperar a la sincronización automática, puedes usar este método para actualizar los datos inmediatamente, teniendo cuidado de llamarlo sólo cuando hayas hecho alguna modificación. Así nos evitamos una sobrecarga de conexiones con la base de datos.

Otro punto importante a tener en cuenta es que todos los valores retornados desde la base de datos son inmutables (es decir, que no se pueden modificar), incluso aunque guardaras un valor mutable (por ejemplo una instancia de NSMutableString), al retornarlo al usuario sería inmutable.

Obteniendo y escribiendo datos

Explicaré mejor el tema con una mini aplicación que he hecho llamada MyDefaults. Esta aplicación tiene ciertas características que mostrar como: la hora actual a intervalos de tiempo, un mensaje de bienvenida al inicio y la fecha y hora de la última vez que se abrió la aplicación. El primer dato que guardaremos será de tipo float y será el intervalo en segundos en el que se va a actualizar la hora; el segundo es de tipo booleano, nos permitirá definir si queremos, o no, mostrar el mensaje de bienvenida; el tercero es un string que contiene nuestro nombre para mostrarlo en el mensaje de bienvenida y en la vista principal; y el cuarto es de tipo NSDate para la última visita. Podremos personalizar cada uno de estos valores desde una ventana modal llamada: Preferencias.

Para interactuar con los datos de la base de datos, NSUserDefaults pone a nuestra disposición una serie de métodos de conveniencia. Ahora, qué datos se nos permite guardar y obtener?, concretamente los mismos que en una Property List: NSString, NSNumber (booleanos, integers, floats y doubles), NSDate, NSArray o NSDictionary. Para tipos diferentes a estos podemos archivarlos con NSData, que también nos permite datos de este tipo.

Como he dicho antes, solo hay una instancia de esta clase por aplicación, así que para acceder a ella usamos el método de clase standardUserDefaults:

1
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

Ya tenemos nuestra instancia de la clase, por lo tanto ya podemos obtener y guardar datos. Esto lo hacemos usando los métodos de conveniencia de los que hablé anteriormente (Al final de este artículo hay enlaces hacia la documentación para ver la relación entera de métodos disponibles). Nosotros vamos a obtener y a mostrar estos valores al iniciar el controlador ViewController en el método viewDidLoad::

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
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

// obtenemos el nombre desde la base de datos
NSString *nombre = [defaults stringForKey:@"kMiNombre"];

// mostramos el mensaje de bienvenida con el nombre si el valor
// de la base de datos nos lo permite
BOOL mostrarMensaje = [defaults boolForKey:@"kMostrarMensaje"];

if (mostrarMensaje)
{
    UIAlertView *mensaje = [[UIAlertView alloc] initWithTitle:@"MyDefaults"
                                                      message:[NSString stringWithFormat:@"Hola %@, bienvenido a MyDefaults", nombre]
                                                     delegate:self
                                            cancelButtonTitle:@"Ok"
                                            otherButtonTitles:nil];
    [mensaje show];
    [mensaje release];
}

// obtenemos la fecha de la ultima visita
NSDate *ultimaVisita = [defaults objectForKey:@"kUltimaVisita"];

// como a esta key le asignamos por defecto nil, comprobamos que no sea (osea que haya alguna
// fecha), de lo contrario mostramos un mensaje alternativo
if (ultimaVisita != nil)
{
    // formateamos la fecha y hora obtenida de la base de datos
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateStyle:NSDateFormatterMediumStyle];
    [formatter setTimeStyle:NSDateFormatterMediumStyle];
    self.lblVisita.text = [formatter stringFromDate:ultimaVisita];
    [formatter release];
}
else {
    self.lblVisita.text = @"Hola, esta es la primera vez que accedes : )";
}

// guardamos la fecha de la última visita
[defaults setObject:[NSDate date] forKey:@"kUltimaVisita"];
[defaults synchronize];

Como pueden observar estos métodos son muy intuitivos y son de la forma: tipoForKey:. Donde tipo es el tipo de dato que quieres obtener, en nuestro caso es float, bool, stringobject (para el de tipo NSDate). Las Keys, son simples strings que identifican a un valor en concreto dentro de la base de datos y no pueden haber 2 iguales. Yo he usado unos cuantos objetos UILabel conectados con el Interface Builder para mostrar estos datos de una mejor manera, como también he usado la clase NSDateFormatter para dar formato a la fecha que viene desde la base de datos en caso de que ya se haya guardado alguna antes. Los comentarios en verde te ayudarán a entender mejor cada parte del código.

Si van un poco más abajo en el código verán que hay un método llamado viewWillApperar:, este método es lanzado cuando la pantalla está a punto de ser mostrada. En este método usamos un objeto NSTimer para programar la actualización de la hora al intervalo de tiempo que hemos obtenido de la base de datos.

1
2
3
4
5
6
7
8
9
10
11
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

// Cargamos el valor de la hora. Usaremos un timer para actualizar la hora cada x tiempo
NSTimeInterval interval = [defaults floatForKey:@"kIntervaloHora"];

// ponemos el timer en funcionamiento y se actualizará cada "interval" segundos
self.timer = [NSTimer scheduledTimerWithTimeInterval:interval
                                              target:self
                                            selector:@selector(actualizaHora)
                                            userInfo:nil
                                             repeats:YES];

Ya tenemos los datos mostrados en la UI (Interfaz de Usuario) para hacer todos los cambios que creamos convenientes (Hazlos!) y luego guardarlos. Para esto vamos al controlador SettingsViewController y ahí encontraremos el método guardar:, el cual, accionado por el botón “Guardar” de la barra de herramientas, nos guardará los datos en la caché para luego sincronizarlos, a través del método syncronize, con la base de datos:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (IBAction)guardar:(id)sender
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

    // Guardamos el intérvalo de actualización de la hora
    [defaults setFloat:[self.txtHora.text floatValue] forKey:@"kIntervaloHora"];

    // Guardamos el estado del UISwitch como un booleano
    [defaults setBool:self.switchMsg.on forKey:@"kMostrarMensaje"];

    // Guardamos el nombre que hemos definido
    [defaults setObject:self.txtNombre.text forKey:@"kMiNombre"];

    // sincronizamos la caché y la base de datos
    [defaults synchronize];
1
2
3
    // cerramos la ventana modal
    [self dismissModalViewControllerAnimated:YES];
}

Aquí usamos nuevamente métodos de conveniencia, los cuales tienen la forma: setTipo:forKey:, donde tipo puede ser de los tipos escalares (integer, float, bool o double), NSUrl u object para cualquier otro tipo de objeto de los mencionados más arriba.

Si nos fijamos, también tenemos redefinido el método viewDidLoad: como pasó en el controlador de la pantalla principal. Hacemos lo mismo, cargar los datos guardados para mostrarlos en la pantalla de preferencias.

Registrando preferencias por defecto

Eso sería todo con respecto a guardar y obtener datos de la base de datos del User Default System. No obstante he querido explicar brevemente como registrar preferencias por defecto para nuestra aplicación. Por ejemplo si es la primera vez que la ejecutamos, probablemente nos gustaría que la base de datos ya tenga unos valores almacenados por defecto. Esto lo hacemos en el método application:didFinishLaunchingWithOptions: del controlador principal AppDelegate, que es llamado inmediatamente después de terminar de cargar la aplicación:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

if (![defaults boolForKey:@"kValoresGuardados"])
{
    NSDictionary *defaultValues = [NSDictionary dictionaryWithObjectsAndKeys:
                                   [NSNumber numberWithFloat:1.0], @"kIntervaloHora",
                                   [NSNumber numberWithBool:YES], @"kMostrarMensaje",
                                   @"ThXou", @"kMiNombre",
                                   nil, @"kUltimaVisita",
                                   [NSNumber numberWithBool:YES], @"kValoresGuardados",
                                   nil];

    [defaults registerDefaults:defaultValues];
}

Al método registerDefaults: le pasamos un diccionario con los valores y las keys que queremos tener por defecto en nuestra base de datos al inicio de la aplicación. Es importante tener en cuenta de que para los valores de tipo escalar tenemos que usar instancias de NSNumber como se puede observar en el código. He añadido una nueva key llamada @"kValoresGuardados" para evitar que nos vuelva a guardar estos mismos valores cada vez que iniciemos la aplicación y que solo lo haga la primera vez.

Y ahora que?

Hoy hemos aprendido a usar la clase NSUserDefaults para a asignar preferencias y configuraciones por defecto para nuestra aplicación, así como una serie de métodos para interactuar con el User Default System. Para continuar aprendiendo sobre este tema te recomiendo que te pases por la documentación de Apple acerca de esta clase y hagas todas las pruebas posibles hasta que entiendas correctamente su funcionamiento.

Comments