sábado, 3 de noviembre de 2012

Hacer Segue desde un UILabel o UIView

Los Segues son una especie de vínculos que nos permiten movernos entre los view controllers. Hacer segue desde botones es la opción más común, pero no la única. Os voy a mostrar como se puede hacer tambien desde UIView o UILabel.

Vamos a suponer que tenemos un View Controller tan simple como el de la siguiente figura, con solamente una etiqueta azul. Queremos conseguir que, al tocar sobre la etiqueta, nos lleve a otro View Controller.


Dentro del Attributes Inspector de la etiqueta es importante marcar el check User interaction Enabled.


A continuación arrastraremos un Tap Gesture Recognizer y lo soltamos sobre la etiqueta. 


Nos creamos otro View Controller simple hacia el que vamos a hacer segue al tocar sobre la etiqueta y, pinchando sobre el Tap Gesture Recognizer con el botón derecho arrastramos hacia el nuevo View Controller.



Y ya está, así de sencillo.

Lo mismo se podría hacer sobre una vista.

@Fin

sábado, 20 de octubre de 2012

Utilización de UIScrollView con UIPageControl

Vamos a ver como utilizar un UIScrollView con un UIPageControl, lo que nos va a permitir crear una aplicación con varias páginas.
La clase UIPageControl son los puntos que aparecen en la parte inferior la ventana de muchas aplicaciones y que indican la ventana en la que estáis cuando hay varias.
Lo primero que haremos será añadir un UIPageControl al UIVIewController en el StoryBoard:



Lo voy a hacer solo con 2 páginas, así que pongo la propiedad Pages a 2. Seleccionando el UIPagecontrol con el botón derecho, lo "cableo" hacía el código del UIViewController para crear la propiedad:

@property (weak, nonatomic) IBOutlet UIPageControl *pageControl; 

Vamos con el UIScrollView. En esta ocasión lo voy a hacer por código, aunque también se podría añadir en el StoryBoard.
Lo primero que haremos es añadir el delegate de UIScrollView al interface ViewController:

@interface myViewController : UIViewController <UIScrollViewDelegate>


A continuación definimos un método que va crear dos vistas con dos etiquetas y las va a añadir al ScrollView:

-(void)createTwoViewsWithTwoLabels
{
    // ScrollView
    UIScrollView *scrollView=[[UIScrollView alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height - 60)];
    
    // View 1
    UIView *view1=[[UIView alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height - 40)];
    view1.backgroundColor=[UIColor colorWithRed:0.5f green:1.0f blue:0.5f alpha:1.0f];
    
    // Label 1
    UILabel *label1 = [[UILabel alloc]initWithFrame:CGRectMake(22, 20, 275, 38)];
    [label1 setBackgroundColor:[UIColor clearColor]];
    [label1 setText:@"Vista 1"];
    label1.textColor=[UIColor blueColor];
    label1.adjustsFontSizeToFitWidth=YES;
    label1.textAlignment = NSTextAlignmentRight;
    label1.font = [UIFont fontWithName:@"Chalkduster" size:50];
    label1.numberOfLines = 1;
    [view1 addSubview:label1];
    
    //Se añade la primera vista al scrollview
    [scrollView addSubview:view1];
    
    //View 2
    UIView *view2=[[UIView alloc]initWithFrame:CGRectMake(self.view.frame.size.width, 0, self.view.frame.size.width, self.view.frame.size.height - 40)];
    view2.backgroundColor=[UIColor colorWithRed:1.0f green:1.0f blue:0.7f alpha:1.0f];
    
    //Label 2
    UILabel *label2 = [[UILabel alloc]initWithFrame:CGRectMake(22, 20, 275, 38)];
    [label2 setBackgroundColor:[UIColor clearColor]];
    [label2 setText:@"Vista 2"];
    label2.textColor=[UIColor blueColor];
    label2.adjustsFontSizeToFitWidth=YES;
    label2.textAlignment = NSTextAlignmentRight;
    label2.font = [UIFont fontWithName:@"Chalkduster" size:50];
    label2.numberOfLines = 1;
    [view2 addSubview:label2];
    
     // Se añade la segunda vista al scrollview
    [scrollView addSubview:view2];
    
    // Propiedades necesarias del scrollview
    scrollView.pagingEnabled = YES;
    scrollView.delegate = self;
    scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * 2, scrollView.frame.size.height);
    scrollView.showsHorizontalScrollIndicator = NO;
    scrollView.showsVerticalScrollIndicator = NO;
    scrollView.bounces = NO;
    
    //Se añade el scroll view a la vista del ViewController
    [self.view addSubview:scrollView];
    
}


Este método se llamará en el viewDidLoad:

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self createTwoViewsWithTwoLabels]; 
}


Por último voy a implementar la función del UIScrollViewDelegate que detecta el cambio de página en el scroll:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    
    CGFloat pageWidth = scrollView.frame.size.width;
    int pagina = floor((scrollView.contentOffset.x- pageWidth / 2) / pageWidth) + 1;
    self.pageControl.currentPage=pagina;
    
}

Y con esto ya tendríamos un sencillo scroll con page control:



@Fin



martes, 21 de agosto de 2012

Ejecución de un proyecto con una localización específica en iOS

Cuando tengáis un proyecto con varias localizaciones, al ejecutarlo en el simulador, se verá la localización definida por defecto.
Necesitaréis comprobar como se comporta la App en los distintos idiomas. La mejor solución es utilizar un argumento que se pasan al ejecutable. Se trata del -AppleLanguages. Se le pueden pasar una lista de idiomas, separados por comas, en el orden preferido. En nuestro caso como lo que necesitamos es probar las diferentes localizaciones de nuestro proyecto, le indicaremos el idioma que queramos verificar en cada momento. Para ello, desde Xcode se accede al menú Product > Edit Scheme...  



A continuación, en la configuración del Run, acceder a la pestaña Arguments, pulsar '+' y añadir el argumento -AppleLanguages "(<localización>)". En el ejemplo de la siguiente imagen, la localización a probar es la española.


Otra forma de probar las diferentes localizaciones es desde un dispositivo iOS. Se cambia el idioma del dispositivo desde las preferencias y se ejecuta el proyecto. Aunque esta otra solución yo la veo más para las últimas fases de los tests.

@Fin



sábado, 2 de junio de 2012

Indicador de actividad

Una de las cosas que se debe evitar por todos los medios cuando se está desarrollando una App es que la ventana se quede bloqueada y no se muestre algún indicador que informe que está trabajando. Por ejemplo cuando se está cargando una página web. 
Una solución para evitar esta situación tan frustrante para el usuario es utilizar la propiedad setNetworkActivityIndicatorVisible de la clase UIApplication. Con esto se consigue añadir el circulo con rayitas que van cambiado de tono en la parte superior de la aplicación:


Otra posible forma sería utilizar la clase UIActivityIndicatorView. Vamos a ver un sencillo en el que mostraremos estos dos tipos de indicadores mientras se carga una página web.

Crearemos un Story Board con un View Controller al que añadiremos un Web View. Además cablearemos este UIWebView hacía el controller para especificarlo como su delegate. A continuación lo metemos en un Navigation Controller.


Network Activity Indicator


El interface del View Controller será así:

#import <UIKit/UIKit.h>

@interface WebViewController : UIViewController <UIWebViewDelegate>
@property (nonatomic, weak) NSString *webPage;
@property (weak, nonatomic) IBOutlet UIWebView *webView;

@end


Como veis el modelo va a ser un string con la URL. Además hemos añadido el protocolo UIWebViewDelegate.

El UIWebView lo voy a definir como local:

@interface WebViewController ()
@property (weak, nonatomic) IBOutlet UIWebView *webView;
@end


La implementación de viewDidLoad será así:

- (void)viewDidLoad
{
    [super viewDidLoad];

    //Cargamos la página web en el web view
    NSURL *url = [NSURL URLWithString:self.webPage];
    NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
    [self.webView loadRequest:requestObj];
}

Y se necesita añadir dos métodos del UIWebViewDelegate:
  •  webViewDidStartLoad: indica cuando comienza a cargarse la página web. Es en ese momento cuando se debe poner la propiedad NetworkActivityIndicator a YES.
  • webViewDidFinishLoad: indica cuando termina de cargarse la página web. Se pondrá NetworkActivityIndicator a NO.

- (void)webViewDidStartLoad:(UIWebView *)webView {
    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
}

- (void)webViewDidFinishLoad:(UIWebView *)webView{
    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
}


Activity Indicator View

Añadiremos una propiedad local para el  UIActivityIndicatorView:

@interface  WebViewController ()
@property (weak, nonatomic) IBOutlet UIWebView *webView;
@property (strong, nonatomic) UIActivityIndicatorView *activityIndicator;
@end

Y voy a definir su método set así:

- (UIActivityIndicatorView *)activityIndicator
{
    if (_activityIndicator == nil) 
    {
        _activityIndicator = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(0, 0, 20, 20)];
    }
    return _activityIndicator;
}

En este caso el viewDidLoad será: 

- (void)viewDidLoad
{
    [super viewDidLoad];
//  Con esto añadimos un botón al navigation bar con el activity indicator   
    UIBarButtonItem * barButton = [[UIBarButtonItem alloc] initWithCustomView:self.activityIndicator];
   [self navigationItem].rightBarButtonItem = barButton;
    
    NSURL *url = [NSURL URLWithString:self.webPage];
    NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];

    [self.webView loadRequest:requestObj];
}

Para terminar en los métodos webViewDidStartLoad webViewDidStartLoad  empezaremos y terminaremos la animación:

- (void)webViewDidStartLoad:(UIWebView *)webView {
    [self.activityIndicator startAnimating];
}

//Called whenever the view finished loading something

- (void)webViewDidFinishLoad:(UIWebView *)webView{
   [self.activityIndicator stopAnimating];
}

El resultado de esta segunda opción será este:


En este caso el indicador de actividad se muestra en la barra del Navigation Controller.

@Fin



domingo, 27 de mayo de 2012

UIActionSheet & UIImagePickerController

Como usuarios de dispositivos iOS os habréis encontrado muchas veces con un Action Sheet. Están formadas por un título opcional y uno o varios botones, cada uno de los cuales se corresponde con una acción.
La clase UIActionSheet se  utiliza para proporcionar al usuario una serie de alternativas acerca de como proceder. También se puede utilizar para informar sobre como actuar ante una situación potencialmente peligrosa.
Vamos a ver un ejemplo muy útil de utilización de UIActionSheet, el que nos da opción de realizar una foto o elegirla de nuestro carrete, por ejemplo para asignarla a un contacto. Es decir, algo así:



Como podéis ver tenemos tres botones:
  1. Hacer Foto: nos abrirá la aplicación de la cámara para realizar una nueva fotografía.
  2. Elegir Foto: permitirá acceder al carrete para escoger una foto previamente realizada.
  3. Cancelar: no realizar ninguna acción y cerrar el action sheet.
En esta ocasión no vamos a poner ningún título.

Lo primero que tenemos que añadir cuando vamos a utilizar esta clase, es el protocolo UIActionSheetDelegate. Deberemos implementar el mensaje actionSheet:clickedButtonAtIndex: , que nos indicará que botón se ha pulsado. El índice va de 0 a n.


- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex

{
    switch (buttonIndex) {
        case 0: //cámara
            [self takePhoto:UIImagePickerControllerSourceTypeCamera];
            break;
        case 1:
            [self takePhoto:UIImagePickerControllerSourceTypeSavedPhotosAlbum];
            break;            
        default:
            break;
    }
}

En el código anterior, en función del índice, estamos enviando un mensaje pasando como parámetro UIImagePickerControllerSourceTypeCameraUIImagePickerControllerSourceTypeSavedPhotosAlbum. Estas dos variables son el tipo de fuente de una clase de la que no hemos hablado hasta ahora y que vamos a necesitar para tomar las fotos: UIImagePickerController. Esta clase se utiliza para gestionar el interface que nos proporciona el sistema para realizar fotografías o vídeos, bien sea desde la cámara del dispositivo o desde la librería de fotos y/o vídeos.
El tipo de fuente puede ser:
  • UIImagePickerControllerSourceTypeCamera: nos proporciona un interface de usuario para realizar fotos o vídeos. Es el que hemos utilizado en la opción 0 del action sheet.
  • UIImagePickerControllerSourceTypePhotoLibrary o UIImagePickerControllerSourceTypeSavedPhotosAlbum: nos proporciona un interface de usuario para elegir entre fotos o vídeos guardados. 
Añadiremos el protocolo UIImagePickerControllerDelegate. Además, para usar un image picker controller, hay que seguir estos pasos:
  • Verificar que es capaz de utilizar el tipo de fuente. Para ello se utiliza el class method isSourceTypeAvailable, que tiene como parámetro una constante de  la enumeración UIImagePickerControllerSourceType:.
  • Comprobar que tipos de medios hay disponibles para el tipo de fuente utilizado. Esto se hace con el class method availableMediaTypesForSourceType:. De esta forma podemos distinguir entre una cámara que se puede usar para vídeos y una que solo se puede usar para fotos.
  • Asignar la propiedad mediaTypes para ajustar el interfaz de usuario en función del tipo de medio.
  • Mostrar el interfaz de usuario llamando al método presentModalViewController:.
  • Cerrar el image picker cuando el usuario ha hecho la acción deseada.
Dicho esto, tenemos que ver como se ha implementado el método takePhoto.

- (void)takePhoto:(UIImagePickerControllerSourceType)sourceType
{
    if ([UIImagePickerController isSourceTypeAvailable:sourceType]) //Verificamos si el dispositivo permite el tipo de fuente
    {
        NSArray *mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:sourceType]; //Comprobamos que tipos de medios hay disponibles para el tipo de fuente utilizado
        if ([mediaTypes containsObject:(NSString *)kUTTypeImage]) //Identificador de tipos abstractos para imágenes
        {
            UIImagePickerController *imagePicherController = [[UIImagePickerController alloc] init];
            imagePicherController.delegate = self;
            imagePicherController.sourceType = sourceType;
            imagePicherController.mediaTypes = [NSArray arrayWithObject:(NSString *)kUTTypeImage];
            imagePicherController.allowsEditing = YES;
            [self presentModalViewController:imagePicherController animated:YES];
        }
    }
    else //Si el tipo de fuente es UIImagePickerControllerSourceTypeCamera y el dispositivo no tiene, se muestra una alerta
    {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Photo", "Pop Up window headerr")
                                                        message:NSLocalizedString(@"Su dispositivo no permite realizar fotos",@"Pop up window error message")
                                                       delegate:nil
                                              cancelButtonTitle:@"OK"
                                              otherButtonTitles:nil];
        [alert show]; 
    }
}

Por último, es necesario implementar dos métodos del protocolo UIImagePickerControllerDelegate: 
  • imagePickerController:didFinishPickingMediaWithInfo: para recuperar la foto
  • imagePickerControllerDidCancel: para cerrar el picker controller.


- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    UIImage *photo = [info objectForKey:UIImagePickerControllerEditedImage];
    if (!photo) photo = [info objectForKey:UIImagePickerControllerOriginalImage];

/*....Aquí haremos con la foto lo que necesitemos, guardarla en los defaults del usuario, crear un thumbnail, etc....*/

    [self dismisImagePicker];
}

- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
    [self dismisImagePicker];
}

Nada más por hoy.
@Fin

sábado, 12 de mayo de 2012

Core Data: Migraciones


Una aplicación que use Core Data, rechazará cargar datos creados con una versión antigua del modelo de datos. Por lo tanto si haces cambios en el modelo de datos se pueden dar dos situaciones: 
  1. Si la aplicación está en las primeras etapas de desarrollo: En este caso, probablemente no te importe perder los datos con los que hayas estado probando en tiempo de desarrollo. Así que puedes simplemente borrarla del dispositivo o el simulador y volverla a instalar. Cuando la vuelvas a instalar,  tendrá el nuevo modelo de datos. Pero recuerda que esto es solo una opción cuando estás en desarrollo y en la primera versión de tu App.
  2. Si has estado usando una versión del modelo por un tiempo y tienes información que no quieres perder o si ya tienes alguna versión previa en la App Store: deberás hacer una migración del modelo.

Vamos a ver como sería esta segunda opción, es decir, la migración de un modelo de datos en Core Data. 

Lo primero que se necesita es tener los dos modelos, el antiguo y el nuevo.
Para crear una nueva versión de nuestro modelo, seleccionamos Editor-->Add Model Version.


Se nos abrirá una ventana en la que debemos indicar el nombre del nuevo modelo y en que modelo está basado. Es decir, el modelo origen y el destino. 
Al terminar este paso tendremos una estructura de árbol colgando del xcdatamodel con las dos versiones. El esquema antiguo tendrá un check verde.

Si vas a hacer cambios simples en el modelo, como por ejemplo añadir nuevos Atributos a una Entidad, Core Data puede realizar una migración automática que se llama lightweight migration (migración ligera). Básicamente es una migración normal, pero en lugar de tener que proporcionar lo que se llama Mapping model, Core Data genera uno con las diferencias encontradas entre las versiones origen y destino.

Un Mapping model, concepto del que he hablado en el párrafo anterior, es una colección de objetos que especifica las transformaciones que se requieren para migrar un almacén Core Data de una versión a otra. Por ejemplo si añades una nuevo atributo, cambias de nombre una entidad, añades una nueva, etc.. Lo normal es que se realice con Xcode.

Lightweight migration

Para hacer la migración Core Data debe ser capaz de encontrar los modelos origen y destino. Además los cambios deben seguir un patrón obvio, por ejemplo, añadir o borrar un atributo, que un atributo obligatorio pasa a ser opcional, renombrar una entidad o propiedad, cambiar, añadir o renombrar relaciones,  cambiar la relación de uno a uno  a de uno a varios, etc.

Si renombras una entidad o una propiedad, se puede establecer el identificador para hacer el cambio de nombre en el modelo destino con el nombre en el modelo origen. Esto lo haremos en sus propiedades.



En el nuevo modelo que hemos creado, hacemos los cambios que necesitemos. Deberemos cambiar también las clases en función de las modificaciones hechas en el xcdatamodel. Esto quiere decir que si has cambiado la entidad Ejemplo, deberás modificar los archivos Ejemplo.h y Ejemplo.m.

Una vez hechas las modificaciones, hay que hacer un pequeño cambio en el código para aprovechar las funciones de migración. Necesitamos cambiar el código que configura  el persistent store para que habilite la migración automática. Normalmente modificaremos el metodo persistentStoreCoordinator del AppDelegate. Debemos activar los flags de migración automática, lo cual se hace en un NSDictionary.

NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                             [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];

Y estas opciones se las pasaremos a addPersistentStoreWithType:configuration:URL:options:error:. El persistentStoreCoordinator quedará tal que así:

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (__persistentStoreCoordinator != nil)
    {
        return __persistentStoreCoordinator;
    }
    
    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"MiModelo.sqlite"];
    
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                             [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
    
    NSError *error = nil;
    __persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];

    if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
    {

        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }    
    
    return __persistentStoreCoordinator;
}


Por último ponemos el nuevo modelo como current version. Nos situamos en el padre del arbol con los xcdatamodel y vamos al apartado Versioned Core Data Model del File inspector, en donde pondremos como Current el nuevo modelo.


Si ahora ejecutamos la aplicación, ya utilizará el nuevo modelo.

Manual migration

Hay ocasiones en las que no se puede llevar a cabo una lightweight migration por qué Core Data no puede entender por si solo la transformación de un modelo a otro. En estos casos se necesita definir un modelo de transformación, que es el Mapping model del que hemos hablado anteriormente.

Para crearlo añadimos un nuevo fichero en el panel Core Date-->mapping model


En la ventana siguiente seleccionamos el modelo de datos fuente. A continuación el destino. Finalmente damos un nombre al fichero y salvamos. En ese momento Xcode crea el mapping model con todas las diferencias que ha conseguido deducir entre el modelo origen y el destino.

En el proceso de migración Core Data crea dos pilas, una para los datos fuente y otra para los datos destino. Después busca los objetos de la fila fuente e inserta su correspondiente apropiado en el destino.

En este tipo de migración se utiliza una instancia de NSMigrationManager. El migration manager necesita:
  • El modelo de datos destino: se trata del persistent store coordinator.
  • El modelo de datos usado para abrir el store origen.
  • El mapping model.
Es decir, la única diferencia con la lightweight migration es la utilización del mapping model.

Si el nuevo modelo simplemente añade propiedades o entidades, no será necesario añadir más código además del que ya vimos en el caso anterior. Pero si el cambio es más complejo, será necesario crear una subclase de NSEntityMigrationPolicy. Es lo que se llama Three-Stage Migration.



@Fin



domingo, 29 de abril de 2012

Añadir eventos de calendario mediante programación: Event Kit

Para poder crear eventos a un calendario, lo primero que se necesita es añadir el framework EventKit.


En este caso lo vamos a hacer mediante programación, luego nos basta con él. Si quisiéramos utilizar  el view controller de Apple para crear eventos (EKEventViewController), también habría que añadir el framework EventKitUI.   Este view controller lo que nos proporciona es una ventana en la que introducir fecha desde, fecha hasta, repeticiones, etc. Pero, como ya os he dicho, en este caso no la vamos a utilizar, en su lugar vamos a suponer que hemos creado nosotros un view controller en el que introducir los datos necesarios.

Una vez añadido el EventKit, ya podemos crear un objeto de la clase EKEventStore que es la utilizada para manejar eventos del calendario del usuario. La inicialización de este tipo de objetos lleva un tiempo relativamente largo, así que lo que se suele hacer es inicializarlo cuando se arranca la aplicación , por ejemplo en el viewDidLoad, y utilizarlo repetidamente en lugar de inicializar uno nuevo cada vez que se necesite una tarea relacionada con un evento.

EKEventStore *eventStore = [[EKEventStore alloc] init];

Ahora tenemos que indicar en que calendario vamos a añadir los eventos. Podríamos utilizar un calendario de los que ya tiene el usuario.

Por ejemplo el calendario por defecto:

EKCalendar *calendar = [self.eventStore  defaultCalendarForNewEvents]

O cualquier otro mediante la propiedad calendars del objeto de la clase EKEventStore.
Pero vamos a crearnos uno nuevo. Primero comprobaremos si ya existe. Buscaremos  calendars del eventStore que hemos creado anteriormente. Voy a crear un método existCalendar:, que devolverá el EKCalendar si existe o nil en caso contrario.



- (EKCalendar *)existsCalendar:(NSString *)title
{
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(title == %@)", title];  
    NSArray *filtered = [self.eventStore.calendars filteredArrayUsingPredicate:predicate];
    return [filtered lastObject];
}

Si nos devuelve nil el método anterior, crearemos el calendario:

EKCalendar *calendar = [EKCalendar calendarWithEventStore:self.eventStore];
calendar.title = NSLocalizedString(@"Mi calendario", @"Calendar's name");
_calendar.CGColor = [[UIColor grayColor] CGColor];

calendar.source = [self lookForTheCalDAVSource];
NSError *error = nil;            
            
if (![_eventStore saveCalendar:_calendar commit:YES error:&error]) 
                NSLog(@"%@",error.localizedDescription);


En el método lookForTheCalDAVSource que veis en el código anterior,  buscamos el source en el que vamos a añadir el calendario. Puede ser de tipo cumpleaños, local, calDav,... Lo voy a crear de tipo CalDav. Esto son los calendarios que aparecen en la aplicación iCal del dispositivo iOS. Hay que comprobar si existe antes de añadírselo a la propiedad source de nuestro nuevo calendario.


- (EKSource *)lookForTheCalDAVSource
{
    EKSource *calSource;
    
    for (EKSource *source in self.eventStore.sources)
        if (source.sourceType == EKSourceTypeCalDAVcalSource = source;
    
    return calSource;
}

Ya solo nos queda crear el evento en nuestro nuevo calendario:

EKEvent *newEvent = [EKEvent eventWithEventStore:self.eventStore];
newEvent.title = @"Mi nuevo evento";
newEvent.startDate = [NSDate date];
newEvent.endDate = [NSDate date];
newEvent.allDay = YES;
newEvent.calendar = self.calendar;

NSError *error = nil;
[self.eventStore saveEvent:newEvent span:EKSpanThisEvent error:&error];
if (error) 
{
        NSLog(@"%@",error.description);
}




La fecha de inicio y fin es la misma ya que es un evento de un día (all day = yes). Lo lógico sería que estas fechas las cogierais de algún textfield que tuviese fechas, pero esto es solo un ejemplo.

¡Disfrutadlo!

sábado, 21 de abril de 2012

Editando un UITableView: insertar filas

Cuando utilicéis UITableView, en muchas ocasiones en necesario permitir al usuario insertar y eliminar filas.  Borrarlas es bastante sencillo, pero insertarlas es algo un poco más complejo. Os voy a mostrar un ejemplo de como se puede hacer. 



Supongamos que tenemos el típico table view controller con el botón de editar en la parte superior derecha, algo del estilo a la siguiente imagen:



Y queréis que, al pulsar el botón Edit, además de que aparezca el simbolo a la derecha para borrar filas, también os añada una nueva al final en la que podais introducir un nuevo texto. Es decir, algo así:



La plantilla que nos proporciona Apple para el UITableViewController ya viene comentado lo que necesitamos para mostrar el botón Edit.  Está en el viewDidLoad y lo único que hay que hacer es descomentarlo. Se trata de la siguiente línea:

self.navigationItem.rightBarButtonItem = self.editButtonItem;

Con esto ya se consigue que, al pulsarlo, nos muestre el símbolo de borrado en las filas existentes del Table View. Pero lo que necesitamos es añadir una fila que permita al usuario introducir nueva información.

Para ello primero vamos a implementar el método tableView:numberOfRowsInSection: del UITableViewDataSource Protocol

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

{
    if (self.editing)
        return [self.propertyListData count] + 1;
    else 
        return [self.propertyListData count];
}

Como veis, se comprueba si estamos en modo de edición y, si es así, devuelve el número de filas actuales mas una. propertyListData es en este caso un NSArray que está definido así en el interface:

@property (weak, nonatomic) NSArray *propertyListData;

También hay que añadir código al método tableView:cellForRowAtIndexPath:


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

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    
    // Configuramos la celda
    if (indexPath.row >= [self.propertyListData count]  && self.editing// Si es la nueva celda añadida y estamos en modo edición
    {

    // Creamos un campo de texto en la nueva fila
    // Estableciendo las propiedades que se necesiten
        UITextField *addTextField = [[UITextField alloc] initWithFrame:CGRectMake(10, 6, 250, 25)];
        addTextField.adjustsFontSizeToFitWidth = YES;
        addTextField.textColor = [UIColor blackColor];
        
      addTextField.placeholder = NSLocalizedString(@"Nueva fila", "New UITableView row");
        addTextField.keyboardType = UIKeyboardTypeDefault;
        addTextField.returnKeyType = UIReturnKeyDone;
        
        addTextField.backgroundColor = [UIColor whiteColor];
        addTextField.autocorrectionType = UITextAutocorrectionTypeNo
        addTextField.autocapitalizationType = UITextAutocapitalizationTypeWords
        addTextField.textAlignment = UITextAlignmentLeft;
        addTextField.tag = 0;
        addTextField.font = [UIFont systemFontOfSize:14];
        addTextField.delegate = self;        
        addTextField.clearButtonMode = UITextFieldViewModeWhileEditing;
        [addTextField setEnabled: YES];

        // Añadimos el textfield al contentView de la celda
        [cell.contentView addSubview:addTextField];
        

    }
    else 
    {
       cell.textLabel.text = [self.propertyListData objectAtIndex:indexPath.row];
    }
    
    cell.textLabel.font = [UIFont systemFontOfSize:14];

    return cell;
}



Otro método a implementar es tableView:editingStyleForRowAtIndexPath: En él se va a indicar el tipo de símbolo que vamos a añadir a la izquierda de la celda cuando esté en modo edición que, en este caso, será el de borrado o el de inserción. Si este método no se sobreescribe aparece el de borrado en todas las filas. Hay que tener en cuenta que las filas se numeran de 0 a n. Entonces, quedará así:

- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath 
{
    
    if (indexPath.row == [self.propertyListData count]) 
    {
        return UITableViewCellEditingStyleInsert;
    } 
    else 
    {
        return UITableViewCellEditingStyleDelete;
    }
    
}



Todo esto no es suficiente, también necesitamos indicarle al TableView que hay que insertar una nueva fila y se debe indicar en el momento apropiado. El método adecuado en donde hacerlo es setEditing:animated.

- (void)setEditing:(BOOL)editing animated:(BOOL)animated 
{
    [super setEditing:editing animated:animated];
//Definimos el indexpath en donde vamos a añadir la nueva fila. En este caso será la última fila

    NSIndexPath *ip = [NSIndexPath indexPathForRow:[self.propertyListData count] inSection:0];

    if (editing) //En modo edición, añadimos la fila
    {
        [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:ip]
         withRowAnimation:UITableViewRowAnimationBottom];
    } 
    else 
    {
        //Si hemos añadido datos a la nueva fila, las tendremos que añadir al modelo
        if (/*lo que sea, dependerá de vuestra codificación*/
        {

        }
        else //Y si no, la borramos
        {
            [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:ip] withRowAnimation:UITableViewRowAnimationFade];
        }   
    }   
}

Y ya lo tendríamos. Por hoy nada más.