Building iRemember – shopping list app, part 4

iRemember
We’re continuing building our iRemember application.
We’re still working on UI of the application. This time we’ll focus on displaying reminders.

Let’s start with code which refreshes reminders.

-(IBAction)refresh
{
    [self.refreshControl beginRefreshing];

    if (self.calendarIdentifier)
    {
        [[IRReminderManager defaultManager] fetchRemindersInCalendarWithIdentifier:self.calendarIdentifier 
                                                                        completion:^(NSArray *reminders) {
            dispatch_async(dispatch_get_main_queue(), ^{
                self.reminders = reminders;
                self.navigationItem.rightBarButtonItem.enabled = [IRReminderManager defaultManager].accessGranted;
                [self.tableView reloadData];
                [self.refreshControl endRefreshing];
            });
        }];
    }
    else
    {
        self.navigationItem.rightBarButtonItem.enabled = NO;
        self.reminders = nil;
        [self.tableView reloadData];
        [self.refreshControl endRefreshing];
    }
}

Reminders are fetched asynchronously, therefore we get response some time later. We’ve also added protection when calendarIdentifier property is not set. Let’s implement state restoration.

-(void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
    if (self.calendarIdentifier)
    {
        [coder encodeObject:self.calendarIdentifier forKey:kCalendarIdentifierKey];
    }
    [super encodeRestorableStateWithCoder:coder];
}

-(void)decodeRestorableStateWithCoder:(NSCoder *)coder
{
    [super decodeRestorableStateWithCoder:coder];
    
    self.calendarIdentifier = [coder decodeObjectForKey:kCalendarIdentifierKey];
}

+ (UIViewController *) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
    UIStoryboard *storyboard = [coder decodeObjectForKey:UIStateRestorationViewControllerStoryboardKey];
    NSString *calendarIdentifier = [coder decodeObjectForKey:kCalendarIdentifierKey];

    if ([[IRReminderManager defaultManager] isCalendarIdentifierValid:calendarIdentifier])
    { 
        IRRemindersViewController *vc = [storyboard instantiateViewControllerWithIdentifier:@"IRRemindersViewController"];
        vc.calendarIdentifier = calendarIdentifier;
        return vc;
    }
    else
    {
        return nil;
    }
}

Here we have class method +viewControllerWithRestorationIdentifierPath:coder: which validates that saved calendarIdentifier is still exists. If calendar with such identifier was deleted, we’re not restoring this view controller and we’ll return to previous one in navigation (that would be IRCalendarsViewController).

There are also simple (and quite ugly) UI to add reminder. We’re using UIAlertView with text field.

-(IBAction)addReminder
{
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Add reminder", @"Add reminder title")
                                                        message:nil
                                                       delegate:self
                                              cancelButtonTitle:NSLocalizedString(@"Cancel", @"Cancel button")
                                              otherButtonTitles:NSLocalizedString(@"Add", @"Add button"), nil];
    alertView.alertViewStyle = UIAlertViewStylePlainTextInput;
    UITextField *textField = [alertView textFieldAtIndex:0];
    textField.placeholder = NSLocalizedString(@"Item title", @"Item title placeholder");
    textField.autocapitalizationType = UITextAutocapitalizationTypeSentences;
    textField.autocorrectionType = UITextAutocorrectionTypeYes;
    [alertView show];
}

-(void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
    if (buttonIndex != alertView.cancelButtonIndex)
    {
        NSString *title = [alertView textFieldAtIndex:0].text;
        [[IRReminderManager defaultManager] addReminderWithTitle:title 
                                        inCalendarWithIdentifier:self.calendarIdentifier 
                                                      completion:^(EKReminder *reminder) {
            if (reminder)
            {
                self.reminders = [self.reminders arrayByAddingObject:reminder];
                dispatch_async(dispatch_get_main_queue(), ^{
                    [self.tableView insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:(self.reminders.count - 1) 
                                                                                inSection:0]]
                                          withRowAnimation:UITableViewRowAnimationAutomatic];
                });
            }
        }];
    }
}

Also, we can mark reminder as completed (and display it accordingly). Note that we’re using attributedText property of the label to display reminder label. Also note that the next time we update list of reminders, completed reminders will go away.

- (UITableViewCell *)tableView:(UITableView *)tableView 
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"ReminderCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier 
                                                            forIndexPath:indexPath];
    
    EKReminder *reminder = self.reminders[indexPath.row];
    
    NSDictionary *attributes = reminder.completed ? @{ NSStrikethroughStyleAttributeName : @YES } : @{};
    cell.textLabel.attributedText = [[NSAttributedString alloc] initWithString:reminder.title 
                                                                    attributes:attributes];
    
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    EKReminder *reminder = self.reminders[indexPath.row];
    reminder.completed = !reminder.completed;
    [tableView reloadRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationAutomatic];
    [[IRReminderManager defaultManager] saveReminder:reminder 
                                      withCompletion:^(BOOL result) {
        if (!result)
        {
#warning Signal if save fails
        }
    }];
}

And last and very simple thing – we allow to delete reminder.

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
    return YES;
}

- (void)tableView:(UITableView *)tableView 
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle 
forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (editingStyle == UITableViewCellEditingStyleDelete)
    {
        EKReminder *reminder = self.reminders[indexPath.row];
        [[IRReminderManager defaultManager] removeReminder:reminder 
                                            withCompletion:^(BOOL result) {
            dispatch_async(dispatch_get_main_queue(), ^{
                if (result)
                {
                    NSMutableArray *reminders = [self.reminders mutableCopy];
                    [reminders removeObjectAtIndex:indexPath.row];
                    self.reminders = [reminders copy];
                    [tableView deleteRowsAtIndexPaths:@[indexPath] 
                                     withRowAnimation:UITableViewRowAnimationFade];
                }
#warning Signal if delete fails
            });
        }];
        
    }
}

So. Let’s summarize what we’ve accomplished so far.

  1. Our application can manage reminders, grouped in lists.
  2. Application preserves its state during relaunches.
  3. Application uses system-provided framework EventKit to retrieve and store reminders. These reminders could be shared with other users (for example, by using iCloud website).

We’ll probably return to this application soon to make it look nice (UI is very important, especially for mobile applications). But as of now, it achieves our goals set in first part.
We’ll also discuss localization of the application. We’ve already coded it for future localization.

For more information on EventKit, consult Apple documentation – Calendar and Reminders Programming Guide, Event Kit Framework Reference, State preservation guide.
Source code for this tutorial is available on GitHub.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s