Chapter     14

User Data Recipes

No two people are alike, and in the same way no two iOS devices are alike. The information that one device stores depends 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 can incorporate it into our applications and provide a more unique, user-specific interface. In this chapter, we cover a variety of methods for dealing with user-based data. First we focus on the calendar, and then we focus on the address book.

Recipe 14-1. Working with NSCalendar and NSDate

Many different applications are used for time-based and date-based calculations. Examples include everything 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.

In this recipe, 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. The NSDate class is used to create dates and handle date operations such as comparing two dates. The NSCalendar class is used to keep track of NSDate objects and perform computations such as determining date ranges. The NSDateComponents class is used to pull out components of a date such as hours, minutes, and so on.

To begin, create a new single view application project. Switch to the Main.storyboard file and build a user interface that resembles Figure 14-1. Use the following components to create the interface:

  • Labels: Month, Day, Year, Gregorian, Hebrew
  • Buttons: To Hebrew, To Gregorian
  • Six text fields: All with a placeholder value of 0

9781430259596_Fig14-01.jpg

Figure 14-1. User interface for calendar conversion

You need to set up properties to represent each UITextField. The text fields will display conversions as well as take inputs. 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 Listing 14-1.

Listing 14-1.  The Complete ViewController.h File

 //
//  ViewController.h
//  Recipe 14-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 code in Listing 14-2 to the viewDidLoad method of the implementation file.

Listing 14-2.  Setting UITextField Delegates

- (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, as shown in Listing 14-3.

Listing 14-3.  Implementation of the textFieldShouldReturn: Delegate Method

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

We briefly discussed the NSCalendar class earlier, but in this example this class is essentially used to set a standard for the dates 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 that 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 to your ViewController.h class:

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

Use lazy initialization for these properties. Lazy initialization basically allows us to forego initialization until these properties are needed. Add the custom getter implementations, as shown in Listing 14-4.

Listing 14-4.  Creating Custom Getter Implementations for gregorianCalendar and hebrewCalendar Properties

-(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 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 available for use with the NSCalendar class, including NSBuddhistCalendar, NSIslamicCalendar, and NSJapaneseCalendar.

Given the immense multicultural nature of today’s technological world, you might 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, as shown in Listing 14-5.

Listing 14-5.  Implementing the convertToGregorian: Method

- (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 from Listing 14-5, you are using a combination of NSDateComponents, NSDate, and NSCalendar to perform this conversion.

The NSDateComponents class, as mentioned earlier, 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 you have defined.

One of the more confusing parts of the preceding method might 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, as shown in Listing 14-6.

Listing 14-6.  Implementing the convertToHebrew: Method

- (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 14-2. Try experimenting with different dates or even different calendars to see what kinds of powerful date conversions you can do.

9781430259596_Fig14-02.jpg

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

Recipe 14-2. Fetching Calendar Events

Now that we have covered how to deal with basic date conversions and calculations, we can go into details on dealing with events and calendars and interacting with the user’s own events and schedule. The next few recipes combine to create a complete utilization of the Event Kit framework, which makes classes available for manipulating events and reminders.

First, create a new single view application project with the name “My Events App” or something similar. We’ll be using this for the next few recipes. This time you use the Event Kit framework, so link EventKit.framework to your newly created project.

Because this app will 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 14-3 shows.

9781430259596_Fig14-03.jpg

Figure 14-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 declarations shown in Listing 14-7 to the ViewController.h file.

Listing 14-7.  Importing the Event Kit Framework and Adding an EKEventStore Property

//
//  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. Do this using the requestAccessToEntityType:completion: method of EKEventStore, passing a code block that will be invoked when the asynchronous process is done (Listing 14-8).

Listing 14-8.  Requesting Permission to Access Calendar Entries in the viewDidLoad Method

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 the current time and 48 hours from then. First create the two dates, as shown in Listing 14-9.

Listing 14-9.  Creating Two Calendar Dates for the Current Time and Current Time + 48 Hours

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, as shown in Listing 14-10.

Listing 14-10.  Creating an NSPredicate

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

You then use the predicate to retrieve the actual events from the event store, as shown in Listing 14-11.

Listing 14-11.  Retrieving 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, as shown in Listing 14-12.

Listing 14-12.  Printing Event Titles

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

Listing 14-13 shows the complete implementation of the viewDidLoad method.

Listing 14-13.  The Complete viewDidLoad Implementation

- (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. Make sure 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 14-4 for an example of such output.

9781430259596_Fig14-04.jpg

Figure 14-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 14-5). This is part of a privacy policy implemented in iOS 7. Because the calendar can contain private information that might be sensitive, apps must ask the user’s explicit permission before accessing it.

9781430259596_Fig14-05.jpg

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

The user is asked only 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, they can do that in the Settings app, under Privacy arrow.jpg Calendars (see Figure 14-6).

9781430259596_Fig14-06.jpg

Figure 14-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 will want to reset the privacy settings to test the initial run scenario again. You can reset these settings in the Settings app, under General arrow.jpg Reset using the Reset Location & Privacy option.

Recipe 14-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.

Start by turning the project into a navigation-based application. This will require a little bit of storyboarding. While we will try to explain all the steps well, it might be beneficial to read about storyboards in Chapter 2 before proceeding.

To begin, switch to the Main.storyboard file and select the view in the storyboard. With the view selected, choose Editor arrow.jpg Embed In arrow.jpg Navigation Controller from the main menu. Once this is done, a navigation controller will be created on the storyboard. You might want to drag the navigation controller off the view controller and arrange the two views, as shown in Figure 14-7.

9781430259596_Fig14-07.jpg

Figure 14-7. Arranging the view controller next to the navigation controller

Next, set up the user interface consisting of a UITableView. Select the View Controller from the Main.storyboard file in the project navigator.

Now you can add the table view from the object library and make it fill the available area. This will exclude the navigation bar. In the attributes inspector for the table view, make sure the Style attribute is set to Grouped. Your main view should now resemble Figure 14-8.

9781430259596_Fig14-08.jpg

Figure 14-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 to your implementation file, you will need to make some additional changes to the header file. First, add the UITableViewDelegate and 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, which is used to store all your events based on which calendar they belong to. The ViewController.h file should now resemble the code in Listing 14-14.

Listing 14-14.  The Complete ViewController.h File

//
//  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 task you need to perform is to modify the viewDidLoad method. Specifically, you need to take these actions:

  1. Set the title displayed in the navigation bar.
  2. Add a Refresh button to the navigation bar.
  3. Set up the table view’s two delegate methods.
  4. Populate the calendars array and the events dictionary.

To accomplish these things, make the changes shown in Listing 14-15 to the viewDidLoad method.

Listing 14-15.  Modifying the viewDidLoad Method to Include a Bar Button Item and Set Delegates

- (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);
         }
     }];
}

The code in Listing 14-15 uses two methods 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. Add the code in Listing 14-16 to the view controller class.

Listing 14-16.  Implementing the refresh: Method

- (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. This code is implemented in Listing 14-17.

Listing 14-17.  Implementing the fetchEvents: Method

- (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 Recipe 14-2. The main difference is that you now perform a search for each 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 method 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 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 code in Listing 14-18 to the ViewController.m file.

Listing 14-18.  Implementing Helper Methods in 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 (Listing 14-19).

Listing 14-19.  Implementing the numberOfSectionsInTableView: Delegate Method

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

You can also implement a method to specify your section titles, as shown in Listing 14-20.

Listing 14-20.  Implementing the tableView:titleForHeaderInSection: Delegate Method

-(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. Listing 14-21 shows this.

Listing 14-21.  Implementing the tableView:numberOfRowsInSection: Delegate Method

-(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, as shown in Listing 14-22.

Listing 14-22.  Implementing the tableView:cellForRowAtindexPath: Delegate Method

- (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 14-9 shows, your application can now display all calendar events that occur within 48 hours.

9781430259596_Fig14-09.jpg

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

Recipe 14-4. Viewing, Editing, and Deleting Events

The next step is to allow the user to view, edit, and delete events through predefined classes in the Event Kit UI framework.

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

You also need to import its API into the main view controller’s header file, as shown in Listing 14-23.

Listing 14-23.  Adding an Import Statement for the EventKitUI Framework to the ViewController.h 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 task 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 tableView:DidSelectRowAtIndexPath: data source method, as shown in Listing 14-24.

Listing 14-24.  Implementing the tableView:didSelectRowAtIndexPath: Delegate 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];
}

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 viewWillLoad method, as shown in Listing 14-25.

Listing 14-25.  Implementing the viewWillAppear Method to Refresh the Table View

- (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, as shown in Listing 14-26.

Listing 14-26.  Adding the EKEventEditViewDelegate to the ViewController.h File

//
//  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, as shown in Listing 14-27. As mentioned previously, this method will take you directly to editing mode.

Listing 14-27.  Implementing the tableView:accessorButtonTappedForRowWithIndexpath: Delegate Method

-(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, as shown in Listing 14-28.

Listing 14-28.  Implementing the eventEditViewController:didCompleteWithAction: Delegate Method

-(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 the implementation shown in Listing 14-29, you simply return the default calendar of the device.

Listing 14-29.  Implementing the eventEditViewControllerDefaultCalendarForNewEvents: Delegate Method

-(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. Figure 14-10 shows an example of the user interfaces of these view controllers.

9781430259596_Fig14-10.jpg

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

Recipe 14-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 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 14-2. The first task is to add an Add button to the navigation bar. Go to the viewDidLoad method in ViewController.m and add the code in Listing 14-30.

Listing 14-30.  Adding a UIBarButtonItem 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;
    
    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, as shown in Listing 14-31.

Listing 14-31.  Declaring the UIAlertViewDelegate

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

Then add the action method that presents the alert view, as shown in Listing 14-32.

Listing 14-32.  Implementing the addEvent: Action Method

- (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, as shown in Listing 14-33.

Listing 14-33.  Implementing the alertView:clickedButtonAtIndex: Delegate Method

- (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 14-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.

9781430259596_Fig14-11.jpg

Figure 14-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 will change the event that this app creates and make it recur. Let’s add the code first and look at how it works later. The code is shown in bold in Listing 14-34.

Listing 14-34.  Modifying the alertView:clickedButtonAtIndex: Method to Add a Recurring Event

- (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 in Listing 14-34, 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: This specifies a basic level of how often the event repeats, whether on a daily, weekly, monthly, or annual basis.
  • interval: This specifies the interval of repetition based on the frequency. A recurring event with a weekly frequency and an interval of three repeats every three weeks.
  • daysOfTheWeek: This 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: This is similar to daysOfTheWeek. It specifies which days in a month to restrict a recurring event to. It is valid only for events with monthly frequency.
  • monthsOfTheYear: This is similar to daysOfTheWeek and daysOfTheMonth; it’s valid only for events with a yearly frequency.
  • weeksOfTheYear: Just like monthsOfTheYear, this is restricted 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 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: This 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 of each month up to a limit of 20 occurrences. Run the app, add an event, and check the calendar to see the changes.

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

Recipe 14-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 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 types of alarms: time-based and location-based. Start by creating a 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 14-12.

9781430259596_Fig14-12.jpg

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

Next, you’ll build a user interface that resembles Figure 14-13, so go to the Main.storyboard file to edit the view controller. Drag in and position the two buttons and the activity indicator. To make the activity indicator appear only when active, set its “Hides When Stopped checkbox” property in the attributes inspector. This will make it initially hidden as well, which is what you want.

9781430259596_Fig14-13.jpg

Figure 14-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 activity indicator.

Next, go to ViewController.h, import the additional APIs you’ll be utilizing, and declare the usual eventStore property, as shown in Listing 14-35.

Listing 14-35.  Importing Frameworks and Adding an EKEventStore Property to the ViewController.h File

//
//  ViewController.h
//  Recipe 14-6 Creating Reminders
//

#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 custom getter shown in Listing 14-36.

Listing 14-36.  Implementing the eventStore Getter Method

- (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 will implement a helper method that handles the requesting of Reminders access. Because this process is asynchronous, you will use the block technique to inject code to run in case access is granted.

Start by declaring a block type and the method signature in ViewController.h, as shown in Listing 14-37.

Listing 14-37.  Declaring Block Types and Methods 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 is denied, it simply displays an alert to inform the user. Listing 14-38 shows the implementation.

Listing 14-38.  Implementing the handleReminderAction: Helper Method

- (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 point to remember is that the completion block might 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:. Listing 14-39 shows the general structure.

Listing 14-39.  Implementing the addTimeBasedReminder: Method

- (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 is 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 (Listing 14-40).

Listing 14-40.  Creating an EKReminder Instance and Setting Properties

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

Next, you need to set a time for the reminder. In Listing 14-41, you set the actual time to tomorrow at 6 p.m. First, calculate the date for tomorrow by retrieving the current date and adding one day to it using NSDateComponents.

Listing 14-41.  Setting the Time for the Reminder

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 p.m., extract the NSDateComponents object from nextDay, change its hour component to 18 (6 p.m. on a 24-hour clock), and create a new date from these adjusted components, as shown in Listing 14-42.

Listing 14-42.  Setting the Specific Time

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, create an EKAlarm with the time and add it to the reminder, as shown in Listing 14-43. It’s recommended that you also set the dueDateComponents property of the reminder. This helps the Reminders app display more relevant information. Fortunately, you already constructed the required NSDateComponents object when you previously constructed the alarm date.

Listing 14-43.  Creating an EKAlarm and Adding It to the Reminder

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 needs to be run on the main thread, as shown in Listing 14-44.

Listing 14-44.  Saving and Committing the New 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];
});

Listing 14-45 shows the complete implementation of the addTimeBasedReminder: action method.

Listing 14-45.  The Complete addTimeBasedReminder: Implementation

- (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 14-14 shows an example of this alert.

9781430259596_Fig14-14.jpg

Figure 14-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 bold code in Listing 14-46 to the ViewController.h file.

Listing 14-46.  Modifying the ViewController.h File to Accommodate Location-Based Reminders

//
//  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 the handleReminderAction: 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, as shown in Listing 14-47.

Listing 14-47.  Implementing the retrieveCurrentLocation: Helper Method

- (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 5 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, as shown in Listing 14-48.

Listing 14-48.  Implementing the locationManager:didUpdateLocation: Delegate Method

- (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 5 for the details of retrieving locations. The important points to note with the preceding implementation are the following:

  1. Invoke the code block that’s stored in the _retrieveCurrentLocationBlock instance variable.
  2. After ten tries, if you still not have obtained an accurate enough reading, abandon it and inform the user.

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

Listing 14-49.  The Complete addLocationBasedreminder: Method Implementation

- (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 14-15 shows an example with the Reminders app displaying two different reminders created using this app.

Note   You might experience an error here if you do not have your reminders tied to your iCloud account. You can change this from the default reminders list found in Settings arrow.jpg Reminders arrow.jpg Default List. Location-based reminders will work only with an iCloud account. If you have another account, such as a Microsoft Exchange account, set to the Default List, this functionality will not work.

9781430259596_Fig14-15.jpg

Figure 14-15. The Reminders app showing a time-based and a location-based reminder

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

Recipe 14-7. Accessing the Address Book

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

First, create a 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 to your view controller’s Main.storyboard to edit the view, and create a view that resembles the one in Figure 14-16.

9781430259596_Fig14-16.jpg

Figure 14-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 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 set 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 Listing 14-50.

Listing 14-50.  The Complete ViewController.h File

//
//  ViewController.h
//  Recipe 14-7 Accessing the Address Book
//

#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 to the implementation file. There you will implement the pickContact: method to create an instance of ABPeoplePickerNavigationController, set its delegate, and then display it, as shown in Listing 14-51.

Listing 14-51.  Implementing the pickContact: Action Method

- (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 (Listing 14-52).

Listing 14-52.  Implementing the peoplePickerNavigationControllerDidCancel: Delegate Method

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

Next, define your main delegate method to handle the selection of a contact. The following is a step-by-step method implementation that demonstrates each part.

Your method header should look like Listing 14-53.

Listing 14-53.  The Method Header

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

The first odd thing you might 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 shown in Listing 14-54.

Listing 14-54.  Setting the First and Last Name Properties

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 multivalues. 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 documentation.

The next value you can access is the person’s phone number, which is a multivalue. Multivalues are usually used for the properties of a person for which multiple entries can be given, such as address, phone number, or e-mail. When you copy this, you will receive a variable of type ABMultiValueRef, which you can then use to access a specific value, as shown in Listing 14-55.

Listing 14-55.  Accessing the Phone Number

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 multivalue 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, as shown in Listing 14-56.

Listing 14-56.  Querying CFDictionary for the Stores 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);

You might be wondering about a couple of things with the code from Listing 14-56. 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 to the respective outlet by using the __bridge_transfer type specifier. But for the multivalue 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 Listing 14-57.

Listing 14-57.  The Complete peoplePickerNavigationController: Method

-(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 the method in Listing 14-58 with a simple implementation similar to your cancellation method.

Listing 14-58.  Implementing the peoplePickerNavigationController:shouldContinueAfterSelectingPerson:identifier:  Delegate 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 app. Figure 14-17 shows examples of the app in different modes.

9781430259596_Fig14-17.jpg

Figure 14-17. A request to access contact information is required, and contact information is 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 14-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 Address Book UI frameworks. Also, as you did in the previous recipe, you should provide a usage description in the application’s property list. Add the “Privacy – Contacts Usage Description” key with the value Testing Creating Contacts.

Set up a simple user interface that allows users to create a new contact. In the Main.storyboard file, add a single UIButton titled “New Contact,” as in Figure 14-18, to the view. Then create an action named addNewContact for when the user taps the button.

9781430259596_Fig14-18.jpg

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

Next, 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 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 ABNewPersonViewControllerDelegate.

The action method looks like Listing 14-59.

Listing 14-59.  Implementing the addNewContacts: Action Method

- (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];
}

The delegate method should look like Listing 14-60.

Listing 14-60.  Implementing the newPersonViewController:didCompleteWithNewPerson: 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 whether 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 14-19 shows.

9781430259596_Fig14-19.jpg

Figure 14-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 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.

You will populate the ABNewPersonViewController with preset values. Update the addNewContact: method, starting with adding hard-coded values, as shown in Listing 14-61.

Listing 14-61.  Updating the addnewContact: Action Method with Preset 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, as shown in Listing 14-62.

Listing 14-62.  Creating the New Contact Record in the addNewContact: Action Method

- (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. This implementation is shown in Listing 14-63.

Listing 14-63.  Adding the Address Record to the addNewContact: Action Method

- (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, initialize and display the ABNewPersonerViewController with the new contact record, which you then release to avoid a memory leak, as shown in Listing 14-64.

Listing 14-64.  Initializing and Displaying the ABnewPersonerViewController

- (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 14-20.

9781430259596_Fig14-20.jpg

Figure 14-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 user. In terms of user experience, being able to access, display, and edit this information allows us as developers to create more powerful, unique, and useful applications.

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

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