Chapter 8

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 incredibly 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 incredibly 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 8–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. In order to use the more intricate event-based user interface, you must have a solid understanding of the simpler NSDate-focused APIs. Here, you will implement a simple application to illustrate the use of the NSDate, NSCalendar, and NSDateComponents classes by converting dates from the popular Gregorian calendar to the Hebrew calendar.

Create a new project called “Chapter8Recipe1”, with the class prefix “Main,” using the Single View Application template. You will not need to utilize any extra frameworks. Switch over to your view controller's XIB file. Set up your XIB file to resemble the one shown in Figure 8–1.

Image

Figure 8–1. User interface for calendar conversion

You will need to set up properties to represent each UITextField. Once your view is set up, connect each UITextField to your header file with the following property names as appropriate.

  • textFieldGMonth
  • textFieldGDay
  • textFieldGYear
  • textFieldHMonth
  • textFieldHDay
  • textFieldHYear

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 to connect the UIButtons to outlets, but make sure they are connected to respective actions -convertToHebrew: and -convertToGregorian:.

Always keep in mind that whenever you are dealing with an application that takes user input, such as the UITextFields here, there is always a possibility of a keyboard popping up and covering the bottom half of your screen. Plan your design accordingly by placing all UITextFields in the upper half of the view!

In order to better control all these UITextFields, you will need to make your view controller the delegate for them all. First, add <UITextFieldDelegate> to your controller's header line, so that it now looks like so:

@interface MainViewController : UIViewController <UITextFieldDelegate>

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

for (UITextField *field in self.view.subviews)
    {
        if ([field respondsToSelector:@selector(setDelegate:)])
        {
            field.delegate = self;
        }
    }

In this for loop, the -respondsToSelector: method call is necessary, otherwise your application will throw an NSException. In general, this is a fairly good method to use whenever you are dealing with the possibility of multiple types of objects in an array.

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 will 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 will be using two instances of the NSCalendar class in order to translate dates from the Gregorian calendar to the Hebrew calendar. You will make these properties of your class.

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

Go ahead and synthesize these two properties as you would any other property. You will, however, need to implement custom implementations of the getter methods for each of these 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 in order to make sure that your calendars are initialized with their correct calendar types. Alternatively, you could simply initialize your calendars in your -viewDidLoad method to be created when the app launches.

There are a large variety of different calendar types that are available for use, including but not limited to the following:

  • NSBuddhistCalendar
  • NSIslamicCalendar
  • 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.

-(void)convertToGregorian:(id)sender
{
    NSDateComponents *hComponents = [[NSDateComponents alloc] init];
    [hComponents setDay:[self.textFieldHDay.text integerValue]];
    [hComponents setMonth:[self.textFieldHMonth.text integerValue]];

    [hComponents setYear:[self.textFieldHYear.text integerValue]];

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

    NSUInteger unitFlags = NSDayCalendarUnit | NSMonthCalendarUnit |
    NSYearCalendarUnit;

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

    self.textFieldGDay.text = [[NSNumber numberWithInteger:hebrewDateComponents.day]
stringValue];
    self.textFieldGMonth.text = [[NSNumber numberWithInteger:hebrewDateComponents.month]
stringValue];
    self.textFieldGYear.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, etc. Here, only the month, day, and year are being specified.

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 this method is 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.

Since 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 incredibly simple, as you just need to change which text fields and calendar you use.

-(void)convertToHebrew:(id)sender
{
    NSDateComponents *gComponents = [[NSDateComponents alloc] init];
    [gComponents setDay:[self.textFieldGDay.text integerValue]];
    [gComponents setMonth:[self.textFieldGMonth.text integerValue]];
    [gComponents setYear:[self.textFieldGYear.text integerValue]];

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

    NSUInteger unitFlags = NSDayCalendarUnit | NSMonthCalendarUnit |
    NSYearCalendarUnit;

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

    self.textFieldHDay.text = [[NSNumber numberWithInteger:hebrewDateComponents.day]
stringValue];
    self.textFieldHMonth.text = [[NSNumber numberWithInteger:hebrewDateComponents.month]
stringValue];
    self.textFieldHYear.text = [[NSNumber numberWithInteger:hebrewDateComponents.year]
stringValue];
}

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

Image

Figure 8–2. Application successfully converting calendar dates

Recipe 8–2: Fetching Events

Now that you have covered how to deal with basic date conversions and calculations, you can go much more into detail on dealing specifically with events and calendars, including interacting with the user's own events and schedule. The next few recipes will all compound in order to create a complete utilization of the Event Kit framework.

First, create a new project using the Single View Application template. I have called mine “Chapter8Recipe2”, with class prefix “Main”.

Next, you need to import the Event Kit framework into your project. You can refer to earlier recipes on exactly how to do this.

In order to use your Event Kit framework, you need to add the following line to your main view controller's header file:

#import <EventKit/EventKit.h>

For this recipe, you will simply be accessing and logging your device's already scheduled events, so you will not be dealing at all with your view controller's XIB file.

Whenever you're dealing with the Event Kit framework, the main element you will be working with most is your EKEventStore. This class will be an incredibly powerful tool in allowing you to access, delete, and save events to your calendars. You will create a property of this type in your header file like so:

@property (strong, nonatomic) EKEventStore *eventStore;

Make sure to @synthesize this property, and then set it to nil in your -viewDidUnload.

The implementation for this first recipe to log your events is incredibly simple. You will simply modify your -viewDidLoad. After initializing your eventStore property, you will create a specific version of the NSPredicate class that will allow you to query for events. Specifically, you will query for all the dates in your device's calendar within the next 48 hours. Your method will look like so:

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.eventStore = [[EKEventStore alloc] init];
    NSDate *now = [NSDate date];
    NSDate *tomorrow = [NSDate dateWithTimeIntervalSinceNow:(2*24.0*60*60)];
    NSPredicate *predicate = [self.eventStore predicateForEventsWithStartDate:now
endDate:tomorrow calendars:nil];
    NSArray *events = [self.eventStore eventsMatchingPredicate:predicate];
    for (EKEvent *event in events)
    {
        NSLog(@"%@", event.title);
    }
}

The +date method is a nice little way to very easily get the current date and time represented as an NSDate. The +dateWithTimeIntervalSinceNow: method takes a float value as a parameter, representing the number of seconds you want your time interval to include. A quick calculation of 2*24*60*60 seconds gives you a two-day range for which to query.

You create your predicate using the -predicateForEventsWithStartDate:endDate:calendars: method for the EKEventStore class. 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.

To finally get your array of EKEvents, you simply call the EKEventStore method -eventsMatchingPredicate: as shown in the previous snippet, and you can now iterate through your events and act accordingly.

You will need to test this application on a device in order for it to access your calendar and print valid information. Make sure that your device actually has events scheduled to serve as your test data. Since the only output you are creating here is in the log, you will also need to run this application on your device from Xcode, so as to capture the output.

Recipe 8–3: Displaying Events in a UITableView

Now that you are able to access your events, you will start by creating a better interface with which to deal with them. You will implement a grouped UITableView to display your events.

First, you need to add your UITableView into your XIB file. Go ahead and drag out a UITableView element from your library, and make it take up the entire view. In the Attribute inspector, make sure that the “Style” of the table view is set to “Grouped”, as shown in Figure 8–3.

Image

Figure 8–3. Configuring your grouped UITableView

Next, connect your UITableView to your view controller's header file. The property used here will be called tableViewEvents.

Before you switch over to your implementation file, you will define two more properties that you will use. First, an NSArray called calendars will be used to hold references to all the calendars in your EKEventStore. Second, an instance of NSMutableDictionary will be used to store all your events based on which calendar they belong to. Make sure to properly synthesize these, and also make sure they are set to nil in -viewDidUnload.

@property (nonatomic, strong) NSMutableDictionary *events;
@property (nonatomic, strong) NSArray *calendars;

The first thing you need to do is set up a newer version of your -viewDidLoad method in order to properly populate your array and dictionary, as well as to make sure your UITableView is properly set up.

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.tableViewEvents.delegate = self;
    self.tableViewEvents.dataSource = self;

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

    [self fetchEvents];
}

The new -fetchEvents method will contain your code to actually query the eventStore for your events. Since you will be sorting your events by the calendar they belong to, you will perform a different query for each calendar, rather than just one for all events.

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

    for (EKCalendar *cal in self.calendars)
    {
        NSPredicate *calPredicate = [self.eventStore
predicateForEventsWithStartDate:[NSDate date] endDate:[NSDate
dateWithTimeIntervalSinceNow:(2*24.0*60*60)] calendars:[NSArray arrayWithObject:cal]];
        //Passing nil to calendars means to search all calendars.

        NSArray *eventsInThisCalendar = [self.eventStore eventsMatchingPredicate:calPredicate];
        if (eventsInThisCalendar != nil)
        {
            [self.events setObject:eventsInThisCalendar forKey:cal.title];
        }
    }
}

As you can see, you will be storing your events in an NSMutableDictionary by using the titles of your calendars as keys, with their objects being arrays of events that belong to that specific calendar.

Before you continue, you need to specify that your view controller will conform to certain protocols in order for your compile to allow it to be set as the delegate and data source of your view controller. After adding the UITableViewDelegate and UITableViewDataSource protocols, your header file should now resemble the following:

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

@interface MainViewController : UIViewController <UITableViewDelegate,
UITableViewDataSource>{
    UITableView *tableViewEvents;
}

@property (nonatomic, strong) EKEventStore *eventStore;
@property (strong, nonatomic) IBOutlet UITableView *tableViewEvents;

@property (nonatomic, strong) NSMutableDictionary *events;
@property (nonatomic, strong) NSArray *calendars;
@end

In order to properly implement a grouped UITableView, you need to implement a method to specify exactly how many sections you will need. Since you have one section per calendar, 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, in an equally simple manner.

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

You will 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
{
    NSString *title = [[self.calendars objectAtIndex:section] title];
    NSArray *eventsInThisCalendar = [self.events objectForKey:title];
    return [eventsInThisCalendar count];
}

Finally, you can build your most important method for dealing with a UITableView, which will define 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.events objectForKey:[[self.calendars
objectAtIndex:indexPath.section] title]] objectAtIndex:indexPath.row] title];

    return cell;
}

As you can see, aside from a fairly generic setup of your cell's view, all you have done is specify the title of any given row through a short, albeit slightly convoluted, call to your array and dictionary.

Your application should now be able to give you a nice display of all the events currently in your calendar!

Recipe 8–4: Viewing, Editing, and Deleting Events

Next, you can look into how to allow your user to view, edit, and delete events through pre-defined classes in the Event Kit UI framework.

Continuing from your previous recipe, the first thing you need to do is add another framework to your project. This time, add EventKitUI.framework. Import this framework's header file into your class with an import statement.

#import <EventKitUI/EventKitUI.h>

In this recipe, you will be assigning your view controller as the delegate for a couple of other view controllers, so you will go ahead and add their protocols to your header file. Here is the total list of protocols your view controller should now contain:

  • UITableViewDelegate
  • UITableViewDataSource
  • EKEventViewDelegate
  • EKEventEditViewDelegate

First, you will implement behavior for when a user selects a specific row in your UITableView. You will use an instance of the EKEventViewController to display information on the selected event. To do this, you use the UITableView's data source method, -tableView:DidSelectRowAtIndexPath:.

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath
*)indexPath
{
    EKEventViewController *eventVC = [[EKEventViewController alloc] init];
    eventVC.event = [[self.events objectForKey:[[self.calendars
objectAtIndex:indexPath.section] title]] objectAtIndex:indexPath.row];
    eventVC.delegate = self;
    eventVC.allowsEditing = YES;
    UINavigationController *navcon = [[UINavigationController alloc]
initWithRootViewController:eventVC];
    [self presentModalViewController:navcon animated:YES];
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}

You will need to implement the EKEventViewController's delegate method, -eventViewController:didCompleteWithAction:, in order to properly handle the completed use of your event's details. Here, you have a fairly simple implementation that either saves or deletes the event from your event store, depending on the action given.

-(void)eventViewController:(EKEventViewController *)controller
didCompleteWithAction:(EKEventViewAction)action
{
    EKEvent *event = controller.event;
    if (action == EKEventViewActionDeleted)
        {
            [self.eventStore removeEvent:event span:EKSpanThisEvent error:nil];
        }
    else
    {
        [self.eventStore saveEvent:event span:EKSpanFutureEvents error:nil];
    }
    [self fetchEvents];
    [self.tableViewEvents reloadData];
    [self dismissModalViewControllerAnimated:YES];
}

The values you have used for the span: parameter of the saving and deleting methods are restricted to one of the two values you have used here. As you can probably tell, they specify whether your save or removal should apply only to the specific event, or to all of its recurrences as well.

Keep in mind that the fact that the user deletes an event in the EKEventViewController does not mean that it has been removed from the eventStore. You need to manually remove it, and then call your -fetchEvents method as well as your table's -reloadData method to update your UITableView.

For extra functionality, you will also make your cell's detail disclosure buttons allow the user to proceed directly to editing an event's information through the use of the EKEventEditViewController. First, you will implement a method to handle the pressing of the disclosure buttons.

-(void)tableView:(UITableView *)tableView
accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
{
    EKEventEditViewController *eventEditVC = [[EKEventEditViewController alloc] init];
    eventEditVC.event = [[self.events objectForKey:[[self.calendars
objectAtIndex:indexPath.section] title]] objectAtIndex:indexPath.row];
    eventEditVC.eventStore = self.eventStore;
    eventEditVC.editViewDelegate = self;
    [self presentModalViewController:eventEditVC animated:YES];
}

Just like with the EKEventViewController, you need to define a delegate method for your EKEventEditViewController.

-(void)eventEditViewController:(EKEventEditViewController *)controller
didCompleteWithAction:(EKEventEditViewAction)action
{
    if (action == EKEventEditViewActionDeleted)
    {
        [self.eventStore removeEvent:controller.event span:EKSpanThisEvent error:nil];
    }
    else if (action == EKEventEditViewActionSaved)
    {
        [self.eventStore saveEvent:controller.event span:EKSpanThisEvent error:nil];
    }
    [self fetchEvents];
    [self.tableViewEvents reloadData];
    [self dismissModalViewControllerAnimated:YES];
}

Finally, you need to define one extra method in order to use an EKEventEditViewController, which will allow you to specify one calendar to be used for the creation of new events. In this implementation, you will simply return the default calendar for this task that the device is already using.

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

Just to ensure that your view always displays the most recent information, you will add to your -viewwillAppear: method in order to consistently refresh both your current data as well as your UITableView. The method will now appear like so:

- (void)viewWillAppear:(BOOL)animated
{
    [self fetchEvents];
    [self.tableViewEvents reloadData];
    [super viewWillAppear:animated];
}

At this point, your application can now successfully allow the user to view and edit the details of an event in two different ways, through the use of either an EKEventViewController (Figure 8–4) or an EKEventEditViewController (Figure 8–5).

Image

Figure 8–4. EKEventViewController

Image

Figure 8–5. EKEventEditViewControl

Recipe 8–5: Creating Simple Events

While it is fairly simple to allow users to create an event by themselves, we, as developers, should always attempt to simplify the lives of the users. 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, such that users never even have to see an EKEventEditViewController.

Again, building off of the previous recipe, you will first go into your view controller's XIB file.

Add a UIToolbar element to the top of your view. You will have to move the top of your UITableView down by the height of the toolbar, which defaults to 44 points. Go ahead and delete the UIBarButtonItem that is inside the toolbar by default; you will implement the items inside your toolbar programmatically.

Connect your UIToolbar to your view controller's header file using the property name toolBarTop.

You will be adding a button to your toolBarTop to allow you to create a new EKEvent, so you will define in advance the header for your method to perform this task.

-(void)addPressed:(UIButton *)sender;

Next, you will edit your -viewDidLoad method to accommodate your newest features.

- (void)viewDidLoad
{
    [super viewDidLoad];

    ///////////////START OF NEW CODE
    UIBarButtonItem *addButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self
action:@selector(addPressed:)];
    UIBarButtonItem *fixedSpace = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:NULL];
    fixedSpace.width = 265;
    [self.toolBarTop setItems:[NSArray arrayWithObjects:fixedSpace, addButton, nil]];
    ///////////////END OF NEW CODE

    self.tableViewEvents.delegate = self;
    self.tableViewEvents.dataSource = self;

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

    [self fetchEvents];
}

When your addButton is pressed, you will have your application present a modal view controller in order to take an input from the user as to the name of the new event to be created. In order to do this, you need to create another view controller from scratch.

Go to File Image New File…, and make sure to choose the “UIViewController subclass” option, as shown in Figure 8–6.

Image

Figure 8–6. Subclassing UIViewController

Name your subclass “EventAddViewController”, making sure that it will be created with a XIB file, as is done in Figure 8–7.

Image

Figure 8–7. Configuring your new view controller

Set up your new view controller's XIB file to resemble the one in Figure 8–8.

Image

Figure 8–8. User interface for event creation

Remember that here you are using a UITextField, which means at some point the bottom half of your view will be taken up by a keyboard, so you must take care not to let anything be hidden by this unnecessarily.

Connect your UITextField as well as your UIButton to your view controller's header file with the respective property names textFieldTitle and doneButton. Define also the header for an action called -donePressed:, as shown here, and connect doneButton to this action.

-(IBAction)donePressed:(id)sender;

Rather than have this class do any specific work with the Event Kit, you will simply have it return the submitted string to a delegate view controller. In order to do this, you first must declare a protocol for the delegate to conform to, by adding the following protocol declaration to the top of your header file:

@protocol EventAddViewControllerDelegate <NSObject>

-(void)EventAddViewController:(EventAddViewController *)controller
didSubmitTitle:(NSString *)title;

@end

Your compiler will complain about this circular reference of a class to itself before it has been declared, so add this extra line above the protocol declaration in order to assure the compiler that the EventAddViewController is, in fact, declared.

-@class EventAddViewController;

Since, as noted previously, you are using a UITextField, you should have your new view controller conform to the UITextFieldDelegate protocol. Make this so.

Finally, declare a property of type id that conforms to your protocol for this view controller called delegate. Make sure to properly synthesize and handle this in your implementation file as always.

@property (strong, nonatomic) id <EventAddViewControllerDelegate> delegate;

In its entirety, your header file should now look like so:

#import <UIKit/UIKit.h>

@class EventAddViewController;

@protocol EventAddViewControllerDelegate <NSObject>
-(void)EventAddViewController:(EventAddViewController *)controller
didSubmitTitle:(NSString *)title;
@end

@interface EventAddViewController : UIViewController <UITextFieldDelegate>

@property (strong, nonatomic) IBOutlet UITextField *textFieldTitle;
@property (strong, nonatomic) IBOutlet UIButton *doneButton;
@property (strong, nonatomic) id <EventAddViewControllerDelegate> delegate;
-(IBAction)donePressed:(id)sender;

@end

Your implementation for this view controller will be incredibly simple, as its function will be served entirely by a delegate method.

First, you must modify your -viewDidLoad method to set your UITextField's delegate.

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.textFieldTitle.delegate = self;
}

Second, you implement a very simple UITextField delegate method to handle the pressing of the “return” button.

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

Finally, you implement your -donePressed: method to call your view controller's delegate method.

-(void)donePressed:(id)sender
{
    [self.delegate EventAddViewController:self didSubmitTitle:self.textFieldTitle.text];
}

You can now switch back to your MainViewController. The first thing to remember is to add the newly created protocol to your view controller, so that, in its final stage, your header file reads like so:

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

@interface MainViewController : UIViewController <UITableViewDelegate,
UITableViewDataSource, EKEventViewDelegate, EKEventEditViewDelegate,
EventAddViewControllerDelegate>

@property (strong, nonatomic) IBOutlet UIToolbar *toolBarTop;

@property (nonatomic, strong) EKEventStore *eventStore;
@property (strong, nonatomic) IBOutlet UITableView *tableViewEvents;

@property (nonatomic, strong) NSMutableDictionary *events;
@property (nonatomic, strong) NSArray *calendars;
-(void)addPressed:(UIButton *)sender;
@end

You can now implement the -addPressed: method that you hinted at earlier to display your new view controller.

-(void)addPressed:(UIButton *)sender
{
    EventAddViewController *addVC = [[EventAddViewController alloc] init];
    addVC.delegate = self;
    [self presentModalViewController:addVC animated:YES];
}

Finally, you can create your EventAddViewController's delegate method, which will take the submitted string, and create a new event with it.

-(void)EventAddViewController:(EventAddViewController *)controller
didSubmitTitle:(NSString *)title
{
    EKEvent *event = [EKEvent eventWithEventStore:self.eventStore];
    event.title = title;
    event.calendar = [self.eventStore defaultCalendarForNewEvents];
    event.startDate = [NSDate dateWithTimeIntervalSinceNow:60*60*24.0];
    event.endDate = [NSDate dateWithTimeInterval:60*60.0 sinceDate:event.startDate];
    [self.eventStore saveEvent:event span:EKSpanThisEvent error:nil];
    [self fetchEvents];
    [self.tableViewEvents reloadData];
    [self dismissModalViewControllerAnimated:YES];
}

I have chosen an incredibly simple method for creating these new events for the sake of demonstration, as you can see in the fact that you have simply made any events created in this way always be a day in advance, and last only an hour. Most likely in your application you would choose a more complex or user-input-based method for creating EKEvents.

NOTE: The code we have used here may generate a “Could not load source: 6” log in Xcode. Do not worry about this, as it does not hinder your app from functioning correctly.

Recipe 8–6: Recurring Events

The Event Kit framework provides an incredibly powerful API for developers to be able to programmatically work with recurring events. Here, you will simply add to the previous recipes in order to have your application also add in a recurring event to your calendar.

In your -EventAddViewController:didSubmitTitle: method that you have created, you will simply add in extra code to create a recurring EKEvent based on the same title as the normal one. Start by adding the following code to set up the new EKEvent.

EKEvent *recurringEvent = [EKEvent eventWithEventStore:self.eventStore];
    recurringEvent.title = [NSString stringWithFormat:@"Recurring %@", title];
    recurringEvent.calendar = [self.eventStore defaultCalendarForNewEvents];
    recurringEvent.startDate = [NSDate dateWithTimeIntervalSinceNow:60*60*24.0];
    recurringEvent.endDate = [NSDate dateWithTimeInterval:60*60.0
sinceDate:event.startDate];

Next, you must 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, due to the sheer possibility of recurrence combinations. With only one method, a developer can create nearly any combination of recurrences imaginable. You will define a slightly complex one here:

EKRecurrenceRule *rule = [[EKRecurrenceRule alloc]
                              initRecurrenceWithFrequency:EKRecurrenceFrequencyDaily
                              interval:2
                              daysOfTheWeek:[NSArray
arrayWithObjects:[EKRecurrenceDayOfWeek dayOfWeek:2], [EKRecurrenceDayOfWeek
dayOfWeek:3], nil]
                              daysOfTheMonth:nil
                              monthsOfTheYear:nil
                              weeksOfTheYear:nil
                              daysOfTheYear:nil
                              setPositions:nil
                              end:[EKRecurrenceEnd
recurrenceEndWithOccurrenceCount:20]];

You would then add this rule to your event with the following line of code. The recurrenceRules property used is inherited from the EKCalendarItem class as of iOS 5.0.

recurringEvent.recurrenceRules = [NSArray arrayWithObject:rule];

The function of each parameter of this method is listed as follows. For any parameter, passing a value of nilindicates a lack of restriction.

  • InitRecurrenceWithFrequency: This specifies a basic level of how often the event will repeat, 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 will repeat every three weeks.
  • DaysOfTheWeek: This property 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.
  • MonthsOfTheYear: Similar to DaysOfTheWeek, this parameter specifies which months to restrict a recurring event to. It is 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: This parameter 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 two days, but only on either a Monday or a Tuesday, up to a limit of 20 occurrences.

Based on the different functionalities you have implemented, you should be able to see exactly how much possibility there is in using the Event Kit framework to interact with a user's schedule and events. Regardless of whether you wish to allow a user to interact with his or her schedule or you prefer a more programmatic, behind-the-scenes approach, the tools needed to perform your goal are easily available and incredibly simple to use, despite having immense flexibility.

Recipe 8–7: Basic Address Book Access

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

First, make a new project called “Chapter8Recipe7”, with class prefix “Main”, using the Single View Application template.

You will need to add in two extra frameworks to your project for this recipe: AddressBook.framework, and AddressBookUI.framework.

Next, switch over to your view controller's XIB file, and make a view that resembles the one in Figure 8–9.

Image

Figure 8–9. XIB file for accessing contact info

Connect these elements to your header file using the following property names:

  • firstLabel
  • lastLabel
  • phoneLabel
  • cityLabel
  • stateLabel

You will not need a property for the UIButton, as you will not need to make any changes to it for this recipe.

Define an action in your header file for your button to perform called -findPressed:, like so:

-(IBAction)findPressed:(id)sender;

Now that your interface is set up, you will make sure that your header file is correctly written. First, add the following two import statements to make sure that you can use your Address Book and Address Book UI frameworks.

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

You will be using an instance of the class ABPeoplePickerNavigationController, and setting its peoplePickerDelegate property to your view controller, so you need to add a protocol implementation to your header file. Make your view controller conform to the ABPeoplePickerNavigationControllerDelegate protocol. Your header file, in its entirety, should now look like so:

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

@interface MainViewController : UIViewController
<ABPeoplePickerNavigationControllerDelegate>

@property (strong, nonatomic) IBOutlet UILabel *firstLabel;
@property (strong, nonatomic) IBOutlet UILabel *lastLabel;
@property (strong, nonatomic) IBOutlet UILabel *phoneLabel;
@property (strong, nonatomic) IBOutlet UILabel *stateLabel;
@property (strong, nonatomic) IBOutlet UILabel *cityLabel;
-(IBAction)findPressed:(id)sender;
@end

Switch over to your implementation file, and implement a simple -viewDidLoad method to reset the text on your UILabels.

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.firstLabel.text = @"...";
    self.lastLabel.text = @"...";
    self.phoneLabel.text = @"...";
    self.cityLabel.text = @"...";
    self.stateLabel.text = @"...";
}

You will implement the -findPressed: method you defined earlier to create an instance of ABPeoplePickerNavigationController, set its delegate, and then present it modally.

-(void)findPressed:(id)sender
{
    ABPeoplePickerNavigationController *picker =[[ABPeoplePickerNavigationController
alloc] init];
    picker.peoplePickerDelegate = self;
    [self presentModalViewController:picker animated:YES];
}

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 dismissModalViewControllerAnimated:YES];
}

Next, you will define your main delegate method to handle the selection of a contact. You will go through this method implementation step-by-step 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 pre-defined functions that utilize and access it. As you will see, many parts of the Address Book framework utilize this more “C-based” style.

Inside the method body, you will first perform your easiest accesses, which will be for the first and last names of the chosen contact.

self.firstLabel.text = (__bridge_transfer NSString *)ABRecordCopyValue(person,
kABPersonFirstNameProperty);
self.lastLabel.text = (__bridge_transfer NSString *)ABRecordCopyValue(person,
kABPersonLastNameProperty);

The ABRecordCopyValue() function will be your go-to call for any kind of accessing data in this section. It takes two parameters, the first being the ABRecordRef that you want to access, and the second being a pre-defined 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.

The __bridge_transfer command is new to iOS 5 with Automatic Reference Counting (ARC), and simply specifies that ARC will now handle the value being “bridged.”

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 the 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.

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

By using the call ABMultiValueCopyValueAtIndex(multi, 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.

Since you created a new CFStringRef phoneNumber to point to your value, you should release it with the CFRelease() command, as shown in the previous snippet.

The next multi-value you will deal with will be 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 will retrieve this dictionary using the ABMultiValueCopyValueAtIndex() function again, and then query its values:

ABMultiValueRef address = ABRecordCopyValue(person, kABPersonAddressProperty);
    if (ABMultiValueGetCount(address) > 0)
    {
        CFDictionaryRef dictionary = ABMultiValueCopyValueAtIndex
        (address, 0);
        CFStringRef cityKey = kABPersonAddressCityKey;
        CFStringRef stateKey = kABPersonAddressStateKey;
        self.cityLabel.text = (__bridge_transfer NSString
*)CFDictionaryGetValue(dictionary, (void *)cityKey);
        self.stateLabel.text = (__bridge_transfer NSString
*)CFDictionaryGetValue(dictionary, (void *)stateKey);

        CFRelease(dictionary);
        CFRelease(cityKey);
        CFRelease(stateKey);
    }
    else
    {
        self.cityLabel.text = @"...";
        self.stateLabel.text = @"...";
    }

Finally, you can dismiss your modal view controller, as well as add your return value for this method. As a whole, your method should look like so:

-(BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController
*)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person
{
    self.firstLabel.text = (__bridge_transfer NSString *)ABRecordCopyValue(person,
kABPersonFirstNameProperty);
    self.lastLabel.text = (__bridge_transfer NSString *)ABRecordCopyValue(person,
kABPersonLastNameProperty);

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

    ABMultiValueRef address = ABRecordCopyValue(person, kABPersonAddressProperty);
    if (ABMultiValueGetCount(address) > 0)
    {
        CFDictionaryRef dictionary = ABMultiValueCopyValueAtIndex
        (address, 0);
        CFStringRef cityKey = kABPersonAddressCityKey;
        CFStringRef stateKey = kABPersonAddressStateKey;
        self.cityLabel.text = (__bridge_transfer NSString
*)CFDictionaryGetValue(dictionary, (void *)cityKey);
        self.stateLabel.text = (__bridge_transfer NSString
*)CFDictionaryGetValue(dictionary, (void *)stateKey);

        CFRelease(dictionary);
        CFRelease(cityKey);
        CFRelease(stateKey);
    }
    else
    {
        self.cityLabel.text = @"...";
        self.stateLabel.text = @"...";
    }

    [self dismissModalViewControllerAnimated:YES];
    return NO;
}

There is a third method you must implement in order to correctly conform to your protocol, which handles the selection of a specific contact's property. However, since this recipe is simply returning after the selection of a contact, this method will not actually be called. You will give it a simple implementation similar to your cancellation method.

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

CAUTION: Whenever you are copying values from an ABRecordRef, include a check to make sure that a value exists, like 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 should now be able to access the address book, select a user, and display the information for which you have queried, as demonstrated by Figures 8–10 and 8–11.

Image

Figure 8–10. Address book contact listing

Image

Figure 8–11. Your application's display of contact info

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 8–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, make a new project called “Chapter8Recipe8”, with the class prefix “Main”, using the Single View Application template.

Add in AddressBook.framework and AddressBookUI.framework to your project just as you have been doing.

You will start out with an incredibly simple user interface that allows users to create a new contact themselves, so in your view controller's XIB file, add a single UIButton, connected to an action called -newContactPressed:, as shown by Figure 8–12.

Image

Figure 8–12. Simple XIB 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, you will create an incredibly 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.

Your action method will look like so:

-(void)newContactPressed:(id)sender
{
    ABNewPersonViewController *view = [[ABNewPersonViewController alloc] init];
    view.newPersonViewDelegate = self;

    UINavigationController *newNavigationController = [[UINavigationController alloc]
initWithRootViewController:view];
    [self presentModalViewController:newNavigationController animated:YES];
}

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 dismissModalViewControllerAnimated:YES];
}

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. Since 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 your simulated app in Figure 8–13 shows.

Image

Figure 8–13. 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.

First, modify your user interface to include a series of UITextFields, each with a Placeholder text value describing their use, resembling Figure 8–14.

Image

Figure 8–14. User interface for specifying new contact info

Connect each UITextField to your header file with the following respective property names:

  • textFieldFirst
  • textFieldLast
  • textFieldPhone
  • textFieldStreet
  • textFieldCity
  • textFieldState
  • textFieldZip

Now, you can implement your code to create a new contact. To do this, you must obtain an ABRecordRef through the function ABPersonCreate(), and then set its values. The overall code for this will appear like so, which can be added to your -newContactPressed: method.

ABMutableMultiValueRef multi =
    ABMultiValueCreateMutable(kABMultiStringPropertyType);
    CFErrorRef anError = NULL;
    ABMultiValueIdentifier multivalueIdentifier;
    bool didAdd, didSet;
    
    didAdd = ABMultiValueAddValueAndLabel(multi, (__bridge
CFStringRef)self.textFieldPhone.text,
                                          kABPersonPhoneMobileLabel,
&multivalueIdentifier);
    if (!didAdd)
    {
        NSLog(@"Error Adding Phone Number");
    }
    
    ABRecordRef aRecord = ABPersonCreate();
    
    ABRecordSetValue(aRecord, kABPersonFirstNameProperty, (__bridge
CFStringRef)self.textFieldFirst.text, nil);
    ABRecordSetValue(aRecord, kABPersonLastNameProperty, (__bridge
CFStringRef)self.textFieldLast.text, nil);
    
    didSet = ABRecordSetValue(aRecord, kABPersonPhoneProperty, multi, &anError);
    if (!didSet)
    {
        NSLog(@"Error Setting Phone Value");
    }
    CFRelease(multi);
    
    ABMutableMultiValueRef address =
    ABMultiValueCreateMutable(kABDictionaryPropertyType);
    
    // Set up keys and values for the dictionary.
    CFStringRef keys[5];
    CFStringRef values[5];
    keys[0] = kABPersonAddressStreetKey;
    keys[1] = kABPersonAddressCityKey;
    keys[2] = kABPersonAddressStateKey;
    keys[3] = kABPersonAddressZIPKey;
    keys[4] = kABPersonAddressCountryKey;
    values[0] = (__bridge CFStringRef)self.textFieldStreet.text;
    values[1] = (__bridge CFStringRef)self.textFieldCity.text;
    values[2] = (__bridge CFStringRef)self.textFieldState.text;
    values[3] = (__bridge CFStringRef)self.textFieldZip.text;
    values[4] = CFSTR("USA");
    
    CFDictionaryRef aDict = CFDictionaryCreate(kCFAllocatorDefault,(void *)keys,
                                               (void *)values,5,
                                               &kCFCopyStringDictionaryKeyCallBacks,
                                               &kCFTypeDictionaryValueCallBacks);
    
    ABMultiValueIdentifier dictionaryIdentifier;
    bool didAddAddress;
    didAddAddress = ABMultiValueAddValueAndLabel(address, aDict, kABHomeLabel,
&dictionaryIdentifier);
    if (!didAddAddress)
    {
        NSLog(@"Error Adding Address");
    }
    
    CFRelease(aDict);
    
    ABRecordSetValue(aRecord, kABPersonAddressProperty, address, nil);

By inspecting this code, you can view the different techniques for creating and setting each type of property, from single values, to multi-values, to the dictionary-based address multi-value.

As with before, you must use the __bridge command in order to move your NSString variables out of ARC.

You now have two choices as to how to implement the creation of your new person in your device. A very user-friendly method would be to then load up your ABNewPersonViewController as before, but this time give it the created ABPersonRef to populate itself with. You can do this by setting the controller's displayedPerson property, by adding the following line.

    view.displayedPerson = aRecord;

If you choose this option, your application will bring up the views shown in Figure 8–15 in order to create a new contact. Notice that the new contact's information must be approved by the user in order to complete its creation. Tap to edit any of the text fields in the contact if the Done button appears grayed out.

Image

Figure 8–15. On the left, your app specifying new contact information, with resulting contact created on the right

Your second option would be to scrap the use of the ABNewPersonViewController and simply create your contact programmatically, saving the user the extra step of approving the contact. Since you already have your ABPersonRef, this is actually fairly simply done in a few lines of code, which would replace the code to set up and display an ABNewPersonViewController.

ABAddressBookRef addressBook = ABAddressBookCreate();
ABAddressBookAddRecord(addressBook, aRecord, nil);
ABAddressBookSave(addressBook, nil);

The ABAddressBookCreate() function is very simple, and simply returns an ABAddressBookRef of your device's address book. After adding the record using ABAddressBookAddRecord(), you simply have to save your changes.

No matter which option chosen, you still need to release two more variables before the end of your method, like so:

CFRelease(aRecord);
CFRelease(address);

Recipe 8–9: Viewing Contacts

Now that we have gone over accessing and setting values, a very simple next step is to discuss how to set up a view controller to see a contact's details.

In a new project called “Chapter8Recipe9”, with class prefix “MainViewController”, after importing the Address Book and Address Book UI frameworks and adding the appropriate #import statements for each, add a UITableView to your view controller. Connect it to your header file with the property name tableViewContacts. Make your view controller conform to the UITableViewDelegate, UITableViewDataSource, and ABPersonViewControllerDelegate protocols as well.

Create a property of type (NSArray *) called contacts to store your contact list.

Overall, your header file should look like so:

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

@interface MainViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, ABPersonViewControllerDelegate>

@property (strong, nonatomic) IBOutlet UITableView *tableViewContacts;
@property (strong, nonatomic) NSArray *contacts;
@end

Before we continue, you will need to set your main view controller inside of a UINavigationController. Switch over to the implementation file for your app delegate, and change your -application:DidFinishLaunchingWithOptions: method to look like so:

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

Now, back in your view controller's implementation file, create a method to update your array of contacts like so:

-(void)updateContacts
{
    ABAddressBookRef addressBook = ABAddressBookCreate();
    CFArrayRef people = ABAddressBookCopyArrayOfAllPeople(addressBook);
    self.contacts = (__bridge NSArray *)people;
    CFRelease(people);
}

This should make your -viewDidLoad method very simple to implement.

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.tableViewContacts.delegate = self;
    self.tableViewContacts.dataSource = self;
    self.title = @"Contacts Table";
    [self updateContacts];
}

You should also change your -viewWillAppear: method to refresh your table in case of any changes made outside of the application.

- (void)viewWillAppear:(BOOL)animated
{
    [self updateContacts];
    [self.tableViewContacts reloadData];
    [super viewWillAppear:animated];
}

Now you implement your UITableView data source methods. You need one to specify the number of rows you have:

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [[NSNumber numberWithInt:[self.contacts count]] intValue];
}

You will have a fairly simple method for creating your cells, based on the accessing code that you used in previous recipes.

-(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 = UITableViewCellAccessoryDisclosureIndicator;
    cell.textLabel.backgroundColor = [UIColor clearColor];
    cell.textLabel.font = [UIFont systemFontOfSize:19.0];
    
    ABRecordRef current = (__bridge ABRecordRef)[self.contacts
objectAtIndex:indexPath.row];
    
    NSString *firstName = (__bridge_transfer NSString *)ABRecordCopyValue(current,
kABPersonFirstNameProperty);
    NSString *lastName = (__bridge_transfer NSString *)ABRecordCopyValue(current,
kABPersonLastNameProperty);
    
    cell.textLabel.text = [NSString stringWithFormat:@"%@ %@", firstName, lastName];

    return cell;
}

The third data source method you need is for the selection of a cell, which will present an instance of ABPersonViewController with the selected contact's information.

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath
*)indexPath
{
    ABRecordRef chosen = (__bridge ABRecordRef)[self.contacts
objectAtIndex:indexPath.row];
    
    ABPersonViewController *view = [[ABPersonViewController alloc] init];
    view.personViewDelegate = self;
    view.displayedPerson = chosen;
    view.allowsEditing = NO;
    
    [self.navigationController pushViewController:view animated:YES];
    [tableView deselectRowAtIndexPath:indexPath animated:NO];
}

Finally, you just need to implement the ABPersonViewController's delegate method to handle the selection of a property. This method can simply return a BOOL that will decide whether the user will be able to call or text a phone number, or any other default property for a value, from your application.

-(BOOL)personViewController:(ABPersonViewController *)personViewController
shouldPerformDefaultActionForPerson:(ABRecordRef)person property:(ABPropertyID)property
identifier:(ABMultiValueIdentifier)identifier
{
    return YES;
}

Your application should have a very rudimentary table view displaying the names of all the contacts in your phone, assuming that you have only individuals listed in your contact list, as opposed to any groups. Figure 8–16 demonstrates the resulting view of selecting a record in your custom-made contact list. Whenever dealing with an ABRecordRef for which you are not sure whether it is an ABPersonRef or an ABGroupRef, you should always include code to check for each case and act accordingly.

Image

Figure 8–16. Contact info displayed after selecting a row in your table

Summary

As you can see, there are a great variety of 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 of this information can be used to personalize an application for each and every customer, while developing generically for all of them. 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, which in the end will translate to a happier customer, and a higher-quality product.

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

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