The app we'll build in this tutorial is going to let users get connected to their Google account, download their calendars, and create a new event with a description and a date/time. The new event will be posted to a calendar that the user selects.
2. www.letsnurture.com
Project Overview
The app we'll build in this tutorial is going to let users
get connected to their Google account, download their
calendars, and create a new event with a description
and a date/time. The new event will be posted to a
calendar that the user selects.
Regarding our app structure, the basic view is going to
be a table view that will contain three sections for
setting the following data:
An event description
An event date/time
A target calendar
3. www.letsnurture.com
As far as the event description is concerned, a textfield will
appear on the cell when the user edits the description and it will
go away when he finishes doing so. For ease of use, an Input
Accessory View will appear above the keyboard every time that
the textfield is displayed.
For setting or changing the event date and time, another view
(not a view controller) is going to be used. This view will contain
a date picker view, from which the user will be able to pick a
date.
An event can be an all-day event, so no specific time needs to
be set. For this case, a button will be used to set the date picker
contents. For events occurring at a specific time, the date picker
will display both date and time. For all-day events, only the date
will be displayed. The view that contains the date picker (and a
toolbar with the necessary bar button items as well) will be
added as a subview to the view of our view controller when the
user taps on the row of the second section of the table view.
4. www.letsnurture.com
Finally, the calendar section is going to be multi-functional.
When the user is not yet signed into their Google account,
only one row is going to exist in the section with a message
that prompts the user to download their calendars. When the
user selects this, the authorization process will be put in
action.
Once the access token has been obtained, an API call will be
made to get the calendar list from Google. After the calendar
list has been taken, the first calendar on the list is going to
replace the prompting message on the row and become the
selected calendar by default.
In addition to all of the above, a toolbar will exist under the
table view and it will contain two bar button items. One button
will be for posting the event on the selected calendar and a
second button will be used to log out.
5. www.letsnurture.com
1. Create a New Project
Step 1
Launch Xcode and create a new project. Select the Single View Application option
and click Next:
6. www.letsnurture.com
In the Product Name field, add the GoogleCalendarPostDemo value. Of
course, you may choose another name if you'd like.
Also, make sure to check the Use Automatic Reference Counting option and
uncheck everything else. After that, keep on going.
8. www.letsnurture.com
Step 2
Now you need to add the class files that were implemented in the
previous tutorial. This class is going to be the mechanism that will do all
the work behind the scenes. So, if you have the previous tutorial's project
files, get these two:
GoogleOAuth.h
GoogleOAuth.m
Add them into your project. If you don't have the previous project, then
you can download and get them from this post's download.
9. www.letsnurture.com
2. Building the Interface
We will use Interface Builder to setup the interface. As you will soon find
out, several subviews are going to be added because we want to make
the demo app as functional as it can be. Here is a series of steps that
describe every subview you should add, along with the properties for
each one:
Open the ViewController.xib file to configure the interface and to add all
the necessary subviews. First of all, set the view's Size to None in the
Utilities Pane > Attributes Inspector > Simulated Metrics, in order to let
the project work properly on iPhones prior to the 5.
10. www.letsnurture.com
Set the view's Background Color to White.
Drag-and-drop a UIToolbar subview into the view. Place it at the bottom
of the screen.
Add the following items on the toolbar (ordered left-to-right):
Bar Button Item with Title: Sign out
Flexible Space Bar Button Item
Bar Button Item with Title: Post
Add a UITableView subview on the view and let it occupy all the
available space left on the view.
Set the next two properties of the table view:
Style: Grouped
Background:: Clear Color
12. www.letsnurture.com
Next, we need to have another view that will contain the date picker
view. Here are the steps:
Add a new view outside of the default view and set its Size to None
(like you did before). Also, set its Height to 460.
Set the view's Background Color to Scroll View Textured Background
Color.
Add a UIDatePicker subview in the view and center it in accordance to
the view's center.
Add a UIToolBar subview at the bottom of the view.
Add the following bar button items to the toolbar:
Bar Button Item with Title: Cancel
Flexible Space Bar Button Item
Bar Button Item with Title: All-day event
Flexible Space Bar Button Item
Bar Button Item with Title: Okay
14. www.letsnurture.com
Finally, add the following subviews outside the default view
controller's view and in the second view we just created:
A UIToolBar subview. This is going to be the Input Accessory
View for the textfield that will be used to edit the event
description. Add the next bar button items to it:
Bar Button Item with Title: Cancel
Flexible Space Bar Button Item
Bar Button Item with Title: Okay
A UIActivityIndicatorView with the following properties:
Style: Large White
Background: Black Color
15. www.letsnurture.com
3. Setup IBOutlet Properties & IBAction Methods
Step 1
We are going to need a few IBOutlet properties connected to our subviews, so
we can modify them in code.
To connect an IBOutlet property to a subview (as well as to create an IBAction
method), you need to have the ViewController.h file shown in the Assistant
Editor.
So, click on the middle button of the Xcode Editor toolbar to let it show up.
Make sure that the contents of the ViewController.h file are displayed there.
16. www.letsnurture.com
I will show you how to create an IBOutlet property for the table view
only. Use the same way to create the properties for the subviews I'll
tell you about next.
Either on the Document Outline pane or directly on the view, do the
following:
1.Right-Click or Control-Click on the table view
2.On the black popup menu, click on the circle next to the New
Referencing Outlet option and keep the mouse button pressed.
3.Drag-and-drop (and at the same time a blue line will follow your
mouse) into the Assistant Editor window.
17. www.letsnurture.com
On the new window that appears, add the tblPostData as the Name of the
property and don't touch any other options.
Click on the Connect button or hit the Return button on your keyboard.
Here is a list with all the subviews that we need IBOutlet connections created,
along with their names. Make sure to follow the same way as before and you'll
be fine.
•Post bar button item: barItemPost
•Sign out bar button item: barItemRevokeAccess
•Input Accessory View Toolbar (the alone toolbar): toolbarInputAccessoryView
•View container of the date picker: viewDatePicker
•Date Picker: dpDatePicker
•All-day bar button item: barItemToggleDatePicker
•Activity Indicator View: activityIndicatorView
18. www.letsnurture.com
Step 2
As you see, we added some bar button items in our views. We require from
them to react on our taps, so we need to create IBAction methods to make
that happen.
Creating an IBAction method is almost the same as I previously
demonstrated. For the Post bar button item only, here is the procedure in
detail:
On either the Document Outline or directly on the view, right-click or control-
click on the bar button item.
On the black popup menu, under the Sent Actions section, click on the circle
next to the Selector option and keep the mouse button down.
Drag and drop on the Assistant Editor window
19. www.letsnurture.com
On the new window, add the post in the Name field and leave everything else
as it is. Click on Connect or hit the Return button on your keyboard.
Now, follow the same pattern and create the next IBAction methods for the
given subviews:
•Sign out bar button item: revokeAccess
•Okay bar button item on the Input Accessory View Toolbar:
acceptEditingEvent
•Cancel bar button item on the Input Accessory View Toolbar:
cancelEditingEvent
•Okay bar button item on the Date Picker View Toolbar: acceptSelectedDate
•Cancel bar button item on the Date Picker View Toolbar: cancelPickingDate
•All-day bar button item on the Date Picker View Toolbar: toggleDatePicker
20. www.letsnurture.com
After the addition of all the IBOutlet properties and all the IBAction methods,
your ViewController.h file should look like this:
@interface ViewController : UIViewController
@property (weak, nonatomic) IBOutlet UITableView *tblPostData;
@property (weak, nonatomic) IBOutlet UIBarButtonItem *barItemPost;
@property (weak, nonatomic) IBOutlet UIBarButtonItem
*barItemRevokeAccess;
@property (strong, nonatomic) IBOutlet UIToolbar
*toolbarInputAccessoryView;
@property (strong, nonatomic) IBOutlet UIView *viewDatePicker;
@property (weak, nonatomic) IBOutlet UIDatePicker *dpDatePicker;
@property (weak, nonatomic) IBOutlet UIBarButtonItem
*barItemToggleDatePicker;
@property (strong, nonatomic) IBOutlet UIActivityIndicatorView
*activityIndicatorView;
- (IBAction)post:(id)sender;
- (IBAction)revokeAccess:(id)sender;
- (IBAction)acceptEditingEvent:(id)sender;
- (IBAction)cancelEditingEvent:(id)sender;
- (IBAction)acceptSelectedDate:(id)sender;
- (IBAction)cancelPickingDate:(id)sender;
- (IBAction)toggleDatePicker:(id)sender;
@end
21. www.letsnurture.com
4. Adopting Protocols
Step 1
Now that the interface has been setup and configured
and the IBOutlet properties along with the IBAction
methods have been created and connected, it's time to
start writing some code.
Previously we added the GoogleOAuth header and
implementation files in the project, but that's not
enough to make our class work. We also need to
import it in the view controller's class and adopt its
protocol.
Open the ViewController.h file and import the
GoogleOAuth.h file at the top of the file:
22. www.letsnurture.com
Step 2
In addition to the GoogleOAuth class' protocol, we need to
adopt the following as well:
UITableViewDelegate: the delegate of the table view.
UITableViewDatasource: the datasource of the table view.
UITextFieldDelegate: the delegate of the textfield that will be
used to edit the event description.
So, while still within the ViewController.h file, modify the
@interface statement like this:
@interface ViewController : UIViewController <UITableViewDelegate,
UITableViewDataSource, UITextFieldDelegate, GoogleOAuthDelegate>
23. www.letsnurture.com
5. Declaring Private Properties & Methods
Step 1
There are some properties and some methods that should
be declared at the private section of the class and are
required to make everything work smoothly.
These properties are mostly going to store application
information, but some simple flags will also be used to
indicate program state.
The next code snippet presents all the private data
members you should add in your project (copy and paste
them if you'd like). The comments explain everything. Don't
forget that we are working now in the ViewController.m file!
24. www.letsnurture.com
@interface ViewController ()
// The string that contains the event description.
// Its value is set every time the event description gets edited and its
// value is displayed on the table view.
@property (nonatomic, strong) NSString *strEvent;
// The string that contains the date of the event.
// This is the value that is displayed on the table view.
@property (nonatomic, strong) NSString *strEventDate;
// This string is composed right before posting the event on the calendar.
// It's actually the quick-add string and contains the date data as well.
@property (nonatomic, strong) NSString *strEventTextToPost;
// The selected event date from the date picker.
@property (nonatomic, strong) NSDate *dtEvent;
// The textfield that is appeared on the table view for editing the event description.
@property (nonatomic, strong) UITextField *txtEvent;
25. www.letsnurture.com
// This array is one of the most important properties, as it contains
// all the calendars as NSDictionary objects.
@property (nonatomic, strong) NSMutableArray *arrGoogleCalendars;
// This dictionary contains the currently selected calendar.
// It's the one that appears on the table view when the calendar list
// is collapsed.
@property (nonatomic, strong) NSDictionary *dictCurrentCalendar;
// A GoogleOAuth object that handles everything regarding the Google.
@property (nonatomic, strong) GoogleOAuth *googleOAuth;
// This flag indicates whether the event description is being edited or not.
@property (nonatomic) BOOL isEditingEvent;
// It indicates whether the event is a full-day one.
@property (nonatomic) BOOL isFullDayEvent;
// It simply indicates whether the calendar list is expanded or not on the table
view.
@property (nonatomic) BOOL isCalendarListExpanded;
@end
28. www.letsnurture.com
6. Table View Datasource & Delegate Methods
Let's keep going by writing some datasource and delegate methods
related to the table view.
For the time being, we'll add only some standard code. As we move on,
we'll add even more code when it's required. So, let's go ahead. As I
said at the beginning, there are going to be three sections on the table
view:
(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 3;
}
Each section is going to contain one row. For the third section, we'll
have as many rows as the calendars support.
In this method, you can see how the _isCalendarListExpanded flag is
used for first time:
34. www.letsnurture.com
7. Editing the Event Description
Step 1
Until now, I have already said many times that a textfield is going to appear on
the table view every time that we want to change the event description.
However, when not editing, the textfield should not appear. After we finish
editing, we'll update the event description and make the textfield go away.
Before we bring this behavior to life, it might be better to implement the private
method that we have declared, the setupEventTextfield. In this method, we'll do
the following tasks:
• We will initialize the textfield by setting a frame related to the cell
content view's frame and set a style too.
• We'll set the contents of the strEvent string as its text.
• Remember the (alone) toolbar we added in the Interface Builder
earlier? We'll set it as the input accessory view of the textfield.
• We'll set self as its delegate so we can handle the Return key of the
keyboard.
39. www.letsnurture.com
Step 3
Now let's update the tableView:cellForRowAtIndexPath: so it reflects the state of
the first section at any given time:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:
(NSIndexPath *)indexPath{
...
...
...
if ([indexPath section] == 0) {
if (!_isEditingEvent) {
// If currently the event description is not being edited then just show
// the value of the strEvent string and let the cell contain a disclosure indicator accessory
view.
// Also, set the gray as the selection style.
[[cell textLabel] setText:_strEvent];
[cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
[cell setSelectionStyle:UITableViewCellSelectionStyleGray];
}
else{
// If the event description is being edited, then empty the textLabel text so as to avoid
// having text behind the textfield.
// Add the textfield as a subview to the cell's content view and turn the selection style to
none.
[[cell textLabel] setText:@""];
[[cell contentView] addSubview:_txtEvent];
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
}
}
}
49. www.letsnurture.com
- (IBAction)acceptSelectedDate:(id)sender
{
// Keep the selected date as a NSDate object.
_dtEvent = [_dpDatePicker date];
// Also, convert it to a string properly formatted depending on
whether the event is a full-day one or not
// by calling the getStringFromDate: method.
_strEventDate = [[NSString alloc] initWithString:[self
getStringFromDate:[_dpDatePicker date]]];
// Remove the view with the date picker from the self.view.
[_viewDatePicker removeFromSuperview];
// Reload the row of the second section of the table view to reflect
the selected date.
[_tblPostData reloadRowsAtIndexPaths:[NSArray arrayWithObject:
[NSIndexPath indexPathForRow:0 inSection:1]]
withRowAnimation:UITableViewRowAnimationAutomatic];
}
50. www.letsnurture.com
In the cancelPickingDate: IBAction method, we only
need to remove the date picker container view from
the superview.
- (IBAction)cancelPickingDate:(id)sender {
// Just remove the view with the date picker from
the superview.
[_viewDatePicker removeFromSuperview];
}
51. www.letsnurture.com
Step 4
In the acceptSelectedDate: method, we made a call to
the getStringFromDate: private method, which is
declared but not yet implemented. It's now time to work
with this method. Before I present the code, I should
make an observation.
The purpose of the getStringFromDate: method is to get
the date we provide (the selected date in our example)
and to return this date as a string and formatted the way
we want. However, there are two kind of string formats
we need to have, depending on whether the event is a
full-day one or not. If the event date has a specific time,
we want this time to be present on the string. This
means that we'll have a condition in our method that will
determine what the output string format will be. So,
having made our intention clear, enter the following
code:
52. www.letsnurture.com
-(NSString *)getStringFromDate:(NSDate *)date{
// Create a NSDateFormatter object to handle the date.
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
if (!_isFullDayEvent) {
// If it's not a full-day event, then set the date format in a way that
contains the time too.
[formatter setDateFormat:@"EEE, MMM dd, yyyy, HH:mm"];
}
else{
// Otherwise keep just the date.
[formatter setDateFormat:@"EEE, MMM dd, yyyy"];
}
// Return the formatted date as a string value.
return [formatter stringFromDate:date];
}
53. www.letsnurture.com
The above will give us something like Mon, Aug 12, 2013,
17:32.
For more information about the date symbols used here
and any other symbols that exist, look at the
Date Format Patterns.
Note: Keep in mind that you should change the date
symbol order in a real app in order to match the date
representation of your own country.
54. www.letsnurture.com
Step 5
So far so good: just one thing left to do. Add some code on the
tableView:cellForRowAtIndexPath: method so the selected date will be
displayed. Under all of the other contents of the method, add the next
snippet:
-(UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath{
...
...
...
else if ([indexPath section] == 1){
// In the event date cell just show the strEventDate string which
either prompts the user
// to pick a date, or contains the selected date as a string.
// Also, add a disclosure indicator view.
[[cell textLabel] setText:_strEventDate];
[cell
setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
}
return cell;
}
55. www.letsnurture.com
9. App Authorization
If you have already run the app, or if you do it now, you'll notice that the row
of the third section on the table view contains the Download calendars...
message.
Of course, nothing takes place when you tap on this method.
What we'd like to do on tap is to make an API call to Google and request
the information we need. But, prior to this, we must authorize ourselves
against the
Google service and obtain an access token that will be used to exchange
data.
Actually, all of this will be done by the GoogleOAuth class we included early
on.
All wee need to do is provide this class with the client ID, the client secret,
and the scope. So, let's pay a visit to the Google developers website and
get all the values we need.
56. www.letsnurture.com
Step 1
Go to the Google Developer website. Click on the Sign In button that
exists on the top-right side of the webpage to login.
57. www.letsnurture.com
After you have signed in, scroll down on the page until you locate the API
Console icon.
Click on it and you'll be transferred to your Dashboard, where you handle all
of your projects.
If you must, create a project now, otherwise select the project you created
from the previous tutorial in this series. On the menu at the left side of the
webpage, click on the API Access option.
58. www.letsnurture.com
Details about the current project will be displayed on the right side of the
page.
In there, you can track down the client ID and client secret values that
you will need. Note them, and let's move ahead.
59. www.letsnurture.com
Next, we need to tell Google that we want to use the Calendars service. To
do so, click on the Services option at the menu on the left side of the
webpage.
A list of all the provided services will appear. Locate the Calendar API item.
Click on the Off button to enable the specific service for the current project.
60. www.letsnurture.com
Step 2
What we haven't located yet is the scope value for getting the calendar info we need.
Just to remind you, a scope indicates an API that the app requests access for.
The best practice to locate what you want is to use the search engine for the Google
developer website. So, go to the homepage of the Google developer site and search
for the term Google Calendar API. In the results page, click on the first result.
ake a look around if you want and explore using the menus. For this tutorial,
you should go to Reference > Calendar List > list.
A new webpage is loaded, containing information about the calendar list API
call.
Find the Authorization area near the bottom of the page. There you can find
the scope we need: https://www.googleapis.com/auth/calendar.
61. www.letsnurture.com
Step 3
In the previous steps, I showed you how you can
enable a service on Google and the best way to find
what you need from the developer web site.
Now, we can keep building the app.
Now that we have access to all the data we need
from Google, let's get back to our app and see how
to use the information.
Append the following code to our project:
62. www.letsnurture.com
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:
(NSIndexPath *)indexPath{
...
...
...
else if ([indexPath section] == 2){
if (_arrGoogleCalendars == nil || [_arrGoogleCalendars count] == 0)
{
// If the arrGoogleCalendars array is nil or contains nothing, then
the calendars should be
// downloaded from Google.
// So, show the activity indicator view and authorize the user by
calling the the next
// method of our custom-made class.
[self showOrHideActivityIndicatorView];
[_googleOAuth authorizeUserWithClienID:@"YOUR_CLIENT_ID"
andClientSecret:@"YOUR_CLIENT_SECRET"
andParentView:self.view
andScopes:[NSArray
arrayWithObject:@"https://www.googleapis.com/auth/calendar"]];
}
}
63. www.letsnurture.com
You'll notice that above we display the indicator view as
well.
We do this because we don't know how long it'll take to
obtain authorization. Don't forget to set your own values for
the client ID and the client secret!
When you use the app for the first time, the embedded
web view will appear.
You must enter your credentials and sign into your Google
account to allow the app to access your calendars. If
everything goes okay, you'll be authorized without a
problem.
64. www.letsnurture.com
10. Downloading Calendar Data
Step 1
At this point, we need to implement some of the
GoogleOAuth delegate methods.
Let's begin with the authorizationWasSuccessful method,
where we'll handle a successful authorization by making
the API call for getting the calendar list.
However, we need the API URL string if we want to
proceed. Navigate to the proper calendar list page in your
Google developer account. At the top of the page you'll find
the URL along with the HTTP method that should be used.
65. www.letsnurture.com
Now we can implement the delegate method:
-(void)authorizationWasSuccessful{
// If user authorization is successful, then make an API call to
get the calendar list.
// For more infomation about this API call, visit:
// https://developers.google.com/google-
apps/calendar/v3/reference/calendarList/list
[_googleOAuth
callAPI:@"https://www.googleapis.com/calendar/v3/users/me/cal
endarList"
withHttpMethod:httpMethod_GET
postParameterNames:nil
postParameterValues:nil];
}
66. www.letsnurture.com
Step 2
If all works according to plan, the
responseFromServiceWasReceived:andResponseJSONAsD
ata: delegate method will be called by the GoogleOAuth
class.
We are responsible to check if Google responded with the
desired results, and then to keep only the data that we care
about.
Let's discuss a bit about what the response contains and
how we are going to manage the data.
What we should do first is to convert the response JSON
data into an NSDictionary object. If you NSLog this
dictionary, we'll see the way the returned data is formed. See
the following example:
68. www.letsnurture.com
Inside each curly bracket there is a block containing a
bunch of information regarding every calendar you have
created in Google Calendars.
The items object is equivalent to an array which contains
dictionaries as objects.
In other words, we will extract the items object as a
NSArray and we'll handle every single object of it as a
NSDictionary object.
69. www.letsnurture.com
Let's get back on track again. Once we acquire all the items
as NSArray objects, we'll go through a loop to access each
calendar's details and we'll keep only the values we want
for the purposes of this example.
Actually, we are going to create key-value pairs with these
values with the goal of creating new NSDictionaries, which
will be stored in the arrGoogleCalendars array. This array is
the calendar list for our app.
Also, the dictCurrentCalendar dictionary will be initialized
with the contents of the first calendar from the list. Once this
has been done, the Post and Logout items will become
enabled. We'll also hide the activity indicator view and we'll
refresh the table to show the selected calendar.
70. www.letsnurture.com
-(void)responseFromServiceWasReceived:(NSString *)responseJSONAsString
andResponseJSONAsData:(NSData *)responseJSONAsData{
NSError *error;
if ([responseJSONAsString rangeOfString:@"calendarList"].location !=
NSNotFound) {
// If the response from Google contains the "calendarList" literal, then the
calendar list
// has been downloaded.
// Get the JSON data as a dictionary.
NSDictionary *calendarInfoDict = [NSJSONSerialization
JSONObjectWithData:responseJSONAsData
options:NSJSONReadingMutableContainers error:&error];
if (error) {
// This is the case that an error occured during converting JSON data to
dictionary.
// Simply log the error description.
NSLog(@"%@", [error localizedDescription]);
}
71. www.letsnurture.com
else{
// Get the calendars info as an array.
NSArray *calendarsInfo = [calendarInfoDict
objectForKey:@"items"];
// If the arrGoogleCalendars array is nil then
initialize it so to store each calendar as a NSDictionary
object.
if (_arrGoogleCalendars == nil) {
_arrGoogleCalendars = [[NSMutableArray alloc]
init];
}
72. www.letsnurture.com
// Make a loop and get the next data of each calendar.
for (int i=0; i<[calendarsInfo count]; i++) {
// Store each calendar in a temporary dictionary.
NSDictionary *currentCalDict = [calendarsInfo objectAtIndex:i];
// Create an array which contains only the desired data.
NSArray *values = [NSArray arrayWithObjects:[currentCalDict
objectForKey:@"id"],
[currentCalDict objectForKey:@"summary"],
nil];
// Create an array with keys regarding the values on the previous
array.
NSArray *keys = [NSArray arrayWithObjects:@"id", @"summary",
nil];
// Add key-value pairs in a dictionary and then add this dictionary
into the arrGoogleCalendars array.
[_arrGoogleCalendars addObject:
[[NSMutableDictionary alloc] initWithObjects:values forKeys:keys]];
}
73. www.letsnurture.com
// Set the first calendar as the selected one.
_dictCurrentCalendar = [[NSDictionary alloc]
initWithDictionary:[_arrGoogleCalendars objectAtIndex:0]];
// Enable the post and the sign out bar button items.
[_barItemPost setEnabled:YES];
[_barItemRevokeAccess setEnabled:YES];
// Stop the activity indicator view.
[self showOrHideActivityIndicatorView];
// Reload the table view section.
[_tblPostData reloadRowsAtIndexPaths:[NSArray
arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:2]]
withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
}
74. www.letsnurture.com
Step 3
Now that we've done all the above, we want to be able to
tap on a calendar name and have the full list expand,
allowing us to select another calendar.
This will be done in the
tableView:didSelectRowAtIndexPath: method. Add the
following code snippet:
75. www.letsnurture.com
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:
(NSIndexPath *)indexPath{
...
else{
// In this case the calendars exist in the arrGoogleCalendars array.
if (_isCalendarListExpanded) {
// If the calendar list is shown on the table view, then the tapped one
shoule become the selected calendar.
// Re-initialize the dictCurrentCalendar dictionary so it contains the
information regarding the selected one.
_dictCurrentCalendar = nil;
_dictCurrentCalendar = [[NSDictionary alloc] initWithDictionary:
[_arrGoogleCalendars objectAtIndex:[indexPath row]]];
}
// Change the value of the isCalendarListExpanded which indicates
whether only the selected calendar is shown, or the
// whole list.
_isCalendarListExpanded = !_isCalendarListExpanded;
// Finally, reload the section.
[_tblPostData reloadSections:[NSIndexSet indexSetWithIndex:2]
withRowAnimation:UITableViewRowAnimationAutomatic];
}
} }
76. www.letsnurture.com
Step 4
Finally, we need to update the tableView:cellForRowAtIndexPath: method to
display everything we've done. Append the following code:
-(UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath{
...
...
...
else if ([indexPath section] == 2){
// This is the case where either the selected calendar is shown, or a list
with all of them.
if (!_isCalendarListExpanded) {
// If the calendar list is not expanded and only the selected calendar is
shown,
// then if the arrGoogleCalendars array is nil or it doesn't have any
contents at all prompt
// the user to download them now.
// Otherwise show the summary (title) of the selected calendar along
with a disclosure indicator.
if (![_arrGoogleCalendars count] || [_arrGoogleCalendars count] == 0)
{
[[cell textLabel] setText:@"Download calendars..."];
}
77. www.letsnurture.com
else{
[[cell textLabel] setText:[_dictCurrentCalendar
objectForKey:@"summary"]];
}
[cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
}
else{
// This is the case where all the calendars should be listed.
// Note that each calendar is represented as a NSDictionary which is read from
the // arrGoogleCalendars array.
// If the calendar that is shown in the current cell is the already selected one,
// then add the checkmark accessory type to the cell, otherwise set the
accessory type to none.
NSDictionary *tempDict = [_arrGoogleCalendars objectAtIndex:
[indexPath row]];
[[cell textLabel] setText:[tempDict objectForKey:@"summary"]];
if ([tempDict isEqual:_dictCurrentCalendar]) {
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
}
else{
[cell setAccessoryType:UITableViewCellAccessoryNone];
} } }
return cell; }
78. www.letsnurture.com
That's it. Go and give it a try. Watch your calendars on the
Simulator and play around for a while by expanding the
calendar list and selecting a calendar!
11. Posting Calendar Events
Step 1
Let's see how we'll manage to add an event to a selected
calendar. The first thing we should do is make sure that
the event description and the event date have been set. To
verify this, we'll check their values and we'll show an alert
if something is wrong.
Our work will now take place in the post: IBAction method.
79. www.letsnurture.com
- (IBAction)post:(id)sender {
// Before posting the event, check if the event description is empty or a date has
not been selected.
if ([_strEvent isEqualToString:@""]) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@""
message:@"Please enter an event description."
delegate:self
cancelButtonTitle:nil
otherButtonTitles:@"Okay", nil];
[alert show];
return;
}
if ([_strEventDate isEqualToString:@"Pick a date..."]) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@""
message:@"Please select a date for the event."
delegate:self
cancelButtonTitle:nil
otherButtonTitles:@"Okay", nil];
[alert show];
return;
}
...
}
80. www.letsnurture.com
You'll notice that we'll simply check if the event description string is equal
to the empty string and if the event date string is equal to its initial value.
Step 2
As I have already said, what we actually do in this project is to implement
the Quick-add feature that Google Calendar supports.
However, we need to know the format for the event string because there
are some rules that apply.
For example, there are special ways that the event date and time should
be appended at the end of the event description string, so Google will
know the exact date/time of the event.
Of course, if you have already used the online Quick-Add feature, then
you surely know what I am talking about.
81. www.letsnurture.com
Thankfully, Google provides help and examples regarding
this issue. We just have to visit the Quick Add
documentation. Go and visit this website and familiarize
your self with this feature.
In our case, if we have a full-day event, we simply have to
add the date using slashes at the end of the event
description string (for example, "This is an event
08/12/2013"). If we don't have a full-day event, then we
will add the time too, using the "at" between the date and
time (for example, "This is an event 08/12/2013 at 21:40").
In addition, we need to know the URL of the API we want
to call. Just like we with the calendar list, we must find the
quick-add related information in the Google Calendar API
documentation. If you don't want to bother looking for it
right now, this is where you can find it. We must search
for the following information:
82. www.letsnurture.com
Request: This is the URL of the API we want to call as it
appeared at the top of the page.
Also notice that we need to use the POST HTTP method.
POST parameters:
Here are the parameters we need to send with the POST
method. There are only two mandatory params, the ID of
the calendar on which we want to add the event and the
event text (of course formatted with the date, as I indicated
before).
Authorization: This is the scope that we should be
authorizing. In this case, the scope is equal to the calendar
list scope, so we don't need to care about it. However, if
that was a different value, then we should include it in the
scope array during authorization.
83. www.letsnurture.com
- (IBAction)post:(id)sender {
...
...
...
// Create the URL string of API needed to quick-add the event into the
Google calendar.
// Note that we specify the id of the selected calendar.
NSString *apiURLString = [NSString
stringWithFormat:@"https://www.googleapis.com/calendar/v3/calendars/
%@/events/quickAdd",
[_dictCurrentCalendar objectForKey:@"id"]];
// Build the event text string, composed by the event description and
the date (and time) that should happen.
// Break the selected date into its components.
NSDateComponents *dateComponents = [[NSDateComponents alloc]
init];
dateComponents = [[NSCalendar currentCalendar]
components:NSDayCalendarUnit|NSMonthCalendarUnit|
NSYearCalendarUnit|NSHourCalendarUnit|NSMinuteCalendarUnit
fromDate:_dtEvent];
84. www.letsnurture.com
if (_isFullDayEvent) {
// If a full-day event was selected (meaning without specific
time), then add at the end of the string just the date.
_strEventTextToPost = [NSString stringWithFormat:@"%@
%d/%d/%d", _strEvent, [dateComponents month],
[dateComponents day], [dateComponents year]];
}
else{
// Otherwise, append both the date and the time that the
event should happen.
_strEventTextToPost = [NSString stringWithFormat:@"%@
%d/%d/%d at %d.%d", _strEvent, [dateComponents month],
[dateComponents day], [dateComponents year],
[dateComponents hour], [dateComponents minute]];
}
85. www.letsnurture.com
// Show the activity indicator view.
[self showOrHideActivityIndicatorView];
// Call the API and post the event on the selected Google
calendar.
// Visit https://developers.google.com/google-
apps/calendar/v3/reference/events/quickAdd for more
information about the quick-add event API call.
[_googleOAuth callAPI:apiURLString
withHttpMethod:httpMethod_POST
postParameterNames:[NSArray
arrayWithObjects:@"calendarId", @"text", nil]
postParameterValues:[NSArray arrayWithObjects:
[_dictCurrentCalendar objectForKey:@"id"],
_strEventTextToPost, nil]];
}
86. www.letsnurture.com
If you run the app now and you click on the Post button, the
event will be posted. You can verify this if you check your
calendar from a web browser. However, until we handle the
response from Google, we are unable to know if the event
was successfully added or not.
Step 3
Each time that an event is posted, Google creates a
respective object which contains several properties and
data.
After a successful addition, Google responds with this data,
so we can have any information we need regarding the
newly created event. In our case, we won't do something
extraordinary. We'll simply show an alert view that will
contain the following values, so we are sure that the event
was successfully posted.
87. www.letsnurture.com
ID: Each new event gets a unique ID value, which we are
going to display on the alert view.
Created Date: The date that the event was created.
Summary: The event description itself.
Of course, in a real app, you must handle this event data in
a different way. Always regard the app's needs.
We are going to work in the
responseFromServiceWasReceived:andResponseJSONAs
Data: method again. Notice that the JSON data is
converted again into an NSDictionary object.
88. www.letsnurture.com
-(void)responseFromServiceWasReceived:(NSString *)responseJSONAsString
andResponseJSONAsData:(NSData *)responseJSONAsData{
NSError *error;
...
...
...
else if ([responseJSONAsString rangeOfString:@"calendar#event"].location !
= NSNotFound){
// If the Google response contains the "calendar#event" literal then the
event has been added to the selected calendar
// and Google returns data related to the new event.
// Get the response JSON as a dictionary.
NSDictionary *eventInfoDict = [NSJSONSerialization
JSONObjectWithData:responseJSONAsData
options:NSJSONReadingMutableContainers error:&error];
if (error) {
// This is the case that an error occured during converting JSON data to
dictionary.
// Simply log the error description.
NSLog(@"%@", [error localizedDescription]);
return;
}
89. www.letsnurture.com
// An alert view with some information regarding the just added event will be
shown.
// Keep only the information that will be shown to the alert view.
// Look at the https://developers.google.com/google-
apps/calendar/v3/reference/events#resource for a complete list of the
// data fields that Google returns.
NSString *eventID = [eventInfoDict objectForKey:@"id"];
NSString *created = [eventInfoDict objectForKey:@"created"];
NSString *summary = [eventInfoDict objectForKey:@"summary"];
// Build the alert message.
NSString *alertMessage = [NSString stringWithFormat:@"ID:
%@nnCreated:%@nnSummary:%@", eventID, created, summary];
// Stop the activity indicator view.
[self showOrHideActivityIndicatorView];
// Show the alert view.
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"New event"
message:alertMessage
delegate:self
cancelButtonTitle:nil
otherButtonTitles:@"Great", nil];
[alert show];
}
}
90. www.letsnurture.com
Now, every time you add an event an alert view containing
the new event info is displayed.
12. Signing Out
Signing out of the Google account is an option that should
always be provided to the users, even though it's
recommended to keep them connected for faster access to
online services.
For our app, it's only a matter of one single line of code,
which we'll add on the revokeAccess: IBAction method.
91. www.letsnurture.com
13. Ancillary Methods
Step 1
Until now, we used only two delegate methods of the
GoogleOAuth class, the authorizationWasSuccessful and the
responseFromServiceWasReceived:andResponseJSONAsData:.
There are three more of them that we should implement. We need
a delegate method for handling the access revocation, another
delegate for handling any error that may occur, and a final method
for dealing with any error messages that may exist in Google
responses.
Regarding the access revocation, we'll have to do only three
things. First, we need to remove all calendars from the
arrGoogleCalendars array. Next, we need to disable the Post and
the Sign Out buttons. Finally, we need to reload the table view to
keep it up to date.
92. www.letsnurture.com
-(void)accessTokenWasRevoked{
// Remove all calendars from the array.
[_arrGoogleCalendars removeAllObjects];
_arrGoogleCalendars = nil;
// Disable the post and sign out bar button items.
[_barItemPost setEnabled:NO];
[_barItemRevokeAccess setEnabled:NO];
// Reload the Google calendars section.
[_tblPostData reloadSections:[NSIndexSet
indexSetWithIndex:2]
withRowAnimation:UITableViewRowAnimationAutomatic];
}
93. www.letsnurture.com
For the next two error handling delegate methods, we won't do much. We'll
simply log the error messages and nothing further.
It's obvious that in a real app you would have to handle the errors in an
appropriate way and figure out workarounds that handle unexpected situations.
For now, here are our stub implementations:
-(void)errorOccuredWithShortDescription:(NSString
*)errorShortDescription andErrorDetails:(NSString
*)errorDetails{
// Just log the error messages.
NSLog(@"%@", errorShortDescription);
NSLog(@"%@", errorDetails);
}
-(void)errorInResponseWithBody:(NSString *)errorMessage{
// Just log the error message.
NSLog(@"%@", errorMessage);
}
94. www.letsnurture.com
Step 2
During the project implementation, we made a few calls to the
showOrHideActivityIndicatorView private method, which has been declared and
called but not yet built. Let's deal with it now.
-(void)showOrHideActivityIndicatorView{
// If the activity indicator view is not currently animating (spinning),
// then set its view center equal to self view's center, add it as a subview and
start animating.
// Otherwise stop animating and remove it from the superview.
if (![_activityIndicatorView isAnimating]) {
[_activityIndicatorView setCenter:self.view.center];
[self.view addSubview:_activityIndicatorView];
[_activityIndicatorView startAnimating];
}
else{
[_activityIndicatorView stopAnimating];
[_activityIndicatorView removeFromSuperview];
}
}