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.

viernes, 13 de abril de 2012

Modificación de property list en iOS

Las property list se guardan en el bundle de la aplicación, que es un directorio con una estructura jerárquica estandarizada que contiene el código ejecutable y los recursos utilizados por ese código. El bundle de la aplicación se firma, luego no se puede modificar una vez firmado. Por lo tanto, a ese directorio solo se tiene acceso de lectura desde la aplicación.

Pero, ¿que pasa si necesitamos modificar una property list?. Supongamos que nuestra aplicación carga un TableView con los valores de una property list y queremos permitir al usuario añadir nuevas filas a esa tabla que se almacenarán en la property list. Pues entonces lo que deberemos hacer primero es copiar la plist al directorio Documents. Este directorio está dentro del sandbox de la aplicación y se tiene acceso de lectura y escritura a los ficheros que se encuentren allí. La copia la podemos hacer la primera vez que se lance la app, por ejemplo en el viewDidLoad. Para evitar machacar los ficheros modificados en posteriores arranques, comprobaremos primero si ya existe. A continuación os muestro una forma de hacerlo:

- (void)copyPropertyLists:(NSString *)propertyListName
{
// Recuperamos la ruta del plist que queremos copiar
    NSString *pListFile = [[NSBundle mainBundle] pathForResource:propertyListName ofType:@"plist"];
    
    if (![[NSFileManager defaultManager] fileExistsAtPath:pListFile]) 
    {
        NSLog(@"El fichero no existe");
    }
    else
    {
// Obtenemos la ruta del directorio Document de nuestra aplicación
        NSString *documentFolder = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
        
// Completamos la ruta con el nombre del nuevo fichero
        NSString *newDoseUnitsPlistFile = [documentFolder stringByAppendingPathComponent:[NSString stringWithFormat:@"%@%@", propertyListName, @".plist"]];
// Comprobamos si ya existe y, si no es así, hacemos la copia
        NSFileManager *fileManager = [NSFileManager defaultManager];
        if(![fileManager fileExistsAtPath:newDoseUnitsPlistFile])
        {
            [[NSFileManager defaultManager]copyItemAtPath:pListFile toPath:newDoseUnitsPlistFile error:nil];
        }
    }    
}

Y ahora ya podemos modificar la plist. Si, por ejemplo, fuese un array de strings: 



- (void)addString:(NSString *)data toPropertyList:(NSString *)propertyListName
{
    NSString *documentFolder = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];

    NSString *pListPath = [documentFolder stringByAppendingPathComponent:[NSString stringWithFormat:@"%@%@", propertyListName, @".plist"]];
    
    if (![[NSFileManager defaultManager] fileExistsAtPath:pListPath]) 
    {
        NSLog(@"El fichero no existe");
    }
    
// Leemos los objetos que almacena actualmente. Necesitamos que sea mutable ya que lo vamos a modificar añadiendo un nuevo elemento.
    NSMutableArray *addData = [NSMutableArray arrayWithContentsOfFile:pListPath];
// Añadimos el nuevo
    [addData addObject:data];
// Y volcamos al fichero
    [addData writeToFile:pListPath atomically:YES];
}



Espero que os sirva de ayuda.