Chapter    11

User Data Recipes

No two people are alike, and, in the same way, no two iOS devices are alike, as the information that one device stores is dependent on the person who uses it. We populate our devices with our lives, including our photos, calendars, notes, contacts, and music. As developers, it is important to be able to access all of this information regardless of the device, so that we may incorporate it into our applications and provide a more unique, user-specific interface. In this chapter, we will cover a variety of methods for dealing with user-based data, dealing first with the calendar, and then with the address book.

Recipe 11-1: Working with NSCalendar and NSDate

Many different applications are often used for time- and date-based calculations. This could be anything from converting calendars, to sorting to-do lists, to telling the user how much time remains before an alarm will go off. To use the more intricate event-based user interface, you must have a solid understanding of the simpler NSDate-focused APIs. Here, you implement a simple application to illustrate the use of the NSDate, NSCalendar, and NSDateComponents classes by converting dates from the Gregorian calendar to the Hebrew calendar.

Create a new single-view application project. Switch over to the view controller’s .xib file and build a user interface that resembles Figure 11-1.

9781430245995_Fig11-01.jpg

Figure 11-1.  User interface for calendar conversion

You need to set up properties to represent each UITextField. Create the following outlets:

  • gMonthTextField
  • gDayTextField
  • gYearTextField
  • hMonthTextField
  • hDayTextField
  • hYearTextField

Note  The “G” and “H” in these property names refer to whether the given UITextField is on the Gregorian or Hebrew side of the application.

You do not need outlets for the buttons, but create the following actions for when the user taps them:

  • convertToHebrew
  • convertToGregorian

To control the UITextFields programmatically, you need to make your view controller the delegate for them. First, add <UITextFieldDelegate> to your controller’s header line, so that it now looks like so:

//
//  ViewController.h
//  Recipe 11.1: Working With NSCalendar and NSDate
//

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController<UITextFieldDelegate>

@property (weak, nonatomic) IBOutlet UITextField *gMonthTextField;
@property (weak, nonatomic) IBOutlet UITextField *gDayTextField;
@property (weak, nonatomic) IBOutlet UITextField *gYearTextField;
@property (weak, nonatomic) IBOutlet UITextField *hMonthTextField;
@property (weak, nonatomic) IBOutlet UITextField *hDayTextField;
@property (weak, nonatomic) IBOutlet UITextField *hYearTextField;

- (IBAction)convertToHebrew:(id)sender;
- (IBAction)convertToGregorian:(id)sender;

@end

Next, set all the UITextField delegates to your view controller by adding the following code to the viewDidLoad method:

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.gMonthTextField.delegate = self;
    self.gDayTextField.delegate = self;
    self.gYearTextField.delegate = self;
    self.hMonthTextField.delegate = self;
    self.hDayTextField.delegate = self;
    self.hYearTextField.delegate = self;
}

Next, define the UITextFieldDelegate method textFieldShouldReturn: to properly dismiss the keyboard.

-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
    [textField resignFirstResponder];
    return NO;
}

The first new class that you see here is the NSCalendar class. This is essentially used to set a standard for the dates that you will later refer to. The NSCalendar method also allows you to perform several useful functions dealing with a calendar, such as changing which day a week starts on, or changing the time zone used. The NSCalendar class also acts as a bridge between the NSDate and NSDateComponents classes that you will see later.

You use two instances of the NSCalendar class to translate dates between the Gregorian calendar and the Hebrew calendar. Add these properties of your class:

@property (nonatomic, strong) NSCalendar *gregorianCalendar;
@property (nonatomic, strong) NSCalendar *hebrewCalendar;

You use lazy initialization for these properties, so add the following custom getter implementations:

-(NSCalendar *)gregorianCalendar
{
    if (!_gregorianCalendar)
    {
        _gregorianCalendar =
            [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
    }
    return _gregorianCalendar;
}

-(NSCalendar *)hebrewCalendar
{
    if (!_hebrewCalendar)
    {
        _hebrewCalendar =
             [[NSCalendar alloc] initWithCalendarIdentifier:NSHebrewCalendar];
    }
    return _hebrewCalendar;
}

These method overrides are necessary to make sure that your calendars are initialized with their correct calendar types. Alternatively, you could simply initialize your calendars in the viewDidLoad method to be created when the app launches.

Note  There are a large variety of different calendar types that are available for use with the NSCalendar class, including NSBuddhistCalendar, NSIslamicCalendar and NSJapaneseCalendar.

Given the immense multicultural nature of today’s technological world, you may find it quite necessary to make use of some of these calendars! Consult the Apple documentation for a full list of possible calendar types.

Now that your setup is done, you can implement your conversion method, starting with the conversion from Gregorian to Hebrew.

- (IBAction)convertToGregorian:(id)sender
{
    NSDateComponents *hComponents = [[NSDateComponents alloc] init];
    [hComponents setDay:[self.hDayTextField.text integerValue]];
    [hComponents setMonth:[self.hMonthTextField.text integerValue]];
    [hComponents setYear:[self.hYearTextField.text integerValue]];

    NSDate *hebrewDate = [self.hebrewCalendar dateFromComponents:hComponents];

    NSUInteger unitFlags =
        NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit;

    NSDateComponents *hebrewDateComponents =
        [self.gregorianCalendar components:unitFlags fromDate:hebrewDate];

    self.gDayTextField.text =
        [[NSNumber numberWithInteger:hebrewDateComponents.day] stringValue];
    self.gMonthTextField.text =
        [[NSNumber numberWithInteger:hebrewDateComponents.month] stringValue];
    self.gYearTextField.text =
        [[NSNumber numberWithInteger:hebrewDateComponents.year] stringValue];
}

As you can see, you are using a combination of NSDateComponents, NSDate, and NSCalendar to perform this conversion.

The NSDateComponents class is used to define the details that make up an NSDate, such as the day, month, year, time, and so on. Here, only the month, day, and year are being used.

As mentioned earlier, you use an instance of the NSCalendar to create an instance of NSDate out of the components that you have defined.

One of the more confusing parts of the above method may be the use of the NSUInteger unitFlags, which is formatted quite unusually. Whenever you specify creating an instance of NSDateComponents out of an NSDate, you need to specify exactly which components to include from the date. You can specify these flags, called NSCalendarUnits, through the use of the NSUInteger, as shown.

Other types of NSCalendarUnits include the following, among many others:

  • NSSecondCalendarUnit
  • NSWeekOfYearCalendarUnit
  • NSEraCalendarUnit
  • NSTimeZoneCalendarUnit

As you can see, the specificity with which you can create instances of NSDate is highly customizable, allowing you to perform unique calculations and comparisons. For a full list of NSCalendarUnit values, refer to the NSCalendar class reference in Apple’s developer API.

Because the values of NSDateComponents are of type NSInteger, you must first convert them to instances of NSNumber, and then take their stringValue before you set them into your text fields.

Once you have defined your conversion from one calendar to the other, the reverse is simple, as you just need to change which text fields and calendar you use.

- (IBAction)convertToHebrew:(id)sender
{
    NSDateComponents *gComponents = [[NSDateComponents alloc] init];
    [gComponents setDay:[self.gDayTextField.text integerValue]];
    [gComponents setMonth:[self.gMonthTextField.text integerValue]];
    [gComponents setYear:[self.gYearTextField.text integerValue]];

    NSDate *gregorianDate = [self.gregorianCalendar dateFromComponents:gComponents];

    NSUInteger unitFlags =
        NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit;

    NSDateComponents *hebrewDateComponents =
        [self.hebrewCalendar components:unitFlags fromDate:gregorianDate];

    self.hDayTextField.text =
        [[NSNumber numberWithInteger:hebrewDateComponents.day] stringValue];
    self.hMonthTextField.text =
        [[NSNumber numberWithInteger:hebrewDateComponents.month] stringValue];
    self.hYearTextField.text =
        [[NSNumber numberWithInteger:hebrewDateComponents.year] stringValue];
}

Your application can now correctly convert instances of NSDate between calendars, as shown in Figure 11-2. Try experimenting with different dates or even different calendars to see what kinds of powerful date conversions you can do.

9781430245995_Fig11-02.jpg

Figure 11-2.  An app that converts between Gregorian and Hebrew dates

Recipe 11-2: Fetching Calendar Events

Now that you have covered how to deal with basic date conversions and calculations, you can go into details on dealing with events and calendars, interacting with the user’s own events and schedule. The next few recipes all compound to create a complete utilization of the Event Kit framework.

First, create a new single-view application project with the name “My Events App.” This time you use the Event Kit framework, so go ahead and link EventKit.framework to your newly created project.

Because this app is going to access the device’s calendars, you should provide a usage description in the project’s Info.plist file. Add the “Privacy – Calendars Usage Description” key to the Information Property List and enter the text Testing Calendar Events, as Figure 11-3 shows.

9781430245995_Fig11-03.jpg

Figure 11-3.  An app providing a calendar usage description in the Information Property List

Whenever you’re dealing with the Event Kit framework, the main element you work with is an EKEventStore. This class allows you to access, delete, and save events in your calendars. An EKEventStore takes a relatively long time to initialize, so you should do it only once and store it in a property. Add the following declarations to the ViewController.h file:

//
//  ViewController.h
//  My Events App
//

#import <UIKit/UIKit.h>
#import <EventKit/EventKit.h>

@interface ViewController : UIViewController

@property (strong, nonatomic) EKEventStore *eventStore;

@end

The implementation for this first recipe is a simple logging of all calendar events within 48 hours from now. You’re not going to build a user interface at this point, instead all the relevant code will reside in the viewDidLoad method. We’ll go through the steps first and then show you the complete implementation.

The first thing you need to do when you want to access the device’s calendar entries is to ask the user for permission to do so. dodo this using the requestAccessToEntityType:completion: method of EKEventStore, passing a code block that will be invoked when the asynchronous process is done:

self.eventStore = [[EKEventStore alloc] init];

[self.eventStore requestAccessToEntityType:EKEntityTypeEvent
completion:^(BOOL granted, NSError *error)
{
    if (granted)
    {
        //...
    }
    else
    {
        NSLog(@"Access not granted: %@", error);
    }
}];

If access was indeed granted, you can go ahead and retrieve the information you want. In this case you’ll fetch all the calendar events from between now and 48 hours from now. First, create the two dates:

NSDate *now = [NSDate date];

NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *fortyEightHoursFromNowComponents = [[NSDateComponents alloc] init];
fortyEightHoursFromNowComponents.day = 2; // 48 hours forward
NSDate *fortyEightHoursFromNow =
    [calendar dateByAddingComponents:fortyEightHoursFromNowComponents toDate:now
        options:0];

Note  As you can see, you use NSCalendar to help create the future date. This ensures a more accurate time than if you use NSDate’s method dateWithTimeIntervalSinceNow:. The reason it’s more accurate is that NSCalendar takes into account the fact that not all days in a year are exactly 24 hours long. Although the difference in this case is insignificant, it’s considered good practice to use the dateByAddingComponents:toDate: method to construct relative dates.

Now that you have the start date and the end date, you can create a search predicate for finding the events within that interval by using the predicateForEventsWithStartDate:endDate:calendars: method on the event store. By passing a value of nil to the calendars parameter of this method, you specify that you want your predicate to be applied to all calendars.

NSPredicate *allEventsWithin48HoursPredicate =
    [self.eventStore predicateForEventsWithStartDate:now endDate:fortyEightHoursFromNow
        calendars:nil];

You then use the predicate to retrieve the actual events from the event store:

NSArray *events =
    [self.eventStore eventsMatchingPredicate:allEventsWithin48HoursPredicate];

Finally, you’ll just iterate over the retrieved events and print their titles to the debug log:

for (EKEvent *event in events)
{
    NSLog(@"%@", event.title);
}

Here’s the complete implementation of the viewDidLoad method:

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.eventStore = [[EKEventStore alloc] init];

    [self.eventStore requestAccessToEntityType:EKEntityTypeEvent
     completion:^(BOOL granted, NSError *error)
     {
         if (granted)
         {
             NSDate *now = [NSDate date];

             NSCalendar *calendar = [NSCalendar currentCalendar];
             NSDateComponents *fortyEightHoursFromNowComponents =
                 [[NSDateComponents alloc] init];
             fortyEightHoursFromNowComponents.day = 2; // 48 hours forward
             NSDate *fortyEightHoursFromNow =
                 [calendar dateByAddingComponents:fortyEightHoursFromNowComponents
                     toDate:now options:0];

             NSPredicate *allEventsWithin48HoursPredicate =
                 [self.eventStore predicateForEventsWithStartDate:now
                     endDate:fortyEightHoursFromNow calendars:nil];
             NSArray *events = [self.eventStore
                 eventsMatchingPredicate:allEventsWithin48HoursPredicate];
             for (EKEvent *event in events)
             {
                 NSLog(@"%@", event.title);
             }
         }
         else
         {
             NSLog(@"Access not granted: %@", error);
         }
     }];
}

Because the iOS Simulator doesn’t have calendar support, you need to test your app on a real device. Be sure that the device has events scheduled to serve as your test data. Because the only output you are creating here is in the log, you also need to run the application from Xcode, so as to capture the output. See Figure 11-4 for an example of such output.

9781430245995_Fig11-04.jpg

Figure 11-4.  Output log for the application, showing the names of nearby events

The first time you run this app you’ll get an alert asking if your app should be allowed to access your calendar (see Figure 11-5). This is a part of the new privacy policy implemented in iOS 6. Because the calendar can contain private information that may sensitive, apps must now ask the user’s explicit permission before accessing it.

9781430245995_Fig11-05.jpg

Figure 11-5.  An alert asking for the user’s permission to access the calendar

The user is only asked once to grant an app access to the calendar. iOS remembers the user’s answer on subsequent runs. If the user wants to change the current access setting, she can do that in the Settings app, under Privacy image Calendars (see Figure 11-6).

9781430245995_Fig11-06.jpg

Figure 11-6.  The privacy settings showing an app that’s currently granted access to the device’s calendar

Tip  Sometimes as a developer of these restricted features, you want to reset the privacy settings to test the initial run scenario again. You can reset these settings in the Settings app, under General image Reset using the Reset Location & Privacy option.

Recipe 11-3: Displaying Events in a Table View

Now that you can access your events, continue by creating a better interface with which to deal with them. In this recipe, you implement a grouped UITableView to display your events.

You start by turning the project into a navigation based application. Declare the UINavigationController property in the AppDelegate.h file:

//
//  AppDelegate.h
//  My Events App
//

#import <UIKit/UIKit.h>

@class ViewController;

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@property (strong, nonatomic) ViewController *viewController;
@property (strong, nonatomic) UINavigationController *navigationController;

@end

Now, make the following changes to the application:didFinishLaunchingWithOptions: method in the AppDelegate.m file:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.viewController =
        [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];
    self.navigationController =
        [[UINavigationController alloc] initWithRootViewController:self.viewController];
    self.window.rootViewController =self.navigationController;
    [self.window makeKeyAndVisible];
    return YES;
}

Next, you set up the user interface consisting of a UITableView. Select the ViewController.xib file in the Project Navigator to bring up Interface Builder. To be sure you take into account the navigation bar when you design your user interface, you should set the corresponding Simulated Metrics attribute in the Attributes inspector. Select the main view and go to the Attributes inspector. There, in the Simulated Metrics section, change the value of the Top Bar attribute to Navigation Bar, as shown in Figure 11-7.

9781430245995_Fig11-07.jpg

Figure 11-7.  Setting the Top Bar simulated metrics to Navigation Bar

Now, you can add the table view and make it take up the remaining parts of the view. In the Attributes inspector for the table view, be sure that the Style attribute is set to Grouped. Your main view should now resemble Figure 11-8.

9781430245995_Fig11-08.jpg

Figure 11-8.  A user interface with a navigation bar and a grouped table view

Next, create an outlet for your UITableView. Name the outlet eventsTableView.

Before you switch over to your implementation file, you will need to make some additional changes to the header file. First, add the UITableViewDelegate and the UITableViewDataSource protocols to the class. Also define two new properties, an NSArray, which is used to hold references to all the calendars in the EKEventStore, and an NSMutableDictionary that is used to store all your events based on which calendar they belong to. The ViewController.h file should now resemble the following code:

//
//  ViewController.h
//  My Events App
//

#import <UIKit/UIKit.h>
#import <EventKit/EventKit.h>

@interface ViewController : UIViewController<UITableViewDelegate, UITableViewDataSource>

@property (strong, nonatomic) EKEventStore *eventStore;
@property (weak, nonatomic) IBOutlet UITableView *eventsTableView;
@property (nonatomic, strong) NSMutableDictionary *events;
@property (nonatomic, strong) NSArray *calendars;

@end

The next thing you need to do is to modify the viewDidLoad method. Specifically, you do these things:

  • Set the title displayed in the navigation bar.
  • Add a refresh button to the navigation bar.
  • Set up the table view’s two delegate methods.
  • Populate the calendars array and the events dictionary.

To accomplish these things, make the following changes to the viewDidLoad method:

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.title = @"Events";

    UIBarButtonItem *refreshButton = [[UIBarButtonItem alloc]
        initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self
        action:@selector(refresh:)];
    self.navigationItem.leftBarButtonItem = refreshButton;

    self.eventsTableView.delegate = self;
    self.eventsTableView.dataSource = self;

    self.eventStore = [[EKEventStore alloc] init];

    [self.eventStore requestAccessToEntityType:EKEntityTypeEvent
     completion:^(BOOL granted, NSError *error)
     {
         if (granted)
         {
             self.calendars =
                 [self.eventStore calendarsForEntityType:EKEntityTypeEvent];
             [self fetchEvents];
         }
         else
         {
             NSLog(@"Access not granted: %@", error);
         }
     }];
}

There are two methods that the preceding code uses that you haven’t implemented yet. The first is the refresh: action method that will be invoked when the user taps the Refresh button on the navigation bar. Go ahead and add the following code to the view controller class:

- (void)refresh:(id)sender
{
    [self fetchEvents];
    [self.eventsTableView reloadData];
}

The second unimplemented method is fetchEvent, which contains the code to actually query the eventStore for the events. Because you sort your events by the calendar they belong to, you perform a different query for each calendar, rather than just one for all events:

- (void)fetchEvents
{
    self.events = [[NSMutableDictionary alloc] initWithCapacity:[self.calendars count]];

    NSDate *now = [NSDate date];

    NSCalendar *calendar = [NSCalendar currentCalendar];
    NSDateComponents *fortyEightHoursFromNowComponents =
        [[NSDateComponents alloc] init];
    fortyEightHoursFromNowComponents.day = 2; // 48 hours forward
    NSDate *fortyEightHoursFromNow =
        [calendar dateByAddingComponents:fortyEightHoursFromNowComponents toDate:now
            options:0];

    for (EKCalendar *calendar in self.calendars)
    {
        NSPredicate *allEventsWithin48HoursPredicate =
            [self.eventStore predicateForEventsWithStartDate:now
                endDate:fortyEightHoursFromNow calendars:@[calendar]];
        NSArray *eventsInThisCalendar =
            [self.eventStore eventsMatchingPredicate:allEventsWithin48HoursPredicate];
        if (eventsInThisCalendar != nil)
        {
            [self.events setObject:eventsInThisCalendar forKey:calendar.title];
        }
    }

    dispatch_async(dispatch_get_main_queue(),^{
        [self.eventsTableView reloadData];
    });
}

You should recognize most of the preceding code from the previous recipe. The main difference is that you now perform a search for each individual calendar and store the results in the events dictionary using the respective calendar names as keys.

Also, when all the fetching is done you’re notifying the table view that its data has changed. However, because the fetchEvents is invoked on an arbitrary thread and any user interface−related code must be run on the main thread, you need to dispatch that particular piece of code to make it run in the main thread.

With the data model in place, you can move your attention over to the table view and its implementation. But before you do that you’ll add a few helper methods. These methods are quite small and simple in nature, but they help make the code you’ll add in a minute easier to read. So add the following code to the ViewController.m file:

- (EKCalendar *)calendarAtSection:(NSInteger)section
{
    return [self.calendars objectAtIndex:section];
}

- (EKEvent *)eventAtIndexPath:(NSIndexPath *)indexPath
{
    EKCalendar *calendar = [self calendarAtSection:indexPath.section];
    NSArray *calendarEvents = [self eventsForCalendar:calendar];
    return [calendarEvents objectAtIndex:indexPath.row];
}

- (NSArray *)eventsForCalendar:(EKCalendar *)calendar
{
    return [self.events objectForKey:calendar.title];
}

Now, implement a method to specify the number of sections it should display. You have one section per calendar so this method is nice and easy:

-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return [self.calendars count];
}

You can also implement a method to specify your section titles:

-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
    return [self calendarAtSection:section].title;
}

You also need to implement a method to determine the number of rows in each group, as given by the count of the array returned by your dictionary for a given section:

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    EKCalendar *calendar = [self calendarAtSection:section];
    return [self eventsForCalendar:calendar].count;
}

Finally, add the method that defines how your table’s cells are created:

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

    UITableViewCell *cell =
        [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil)
    {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1
            reuseIdentifier:CellIdentifier];
    }

    cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
    cell.textLabel.backgroundColor = [UIColor clearColor];
    cell.textLabel.font = [UIFont systemFontOfSize:19.0];

    cell.textLabel.text = [self eventAtIndexPath:indexPath].title;

    return cell;
}

As Figure 11-9 shows, your application can now display all calendar events that occur within 48 hours.

9781430245995_Fig11-09.jpg

Figure 11-9.  A simple app that displays calendar events within two days from now

Recipe 11-4: Viewing, Editing, and Deleting Events

The next step is to look into how to allow the user to view, edit, and delete events through pre-defined classes in the Event Kit UI framework.

You’ll continue adding to the same project that you’ve been using since Recipe 11-2. This time you’re going to make use of a couple of predefined user interfaces for viewing and editing calendar events. Specifically, you’ll be utilizing the EKEventViewController and EKEventViewEditController classes. These are a part of the Event Kit UI framework, so go ahead and add EventKitUI.framework to the project.

You also need to import its API into the main view controller’s header file:

//
//  ViewController.h
//  Calendar Events
//

#import <UIKit/UIKit.h>
#import <EventKit/EventKit.h>
#import <EventKitUI/EventKitUI.h>

@interface ViewController : UIViewController<UITableViewDelegate,
                                             UITableViewDataSource>

@property (strong, nonatomic) EKEventStore *eventStore;
@property (weak, nonatomic) IBOutlet UITableView *eventsTableView;
@property (nonatomic, strong) NSMutableDictionary *events;
@property (nonatomic, strong) NSArray *calendars;

@end

The next thing you do is to implement behavior for when a user selects a specific row in your table view. You use an instance of the EKEventViewController to display information on the selected event. To do this, add the following tableView:DidSelectRowAtIndexPath: data source method:

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    EKEventViewController *eventVC = [[EKEventViewController alloc] init];
    eventVC.event = [self eventAtIndexPath:indexPath];
    eventVC.allowsEditing = YES;
    [self.navigationController pushViewController:eventVC animated:YES];
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}

Now, if the user has edited or removed the selected event from within the event view controller, you’ll need to update the table view somehow. The easiest way to do this is to refresh it in the main view controller’s viewDidLoad method, like so:

- (void)viewWillAppear:(BOOL)animated
{
    [self refresh:self];
    [super viewWillAppear:animated];
}

For extra functionality, make your cell’s detail disclosure buttons allow the user to proceed directly to editing mode through the use of the EKEventEditViewController. The EKEventEditViewController requires you to assign a delegate to handle its dismissal so start by adding the EKEventEditViewDelegate protocol to the main view controller’s class declaration:

//
//  ViewController.h
//  Calendar Events
//

#import <UIKit/UIKit.h>
#import <EventKit/EventKit.h>
#import <EventKitUI/EventKitUI.h>

@interface ViewController : UIViewController<UITableViewDelegate, UITableViewDataSource,
    EKEventEditViewDelegate>

@property (strong, nonatomic) EKEventStore *eventStore;
@property (weak, nonatomic) IBOutlet UITableView *eventsTableView;
@property (nonatomic, strong) NSMutableDictionary *events;
@property (nonatomic, strong) NSArray *calendars;

@end

Then, implement a method to handle the tapping of the disclosure buttons:

-(void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
{
    EKEventEditViewController *eventEditVC = [[EKEventEditViewController alloc] init];
    eventEditVC.event = [self eventAtIndexPath:indexPath];
    eventEditVC.eventStore = self.eventStore;
    eventEditVC.editViewDelegate = self;
    [self presentViewController:eventEditVC animated:YES completion:nil];
}

Finally, implement the eventEditViewController:didCompleteWithAction: delegate method to dismiss the edit view controller:

-(void)eventEditViewController:(EKEventEditViewController *)controller didCompleteWithAction:(EKEventEditViewAction)action
{
    [self dismissViewControllerAnimated:YES completion:nil];
}

Before you’re done, you need to implement one last delegate method to specify the default calendar that will be used for the creation of new events. In this implementation, you simply return the default calendar of the device:

-(EKCalendar *)eventEditViewControllerDefaultCalendarForNewEvents:
(EKEventEditViewController *)controller
{
    return [self.eventStore defaultCalendarForNewEvents];
}

At this point, your application now allows the user to view and edit the details of an event in two different ways, through the use of either an EKEventViewController or an EKEventEditViewController. An example of the user interfaces of these view controllers can be seen in Figure 11-10.

9781430245995_Fig11-10.jpg

Figure 11-10.  The user interfaces of an EKEventViewController and an EKEventEditViewController, respectively

Recipe 11-5: Creating Calendar Events

While it is fairly simple to allow users to create a calendar event by themselves, we, as developers, should always strive to simplify even the simple things. The less that users have to do on their own, the happier they tend to be with the final product. To this end, it is important to be able to create and edit events programmatically, at the tap of a button.

Again, you’ll continue adding to the project you created in Recipe 11-2. The first thing is to add an Add button to the navigation bar. Go to the viewDidLoad method in ViewController.m and add the following code:

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.title = @"Events";

    UIBarButtonItem *refreshButton = [[UIBarButtonItem alloc]
        initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self
        action:@selector(refresh:)];
    self.navigationItem.leftBarButtonItem = refreshButton;

    UIBarButtonItem *addButton = [[UIBarButtonItem alloc]
        initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self
        action:@selector(addEvent:)];
    self.navigationItem.rightBarButtonItem = addButton;

    // ...

}

When the user taps the Add button, the app will create a new event in the device’s default calendar. To make things simple in this recipe, you ask the user to enter a title for the event using an alert view. To implement that, start by adding the UIAlertViewDelegate protocol to the main view controller’s header declaration:

@interface ViewController : UIViewController<UITableViewDelegate, UITableViewDataSource,
    EKEventEditViewDelegate,UIAlertViewDelegate>

Then add the action method that presents the alert view:

- (void)addEvent:(id)sender
{
    UIAlertView * inputAlert = [[UIAlertView alloc] initWithTitle:@"New Event"
        message:@"Enter a title for the event" delegate:self cancelButtonTitle:@"Cancel"
        otherButtonTitles:@"OK", nil];
    inputAlert.alertViewStyle = UIAlertViewStylePlainTextInput;
    [inputAlert show];
}

Finally, add the delegate method that will be invoked when the user has tapped a button in the alert view:

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (buttonIndex == 1)
    {
        // OK button tapped

        // Calculate the date exactly one day from now
        NSCalendar *calendar = [NSCalendar currentCalendar];
        NSDateComponents *aDayFromNowComponents = [[NSDateComponents alloc] init];
        aDayFromNowComponents.day = 1;
        NSDate *now = [NSDate date];
        NSDate *aDayFromNow = [calendar dateByAddingComponents:aDayFromNowComponents
            toDate:now options:0];

        // Create the event
        EKEvent *event = [EKEvent eventWithEventStore:self.eventStore];
        event.title = [alertView textFieldAtIndex:0].text;
        event.calendar = [self.eventStore defaultCalendarForNewEvents];
        event.startDate = aDayFromNow;
        event.endDate = [NSDate dateWithTimeInterval:60*60.0 sinceDate:event.startDate];

        // Save the event and update the table view
        [self.eventStore saveEvent:event span:EKSpanThisEvent error:nil];
        [self refresh:self];
    }
}

For the sake of demonstration we have chosen a very simple method for creating these new events (see Figure 11-11). They are all set up a day in advance and last an hour. Most likely in your application you would choose a more complex or user-input-based method for creating EKEvents.

9781430245995_Fig11-11.jpg

Figure 11-11.  A simple user interface to add new calendar events

Creating Recurring Events

The Event Kit framework provides a powerful API for working with recurring events. To see how it works you’re going to change the event that this app creates and make it recur. Let’s add the code first and look at how it works later:

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (buttonIndex == 1)
    {
        // OK button tapped

        // Calculate the date exactly one day from now
        NSCalendar *calendar = [NSCalendar currentCalendar];
        NSDateComponents *aDayFromNowComponents = [[NSDateComponents alloc] init];
        aDayFromNowComponents.day = 1;
        NSDate *now = [NSDate date];
        NSDate *aDayFromNow = [calendar dateByAddingComponents:aDayFromNowComponents
            toDate:now options:0];

        // Create the event
        EKEvent *event = [EKEvent eventWithEventStore:self.eventStore];
        event.title = [alertView textFieldAtIndex:0].text;
        event.calendar = [self.eventStore defaultCalendarForNewEvents];
        event.startDate = aDayFromNow;
        event.endDate = [NSDate dateWithTimeInterval:60*60.0 sinceDate:event.startDate];

        // Make it recur
        EKRecurrenceRule *repeatEverySecondWednesdayRecurrenceRule =
        [[EKRecurrenceRule alloc] initRecurrenceWithFrequency:EKRecurrenceFrequencyDaily
            interval:2
            daysOfTheWeek:@[[EKRecurrenceDayOfWeek dayOfWeek:4]]
            daysOfTheMonth:nil
            monthsOfTheYear:nil
            weeksOfTheYear:nil
            daysOfTheYear:nil
            setPositions:nil
            end:[EKRecurrenceEnd recurrenceEndWithOccurrenceCount:20]];

        event.recurrenceRules = @[repeatEverySecondWednesdayRecurrenceRule];

        // Save the event and update the table view
        [self.eventStore saveEvent:event span:EKSpanThisEvent error:nil];
        [self refresh:self];
    }
}

As you can see, you create an instance of the class EKRecurrenceRule to provide to your event. This class is an incredibly flexible method with which to programmatically implement recurrent events. With only one method, a developer can create nearly any combination of recurrences imaginable. The function of each parameter of this method is listed as follows. For any parameter, passing a value of nil indicates a lack of restriction.

  • initRecurrenceWithFrequency: Specifies a basic level of how often the event repeats, whether on a daily, weekly, monthly, or annual basis.
  • interval: Specifies the interval of repetition based on the frequency. A recurring event with a weekly frequency and an interval of 3 repeats every three weeks.
  • daysOfTheWeek: Takes an NSArray of objects that must be accessed through the EKRecurrenceDayOfWeek dayOfWeek method, which takes an integer parameter representing the day of the week, starting with 1 referring to Sunday. By setting this parameter, a developer can create an event to repeat every few days, but only if the event falls on specified days of the week.
  • daysOfTheMonth: Similar to daysOfTheWeek. Specifies which days in a month to restrict a recurring event to. It is only valid for events with monthly frequency.
  • monthsOfTheYear: Similar to daysOfTheWeek and daysOfTheMonth, valid only for events with a yearly frequency.
  • weeksOfTheYear: Just like monthsOfTheYear, this is restricted only to events with an annual frequency, but with specific weeks to restrict instead of months.
  • daysOfTheYear: Another parameter restricted to annually recurring events, this allows you to specify only certain days, counting from either the beginning or the end of the year, to filter a specific event to.
  • setPositions: This parameter is the ultimate filter, allowing you to entirely restrict the event you have created to only specific days of the year. In this way, an event that repeats daily could, for example, be restricted to occur only on the 28th, 102nd, and 364th days of the year for whatever reason a developer might choose.
  • end: Requires a class call to the EKRecurrenceEnd class, and specifies when your event will no longer repeat. The two class methods to choose between are as follows:
  • recurrenceEndWithEndDate: Allows the developer to specify a date after which the event will no longer repeat
  • recurrenceEndWithOccurenceCount: Restricts an event’s repetition to a limited number of occurrences

Based on all this, you can see that the recurring event you have created for demonstration will repeat every second Wednesday up to a limit of 20 occurrences.

This concludes the series of recipes that demonstrates the part of the Event Kit framework that handles calendar events. Next, you’ll take a quick look at a related topic, namely reminders.

Recipe 11-6: Creating Reminders

In iOS 6, Apple released an API that allows you to add entries to the Reminders app that was introduced in iOS 5. With this new API, your apps can interact directly with this great utility and build features that automatically create reminders relevant to your users.

Setting Up the Application

In this recipe you build a simple user interface that allows you to create reminders with two different types of alarms: time-based and location-based. Start by creating a new single-view application project. You use both the Event Kit framework and the Core Location framework, so link the EventKit.framework and CoreLocation.framework binaries to the project.

This time you access two restricted services, Core Location and Reminders, which means you should provide Usage Descriptions for these in the project property list. Add the key Privacy – Location Usage Description with the text Testing Location-Based Reminders and the key Privacy – Reminders Usage Description with the text Testing Reminders, as shown in Figure 11-12.

9781430245995_Fig11-12.jpg

Figure 11-12.  Setting usage descriptions for Location Services and Reminders

Next, you’ll build a user interface that resembles Figure 11-13. So go to the ViewController.xib to bring up Interface Builder. Drag in and position the two buttons and the Activity Indicator. To make the Activity Indicator only appear when active, set its Hides When Stopped checkbox in the Attributes inspector. This will make it initially hidden as well, which is what you want.

9781430245995_Fig11-13.jpg

Figure 11-13.  A user interface for creating two types of reminders

Create actions with the names addTimeBasedReminder and addLocationBasedReminder for the buttons, and an outlet named activityIndicator for the Acitivity Indicator.

Next, go to ViewController.h and import the additional APIs you’ll be utilizing, and declare the usual eventStore property:

//
//  ViewController.h
//  Remind Me
//

#import <UIKit/UIKit.h>
#import <EventKit/EventKit.h>
#import <CoreLocation/CoreLocation.h>

@interface ViewController : UIViewController<CLLocationManagerDelegate>

@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator;
@property (strong, nonatomic)EKEventStore *eventStore;

- (IBAction)addTimeBasedReminder:(id)sender;
- (IBAction)addLocationBasedReminder:(id)sender;

@end

You’ll use lazy initialization for the eventStore property, so go to ViewController.m and add the following custom getter:

- (EKEventStore *)eventStore
{
    if (_eventStore == nil)
    {
        _eventStore = [[EKEventStore alloc] init];
    }
    return _eventStore;
}

Requesting Access to Reminders

As with Calendar Events, access to Reminders is restricted and requires explicit acceptance from the user. In this recipe you’re going to implement a helper method that handles the requesting of Reminders access. Because this process is asynchronous, you use the block technique to inject code to run in case access was granted.

Start by declaring a block type and the method signature in ViewController.h:

//
//  ViewController.h
//  Remind Me
//

#import <UIKit/UIKit.h>
#import <EventKit/EventKit.h>
#import <CoreLocation/CoreLocation.h>

typedef void(^RestrictedEventStoreActionHandler)();
@interface ViewController : UIViewController<CLLocationManagerDelegate>

@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator;
@property (strong, nonatomic)EKEventStore *eventStore;

- (IBAction)addTimeBasedReminder:(id)sender;
- (IBAction)addLocationBasedReminder:(id)sender;

- (void)handleReminderAction:(RestrictedEventStoreActionHandler)block;

@end

The mission of the handleReminderAction: helper method is to request access to Reminders and invoke the provided block of code if granted. If access was denied, it simply displays an alert to inform the user. Here’s the implementation:

- (void)handleReminderAction:(RestrictedEventStoreActionHandler)block
{
    [self.eventStore requestAccessToEntityType:EKEntityTypeReminder
                                    completion:^(BOOL granted, NSError *error)
     {
         if (granted)
         {
             block();
         }
         else
         {
             UIAlertView *notGrantedAlert = [[UIAlertView alloc] initWithTitle:@"Access Denied"
                 message:@"Access to device's reminders has been denied for this app."
                 delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];

             dispatch_async(dispatch_get_main_queue(), ^{
                 [notGrantedAlert show];
             });
         }
    }];
}

An important thing to remember is that the completion block may be invoked on any arbitrary thread. So, if you want to perform an action that affects the user interface, for example displaying an alert view, you need to wrap it in a dispatch_async() function call to make it run on the main thread.

With the helper method in place, you can start implementing the action methods, starting with addTimeBasedReminder:. Here’s the general structure:

- (IBAction)addTimeBasedReminder:(id)sender
{
    [self.activityIndicator startAnimating];
    [self handleReminderAction:^()
    {
        //TODO: Create and add Reminder

        dispatch_async(dispatch_get_main_queue(), ^{
            // TODO: Notify user if the reminder was successfully added or not
            [self.activityIndicator stopAnimating];
        });
    }];
}

Here you make use of the helper method you just created, providing a code block that will be invoked if access was granted by the user.

Now, let’s look at how to implement the first of the two TODOs in the preceding code.

Creating Time-Based Reminders

We’ll show you the steps first and then the complete implementation of the addTimeBasedReminder: method later. The first thing you’ll do when granted access is to create a new reminder object and set its title, and the calendar (that is, Reminder List) in which it will be stored.

EKReminder *newReminder = [EKReminder reminderWithEventStore:self.eventStore];
newReminder.title = @"Simpsons is on";
newReminder.calendar = [self.eventStore defaultCalendarForNewReminders];

Next, you want to set a time for the reminder. In this example you set the actual time to tomorrow at 6 PM. First, you calculate the date for tomorrow by retrieving the current date and adding one day to it using NSDateComponents:

NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *oneDayComponents = [[NSDateComponents alloc] init];
oneDayComponents.day = 1;
NSDate *nextDay =
    [calendar dateByAddingComponents:oneDayComponents toDate:[NSDate date] options:0];

Then, to set the specific time to 6 PM you’ll extract the NSDateComponents object from nextDay, change its hour component to 18 (6 PM on a 24 hour clock) and create a new date from these adjusted components:

NSUInteger unitFlags = NSEraCalendarUnit | NSYearCalendarUnit | NSMonthCalendarUnit |
    NSDayCalendarUnit;
NSDateComponents *tomorrowAt6PMComponents =
    [calendar components:unitFlags fromDate:nextDay];
tomorrowAt6PMComponents.hour = 18;
tomorrowAt6PMComponents.minute = 0;
tomorrowAt6PMComponents.second = 0;
NSDate *nextDayAt6PM = [calendar dateFromComponents:tomorrowAt6PMComponents];

Next, you’ll create an EKAlarm with the time and add it to the reminder. It’s recommended that you also set the dueDateComponents property of the Reminder. This helps the Reminders app display more relevant information. Fortunately, you’ve already constructed the required NSDateComponents object when you previously constructed the alarm date.

EKAlarm *alarm = [EKAlarm alarmWithAbsoluteDate:nextDayAt6PM];
[newReminder addAlarm:alarm];
newReminder.dueDateComponents = tomorrowAt6PMComponents;

Finally, save and commit the new Reminder and inform the user whether the operation was successful. Because displaying a UIAlertView is affecting the user interface, this particular code too needs to be run on the main thread:

// …

NSString *alertTitle;
NSString *alertMessage;
NSString *alertButtonTitle;
NSError *error;
[self.eventStore saveReminder:newReminder commit:YES error:&error];
if (error == nil)
{
    alertTitle = @"Information";
    alertMessage = [NSString stringWithFormat:@""%@" was added to Reminders",
        newReminder.title];
    alertButtonTitle = @"OK";
}
else
{
    alertTitle = @"Error";
    alertMessage = [NSString stringWithFormat:@"Unable to save reminder: %@", error];
    alertButtonTitle = @"Dismiss";
}

dispatch_async(dispatch_get_main_queue(), ^{
    UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:alertTitle
        message:alertMessage delegate:nil cancelButtonTitle:alertButtonTitle
        otherButtonTitles:nil];
    [alertView show];
    [self.activityIndicator stopAnimating];
});

Here’s the complete implementation of the addTimeBasedReminder: action method:

- (IBAction)addTimeBasedReminder:(id)sender
{
    [self.activityIndicator startAnimating];
    [self handleReminderAction:^()
    {
        // Create Reminder
        EKReminder *newReminder = [EKReminder reminderWithEventStore:self.eventStore];
        newReminder.title = @"Simpsons is on";
        newReminder.calendar = [self.eventStore defaultCalendarForNewReminders];

        // Calculate the date exactly one day from now
        NSCalendar *calendar = [NSCalendar currentCalendar];
        NSDateComponents *oneDayComponents = [[NSDateComponents alloc] init];
        oneDayComponents.day = 1;
        NSDate *nextDay = [calendar dateByAddingComponents:oneDayComponents
            toDate:[NSDate date] options:0];

        NSUInteger unitFlags = NSEraCalendarUnit | NSYearCalendarUnit |
            NSMonthCalendarUnit | NSDayCalendarUnit;
        NSDateComponents *tomorrowAt6PMComponents = [calendar components:unitFlags
            fromDate:nextDay];
        tomorrowAt6PMComponents.hour = 18;
        tomorrowAt6PMComponents.minute = 0;
        tomorrowAt6PMComponents.second = 0;
        NSDate *nextDayAt6PM = [calendar dateFromComponents:tomorrowAt6PMComponents];

        // Create an Alarm
        EKAlarm *alarm = [EKAlarm alarmWithAbsoluteDate:nextDayAt6PM];
        [newReminder addAlarm:alarm];
        newReminder.dueDateComponents = tomorrowAt6PMComponents;

        // Save Reminder
        NSString *alertTitle;
        NSString *alertMessage;
        NSString *alertButtonTitle;
        NSError *error;
        [self.eventStore saveReminder:newReminder commit:YES error:&error];
        if (error == nil)
        {
            alertTitle = @"Information";
            alertMessage = [NSString stringWithFormat:@""%@" was added to Reminders",
                newReminder.title];
            alertButtonTitle = @"OK";
        }
        else
        {
            alertTitle = @"Error";
            alertMessage = [NSString stringWithFormat:@"Unable to save reminder: %@",
                error];
            alertButtonTitle = @"Dismiss";
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:alertTitle
                message:alertMessage delegate:nil cancelButtonTitle:alertButtonTitle
                otherButtonTitles:nil];
            [alertView show];
            [self.activityIndicator stopAnimating];
        });
    }];
}

You can now build and run the application and have it create a time-based reminder. The first time this app runs and you tap the button to create a time-based reminder, you’ll be asked whether the app is allowed to access your Reminders. Figure 11-14 shows an example of this alert.

9781430245995_Fig11-14.jpg

Figure 11-14.  An app asking permission to access the user’s Reminders, giving “Testing Reminders” as a reason

Creating Location-Based Reminders

We’re now going to raise the bar a little and create a location-based reminder. What you’re going to do is to implement the addLocationBasedReminder: action method and make it create a new Reminder with an alarm that’s triggered when the user leaves the current location.

Again, you’re going to use code blocks and make a helper method that handles the retrieving of the user’s current location. As you’ll soon realize, this will be a little more complicated because the API to get the device location is based on the delegate pattern and not on the usage of blocks. However, the complication will be hidden behind the nice little API that you’ll set up to retrieve the location.

Add the following code to the ViewController.h file:

//
//  ViewController.h
//  Remind Me
//

#import <UIKit/UIKit.h>
#import <EventKit/EventKit.h>
#import <CoreLocation/CoreLocation.h>

typedef void(^RestrictedEventStoreActionHandler)();
typedef void(^RetrieveCurrentLocationHandler)(CLLocation *);

@interface ViewController : UIViewController<CLLocationManagerDelegate>
{
    @private
    CLLocationManager *_locationManager;
    RetrieveCurrentLocationHandler _retrieveCurrentLocationBlock;
    int _numberOfTries;
}

@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator;
@property (strong, nonatomic)EKEventStore *eventStore;

- (IBAction)addTimeBasedReminder:(id)sender;
- (IBAction)addLocationBasedReminder:(id)sender;

- (void)handleReminderAction:(RestrictedEventStoreActionHandler)block;
- (void)retrieveCurrentLocation:(RetrieveCurrentLocationHandler)block;

@end

As you can see, the signature of the retrieveCurrentLocation: helper method resembles handleReminderAction: that you created in the previous section. The only difference is that its block argument has a CLLocation * parameter. You’ve also prepared the ViewController class to act as a Location Manager delegate by adding the CLLocationManagerDelegate protocol. Additionally, you’ve declared three private instance variables that you’ll be using in the helper method later.

Now, implement the retrieveCurrentLocation: helper method like so:

- (void)retrieveCurrentLocation:(RetrieveCurrentLocationHandler)block
{
    if ([CLLocationManager locationServicesEnabled] == NO)
    {
        UIAlertView *locationServicesDisabledAlert = [[UIAlertView alloc]
            initWithTitle:@"Location Services Disabled" message:@"This feature requires
            location services. Enable it in the privacy settings on your device"
            delegate:nil cancelButtonTitle:@"Dismiss" otherButtonTitles:nil];
        [locationServicesDisabledAlert show];
        return;
    }

    if (_locationManager == nil)
    {
        _locationManager = [[CLLocationManager alloc] init];
        _locationManager.desiredAccuracy = kCLLocationAccuracyBest;
        _locationManager.distanceFilter = 1; // meter
        _locationManager.activityType = CLActivityTypeOther;
        _locationManager.delegate = self;
    }
    _numberOfTries = 0;
    _retrieveCurrentLocationBlock = block;
    [_locationManager startUpdatingLocation];
}

Refer to Chapter 4, Location Recipes, for the details of this method. Note that you’re initializing the _numberOfTries and _retrieveCurrentLocationBlock instance methods before starting the location updates.

Next, implement the delegate method for getting the location:

- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
    // Make sure this is a recent location event
    CLLocation *lastLocation = [locations lastObject];
    NSTimeInterval eventInterval = [lastLocation.timestamp timeIntervalSinceNow];
    if(abs(eventInterval) < 30.0)
    {
        // Make sure the event is accurate enough
        if (lastLocation.horizontalAccuracy >= 0 &&
            lastLocation.horizontalAccuracy < 20)
        {
            [_locationManager stopUpdatingLocation];
            _retrieveCurrentLocationBlock(lastLocation);
            return;
        }
    }
    if (_numberOfTries++ == 10)
    {
        [_locationManager stopUpdatingLocation];
        UIAlertView *unableToGetLocationAlert =
            [[UIAlertView alloc]initWithTitle:@"Error"
                message:@"Unable to get the current location." delegate:nil
                cancelButtonTitle:@"Dismiss" otherButtonTitles: nil];
        [unableToGetLocationAlert show];
    }
}

Again, refer to Chapter 4 for the details of retrieving locations. The important thing with the preceding implementation, though, is that:

  1. You invoke the code block that’s stored in the _retrieveCurrentLocationBlock instance variable, and…
  2. If you, after ten tries, still not have gotten an accurate enough reading, you abandon it and inform the user.

Finally, you implement the addLocationBasedReminder: action method. It resembles a lot of the addTimeBasedReminder: method you implemented earlier, except that it makes use of both helper methods and of course sets up a location-based reminder. Here’s the complete implementation with the differences to addTimeBasedReminder: method marked in bold:

- (IBAction)addLocationBasedReminder:(id)sender
{
    [self.activityIndicator startAnimating];

    [self retrieveCurrentLocation:
     ^(CLLocation *currentLocation)
     {
         if (currentLocation != nil)
         {
             [self handleReminderAction:^()
              {
                  // Create Reminder
                  EKReminder *newReminder =
                      [EKReminder reminderWithEventStore:self.eventStore];
                  newReminder.title = @"Buy milk!";
                  newReminder.calendar =
                      [self.eventStore defaultCalendarForNewReminders];

                  // Create Location-based Alarm
                  EKStructuredLocation *currentStructuredLocation =
                      [EKStructuredLocation locationWithTitle:@"Current Location"];
                  currentStructuredLocation.geoLocation = currentLocation;

                  EKAlarm *alarm = [[EKAlarm alloc] init];
                  alarm.structuredLocation = currentStructuredLocation;
                  alarm.proximity = EKAlarmProximityLeave;

                  [newReminder addAlarm:alarm];
                  // Save Reminder
                  NSString *alertTitle;
                  NSString *alertMessage;
                  NSString *alertButtonTitle;
                  NSError *error;
                  [self.eventStore saveReminder:newReminder commit:YES error:&error];
                  if (error == nil)
                  {
                      alertTitle = @"Information";
                      alertMessage =
                          [NSString stringWithFormat:@""%@" was added to Reminders",
                              newReminder.title];
                      alertButtonTitle = @"OK";
                  }
                  else
                  {
                      alertTitle = @"Error";
                      alertMessage =
                          [NSString stringWithFormat:@"Unable to save reminder: %@",
                              error];
                      alertButtonTitle = @"Dismiss";
                  }

                  dispatch_async(dispatch_get_main_queue(), ^{
                      UIAlertView *alertView =
                          [[UIAlertView alloc]initWithTitle:alertTitle
                              message:alertMessage delegate:nil
                              cancelButtonTitle:alertButtonTitle otherButtonTitles:nil];
                      [alertView show];
                      [self.activityIndicator stopAnimating];
                  });
              }];
         }
     }];
}

You can build and run the application again and this time create both a time-based and a location-based reminder. Figure 11-15 shows an example with the Reminders app displaying two different Reminders created using this app.

9781430245995_Fig11-15.jpg

Figure 11-15.  The Reminders app showing a time-based and a location-based Reminder

Caution  Although working, this app has one serious flaw. Because the creating of Reminders is running on a separate thread and may take a few seconds, the user can tap the buttons before the task has had the chance to finish. This starts a new process that, while accessing the same instance variables, may interfere with the ongoing task and cause unexpected behavior. The easy way to fix this issue is to disable the buttons while the creating of Reminders is ongoing. We leave this implementation to you, as an exercise.

Recipe 11-7: Accessing the Address Book

One of the most imperative functions of any modern device is storing contact information, and, as such, you should take care to develop applications that take advantage of this important data. In this recipe, you cover three basic functionalities for accessing and dealing with a device’s contacts list.

First, create a new single-view application project called “My Pick Contact App.”

For this recipe, you need to add two extra frameworks to your project: AddressBook.framework, and AddressBookUI.framework.

Because the Address Book, like the Calendar, is a restricted entity, you should provide a usage description in the application’s property list. Add the Privacy – Contacts Usage Description key with the value Testing Address Book Access.

Next, switch over to your view controller’s .xib file, and create a view that resembles the one in Figure 11-16.

9781430245995_Fig11-16.jpg

Figure 11-16.  User interface for accessing contact information

Create these outlets to connect the elements to your code:

  • firstNameLabel
  • lastNameLabel
  • phoneNumberLabel
  • cityNameLabel

You do not need an outlet for the button, but create an action with the name pickContact for when the user taps it.

Now that your interface is set up, make some changes to the header file. First, add the following two import statements so that you can access the Address Book and Address Book UI frameworks.

#import <AddressBook/AddressBook.h>
#import <AddressBookUI/AddressBookUI.h>

You use an instance of the class ABPeoplePickerNavigationController, and setting its peoplePickerDelegate property to your view controller, so you need to add the ABPeoplePickerNavigationControllerDelegate protocol implementation to your header file.

The header file, in its entirety, should now look like this:

//
//  ViewController.h
//  My Pick Contact App
//

#import <UIKit/UIKit.h>
#import <AddressBook/AddressBook.h>
#import <AddressBookUI/AddressBookUI.h>

@interface ViewController : UIViewController<ABPeoplePickerNavigationControllerDelegate>

@property (weak, nonatomic) IBOutlet UILabel *firstNameLabel;
@property (weak, nonatomic) IBOutlet UILabel *lastNameLabel;
@property (weak, nonatomic) IBOutlet UILabel *phoneNumberLabel;
@property (weak, nonatomic) IBOutlet UILabel *cityNameLabel;

- (IBAction)pickContact:(id)sender;

@end

Switch over to the implementation file. You implement the pickContact: method to create an instance of ABPeoplePickerNavigationController, set its delegate, and then display it.

- (IBAction)pickContact:(id)sender
{
    ABPeoplePickerNavigationController *picker =
        [[ABPeoplePickerNavigationController alloc] init];
    picker.peoplePickerDelegate = self;
    [self presentViewController:picker animated:YES completion:nil];
}

Now you just need to create your delegate methods, of which there are three you are required to implement. The first, and simplest, is for when the picker controller is canceled.

-(void)peoplePickerNavigationControllerDidCancel:
(ABPeoplePickerNavigationController *)peoplePicker
{
    [self dismissViewControllerAnimated:YES completion:nil];
}

Next, define your main delegate method to handle the selection of a contact. Here is a step-by-step method implementation to discuss each part.

First, your method header looks like so:

-(BOOL)peoplePickerNavigationController:
(ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person

The first odd thing you may notice about this header is that the variable person is of type ABRecordRef, which does not have a “*” after it. This essentially means that person is not a pointer, and thus will not be used to call methods. Instead, you will use predefined functions that utilize and access it. As you see, many parts of the Address Book framework use this “C-based” style.

Inside the method body, you first access the simplest properties, which are the first and last names of the chosen contact.

self.firstNameLabel.text =
    (__bridge_transfer NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty);
self.lastNameLabel.text =
    (__bridge_transfer NSString *)ABRecordCopyValue(person, kABPersonLastNameProperty);

The ABRecordCopyValue()function is your go-to call for any kind of accessing data in this section. It takes two parameters, the first is the ABRecordRef that you want to access, and the second is a predefined PropertyID that instructs the function on which piece of data to retrieve.

There are two types of values that can be dealt with by this function: single values and multi-values. For these first two calls, you are dealing only with single values, for which the ABRecordCopyValue() function returns a type of CFStringRef. You can cast this up to an NSString by adding the (__bridge_transfer NSString *) code in front of the value.

Note  The __bridge_transfercommand specifies that the memory management of the object is being transferred to ARC. You can find more information on this in Apple’s documentation1.

The next value you can access is the person’s phone number, which is a multi-value. Multi-values are usually used for the properties of a person for which multiple entries can be given, such as address, phone number, or email. When you copy this, you will receive a variable of type ABMultiValueRef, which you can then use to access a specific value.

ABMultiValueRef phoneRecord = ABRecordCopyValue(person, kABPersonPhoneProperty);
CFStringRef phoneNumber = ABMultiValueCopyValueAtIndex(phoneRecord, 0);
self.phoneNumberLabel.text = (__bridge_transfer NSString *)phoneNumber;
CFRelease(phoneRecord);

By using the call ABMultiValueCopyValueAtIndex(phoneProperty, 0), you have specified that you want the first phone number stored for the given user. From there, you can set your label’s text just as you did before.

The next multi-value you deal with is the main address of the chosen contact. When dealing with the address, an extra step is required, as an address is stored as a CFDictionary. You retrieve this dictionary using the ABMultiValueCopyValueAtIndex() function again, and then query its values:

ABMultiValueRef addressRecord = ABRecordCopyValue(person, kABPersonAddressProperty);
if (ABMultiValueGetCount(addressRecord) > 0)
{
    CFDictionaryRef addressDictionary = ABMultiValueCopyValueAtIndex(addressRecord, 0);
    self.cityNameLabel.text =
        [NSString stringWithString:
            (__bridge NSString *)CFDictionaryGetValue(addressDictionary,
                kABPersonAddressCityKey)];
    CFRelease(addressDictionary);
}
else
{
    self.cityNameLabel.text = @"...";
}
CFRelease(addressRecord);

There are a couple of things with the preceding code that you may be wondering about. First, why do you need to release, for example, addressDictionary and addressRecord, but not the First Name and Last Name values that you retrieved earlier?

The reason is that in those cases, you transferred the ownership of the value over to the respective outlet by using the __bridge_transfer type specifier. But for the multi-value records, you didn’t transfer ownership so they must be released or their memory will be leaked.

The second thing you might be wondering is what the following piece of code is about:

self.cityNameLabel.text =
    [NSString stringWithString:
        (__bridge NSString *)CFDictionaryGetValue(addressDictionary,
            kABPersonAddressCityKey)];

Why are you suddenly using __bridge and not __bridge_transfer here? And why construct a new string using the stringWithString class method? Here too, the answer is ownership. The CFDictionaryGetValue()function, as opposed to ABMultiValueCopyValueAtIndex(), retains ownership of the value it returns. Because you want to store the string in your cityNameLabel.text property, you need to copy it first. And because you don’t want to transfer ownership of the original string value (which would lead to a memory leak), you use a plain __bridge cast.

To finalize the implementation of the peoplePickerNavigationController:shouldContinueAfterSelectingPerson: delegate method, you dismiss the modal view controller and return NO. As a whole, your method should look like so:

-(BOOL)peoplePickerNavigationController:
(ABPeoplePickerNavigationController *)peoplePicker
     shouldContinueAfterSelectingPerson:(ABRecordRef)person
{

    self.firstNameLabel.text =
        (__bridge_transfer NSString *)ABRecordCopyValue(person,
            kABPersonFirstNameProperty);
    self.lastNameLabel.text =
        (__bridge_transfer NSString *)ABRecordCopyValue(person,
            kABPersonLastNameProperty);

    ABMultiValueRef phoneRecord = ABRecordCopyValue(person, kABPersonPhoneProperty);
    CFStringRef phoneNumber = ABMultiValueCopyValueAtIndex(phoneRecord, 0);
    self.phoneNumberLabel.text = (__bridge_transfer NSString *)phoneNumber;
    CFRelease(phoneRecord);

    ABMultiValueRef addressRecord = ABRecordCopyValue(person, kABPersonAddressProperty);
    if (ABMultiValueGetCount(addressRecord) > 0)
    {
        CFDictionaryRef addressDictionary =
            ABMultiValueCopyValueAtIndex(addressRecord, 0);
        self.cityNameLabel.text =
            [NSString stringWithString:
                (__bridge NSString *)CFDictionaryGetValue(addressDictionary,
                    kABPersonAddressCityKey)];
        CFRelease(addressDictionary);
    }
    else
    {
        self.cityNameLabel.text = @"...";
    }
    CFRelease(addressRecord);

    [self dismissViewControllerAnimated:YES completion:nil];
    return NO;
}

There is a third delegate method you must implement in order to fully conform to the ABPeoplePickerNavigationControllerDelegate protocol. It handles the selection of a specific contact’s property. However, because this recipe is simply returning after the selection of a contact, this method will not actually be called. To get rid of the compiler warning, add this method with a simple implementation similar to your cancellation method:

-(BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier
{
    [self dismissViewControllerAnimated:YES completion:nil];
    return NO;
}

Caution  Whenever you are copying values from an ABRecordRef, include a check to be sure that a value exists, as you did with the address. The previous code assumed that the first name, last name, and phone number exist, but an empty query can result in your application throwing an exception.

Your application can now access the address book, select a user, and display the information for which you have queried. The first time the function is run you’re asked to grant the app access to the device’s Contacts. Figure 11-17 shows examples of the app in different modes.

9781430245995_Fig11-17.jpg

Figure 11-17.  To the left, a request to access Contact information is required. In the middle, a contact in the user’s Address Book. To the right, contact information retrieved from the Address Book and displayed in the app.

While you have not included code to access all the possible values for an ABRecordRef, you should be able to use any combination of the utilized functions to access whichever ones you need.

Recipe 11-8: Setting Contact Information

Just as important as being able to access values is being able to set them. To this end, you will implement two different methods for creating and setting values of a contact and adding it to your device’s address book.

First, create a new single-view application project to which you link the Address Book and the Address Book UI frameworks. Also, like you did in the previous recipe, provide a usage description explaining why your app wants to access the device’s Address Book. We chose “Testing creating contacts.”

Set up a simple user interface that allows users to create a new contact themselves. In the view controller’s .xib file, add a single UIButton titled “New Contact,” like in Figure 11-18. Then create an action named addNewContact, for when the user taps the button.

9781430245995_Fig11-18.jpg

Figure 11-18.  Simple user interface setup for creating contacts

Next, you need to import your frameworks into your header file and configure your view controller’s protocol to conform to. Conform your view controller to the ABNewPersonViewControllerDelegate protocol, and then add the usual two import statements.

#import <AddressBook/AddressBook.h>
#import <AddressBookUI/AddressBookUI.h>

Now, create a very simple implementation, for which you have to define only two methods: the action to handle the selection of your button, and the delegate method for an ABNewPersonViewController.

The action method looks like so:

- (IBAction)addNewContact:(id)sender
{
    ABNewPersonViewController *view = [[ABNewPersonViewController alloc] init];
    view.newPersonViewDelegate = self;
    UINavigationController *newNavigationController =
        [[UINavigationController alloc] initWithRootViewController:view];
    [self presentViewController:newNavigationController animated:YES completion:nil];
}

Here is the delegate method:

-(void)newPersonViewController:(ABNewPersonViewController *)newPersonView didCompleteWithNewPerson:(ABRecordRef)person
{
    if (person == NULL)
    {
        NSLog(@"User Cancelled Creation");
    }
    else
    {
        NSLog(@"Successfully Created New Person");
    }
    [self dismissViewControllerAnimated:YES completion:nil];
}

Unlike most modal view controllers that you deal with, the ABNewPersonViewController has only one delegate method that handles both success and cancellation, as opposed to others that have one method for each. As you can see, you differentiate between each result by checking to see if the ABRecordRef person parameter is not NULL. Because this parameter is not a pointer, you compare it to the NULL value instead of nil.

At this point, you should be able to allow your user to create a new contact to be added to the address book, as the simulated app in Figure 11-19 shows.

9781430245995_Fig11-19.jpg

Figure 11-19.  A blank ABNewPersonViewController

While you have provided users with a great deal of flexibility as to how they want their contacts to be set up, you have also provided them with a great deal of work to do, in that they have to type in every value that they want. You will next see how to programmatically create records and set their values. For the purpose of demonstration, we will make it simple and provide the ABNewPersonViewController with preset values that are hard-coded.

What you’re going to do, is to populate the ABNewPersonViewController with preset values. Update the addNewContact: method, starting with the adding of hard-coded values:

- (IBAction)addNewContact:(id)sender
{
    NSString *firstName = @"John";
    NSString *lastName = @"Doe";
    NSString *mobileNumber = @"555-123-4567";
    NSString *street = @"12345 Circle Wave Ave";
    NSString *city = @"Coral Gables";
    NSString *state = @"FL";
    NSString *zip = @"33146";
    NSString *country = @"United States";

    // ...
}

Next, create a new contact record and add values for the first name, last name and phone contact information:

- (IBAction)addNewContact:(id)sender
{
    // ...

    ABRecordRef contactRecord = ABPersonCreate();
    // Setup first and last name records
    ABRecordSetValue(contactRecord, kABPersonFirstNameProperty,
         (__bridge_retained CFStringRef)firstName, nil);
    ABRecordSetValue(contactRecord, kABPersonLastNameProperty,
         (__bridge_retained CFStringRef)lastName, nil);

    // Setup phone record
    ABMutableMultiValueRef phoneRecord =
        ABMultiValueCreateMutable(kABMultiStringPropertyType);
    ABMultiValueAddValueAndLabel(phoneRecord,
        (__bridge_retained CFStringRef)mobileNumber, kABPersonPhoneMobileLabel, NULL);
    ABRecordSetValue(contactRecord, kABPersonPhoneProperty, phoneRecord, nil);
    CFRelease(phoneRecord);

    // ...
}

The __bridge_retained type specifier indicates that you want to transfer ownership from an ARC controlled object (NSString in this case) to a Core Foundation object (CFStringRef). This is necessary so these objects don’t prematurely get released by ARC.

Now, the address record involves a bit more work to create the dictionary and add it to the contact record:

- (IBAction)addNewContact:(id)sender
{
    // ...

    // Setup address record
    ABMutableMultiValueRef addressRecord =
        ABMultiValueCreateMutable(kABDictionaryPropertyType);
    CFStringRef dictionaryKeys[5];
    CFStringRef dictionaryValues[5];
    dictionaryKeys[0] = kABPersonAddressStreetKey;
    dictionaryKeys[1] = kABPersonAddressCityKey;
    dictionaryKeys[2] = kABPersonAddressStateKey;
    dictionaryKeys[3] = kABPersonAddressZIPKey;
    dictionaryKeys[4] = kABPersonAddressCountryKey;
    dictionaryValues[0] = (__bridge_retained CFStringRef)street;
    dictionaryValues[1] = (__bridge_retained CFStringRef)city;
    dictionaryValues[2] = (__bridge_retained CFStringRef)state;
    dictionaryValues[3] = (__bridge_retained CFStringRef)zip;
    dictionaryValues[4] = (__bridge_retained CFStringRef)country;

    CFDictionaryRef addressDictionary = CFDictionaryCreate(kCFAllocatorDefault,
        (void *)dictionaryKeys, (void *)dictionaryValues, 5,
        &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    ABMultiValueAddValueAndLabel(addressRecord, addressDictionary, kABHomeLabel, NULL);
    CFRelease(addressDictionary);

    ABRecordSetValue(contactRecord, kABPersonAddressProperty, addressRecord, nil);
    CFRelease(addressRecord);

    // ...
}

Finally, you initialize and display the ABNewPersonerViewController with the new contact record, which you then release to avoid a memory leak:

- (IBAction)addNewContact:(id)sender
{
    // ...

    // Display View Controller
    ABNewPersonViewController *view = [[ABNewPersonViewController alloc] init];
    view.newPersonViewDelegate = self;
    view.displayedPerson = contactRecord;

    UINavigationController *newNavigationController =
        [[UINavigationController alloc] initWithRootViewController:view];
    [self presentViewController:newNavigationController animated:YES completion:nil];

    CFRelease(contactRecord);
}

If you build and run your application now, you should have the ABNewPersonViewController populated with the preset values, as shown in Figure 11-20.

9781430245995_Fig11-20.jpg

Figure 11-20.  The ABNewPersonViewController with preset values that have been added programmatically

Summary

As you can see, there are many methods and functionalities for interacting with any specific user’s personal data. From recurring events, to multiple calendars, to the vast number of contacts and phone numbers that most users have—all this information can be used to personalize an application for each and every user. In terms of user experience, being able to access, display, and edit all this information allows us as developers to create more powerful, more unique, and more useful applications.

1http://developer.apple.com/library/ios/#documentation/CoreFoundation/Conceptual/CFDesignConcepts/Articles/tollFreeBridgedTypes.html

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset