[iOS][Objective-C] Google Calendar API で予定を書き込んでみる

概要

今回は、iOS アプリからカレンダーに予定を書き込んでみます。

Google Calendar API の iOS 用ライブラリと、Google が公開しているサンプルコードを使用します。

公開場所

iOS アプリで Google Calendar API を使う準備は、以下のガイドを見ながら進めることができます。

ここから先しばらくは、ガイドを参考に、準備・実装を進めていきます。

iOS Quickstart | Google Calendar API | Google Developers

事前準備

もし Google アカウントをお持ちでない場合は、Google Calendarを使用するため、Googleで取得しましょう。

Google Calendar API を有効化する

Google Developer Console で 認証に必要な情報をゲットする

今回は、はじめて Google Developer Console を使用する前提で進めていきます。

Google アカウントにログイン後、Google Developer Console のGoogle Calendar API のウィザードページ に飛びます。

1

同意事項にチェックを入れて、「同意して続行」をクリックします。

7

しばらくすると、ブラウザウインドウ右下に、このような表示がされます。ここで「すべてのアクテビティを表示」をクリック。

遷移先画面のプルダウンメニューから、「My Project」を選択します。

4

左ペインの「APIと認証」項目内の「同意画面」をクリックすると、このような画面になります。サービス名がブランクになっていると思いますので、ここに今回作成するサービス名を入力し、「保存」をクリックします。

5

次に、「APIと認証」項目内の「認証情報」をクリックし、「新しいクライアントIDを作成」をクリックします。

6

クライアントIDの作成ウインドウが表示されます。ここで以下のように設定します。

  • アプリケーションの種類: インストールされているアプリケーション
  • インストールされているアプリケーションの種類: その他
    • iOS を選択すると、App Store ID と バンドルID などの入力を求められますので、今回はその他を選択します。

7

しばらくすると、OAuth 2.0 での認証に使用する、クライアントID、クライアントシークレット等が生成されます。後ほど使うので、必要に応じてエディタなどで保存しておきましょう。

Xcode プロジェクトの準備

注意

※ 以下を実際に試される際は、説明の流れ上、実行結果がうまくいくために、実機ではなくiOS シミュレータ上で行ってください。

8

9

iOS の Single View Application テンプレートを使用してプロジェクトを作成しましょう。今回、言語は Objective-C で進めます。

Cocoapods を使って API のライブラリをインストールしましょう。

pod 'Google-API-Client/Calendar', '~> 1.0'

pod init などで Podfile を作成後、上記を追記し pod install

ここから先は、新規に生成されたワークスペースを開いて進めていきます。

[!] Please close any current Xcode sessions and use 'GoogleCalendarSample.xcworkspace' for this project from now on.

サンプルコードを使用

iOS Quickstart | Google Calendar API | Google Developersに飛び、Step3 に書かれているコードをプロジェクトに貼り付けます。

ViewController.hViewController.m のコードが乗っていますので、そのままコピー&ペーストしましょう。

次に、 ViewController.m ファイルを編集します。

static NSString *const kClientID = @"YOUR_CLIENT_ID_HERE";
static NSString *const kClientSecret = @"YOUR_CLIENT_SECRET_HERE";

冒頭に、このような記述があります。ここで、先ほど取得したクライアントIDとクライアントシークレットの出番です。それぞれ、YOUR_CLIENT_ID_HEREYOUR_CLIENT_SECRET_HERE に貼り付けましょう。

Google Calendar 上で予定を作成

このサンプルコードは、今より後のカレンダー内の予定を表示してくれるものになっています。もし現在時刻より後に予定を入れていない場合は、なんでも良いので、Google Calendar にいくつか予定を入れてみましょう。

サンプル実行

ここで一度実行してみましょう。

実行すると、Gooogle アカウントへのログイン画面が表示されます。ログインしましょう。

アクセス許可のリクエストが表示されます。許可します。

この先の予定が表示されました。サンプルコードが無事動きました!

サンプル解説

ここから先は、サンプルがどうやって認証・表示を行っているか見て行きましょう。

ViewController.m における主なメソッドを見ていきます。

viewDidLoad

- (void)viewDidLoad {
  [super viewDidLoad];

  // Create a UITextView to display output.
  self.output = [[UITextView alloc] initWithFrame:self.view.bounds];
  self.output.editable = false;
  self.output.contentInset = UIEdgeInsetsMake(20.0, 0.0, 20.0, 0.0);
  self.output.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
  [self.view addSubview:self.output];

  // Initialize the Google Calendar API service & load existing credentials from the keychain if available.
  self.service = [[GTLServiceCalendar alloc] init];
  self.service.authorizer =
  [GTMOAuth2ViewControllerTouch authForGoogleFromKeychainForName:kKeychainItemName
                                                        clientID:kClientID
                                                    clientSecret:kClientSecret];
}

ログを出力するための TextView を生成して、View に追加しています。 Google Calendar API サービスを初期化し、もし可能であれば、キーチェーンに保存されている認証情報を読み込みます。

viewDidAppear:

// When the view appears, ensure that the Google Calendar API service is authorized, and perform API calls.
- (void)viewDidAppear:(BOOL)animated {
  if (!self.service.authorizer.canAuthorize) {
    // Not yet authorized, request authorization by pushing the login UI onto the UI stack.
    [self presentViewController:[self createAuthController] animated:YES completion:nil];

  } else {
    [self fetchEvents];
  }
}

認証済みか判定します。もし認証が済んでいなければ、ログイン画面を表示します。 既に認証が済んでいれば、fetchEvents メソッドを呼び、カレンダーからのイベント取得処理に進みます。

fetchEvents

// Construct a query and get a list of upcoming events from the user calendar. Display the
// start dates and event summaries in the UITextView.
- (void)fetchEvents {
  GTLQueryCalendar *query = [GTLQueryCalendar queryForEventsListWithCalendarId:@"primary"];
  query.maxResults = 10;
  query.timeMin = [GTLDateTime dateTimeWithDate:[NSDate date]
                                       timeZone:[NSTimeZone localTimeZone]];;
  query.singleEvents = YES;
  query.orderBy = kGTLCalendarOrderByStartTime;

  [self.service executeQuery:query
                    delegate:self
           didFinishSelector:@selector(displayResultWithTicket:finishedWithObject:error:)];
}

クエリを生成し、各種プロパティを設定しています。

プロパティ 意味 コード内での設定内容 備考
maxResults イベントの最大取得件数 10件
timeMin 取得開始日時 現在日時
singleEvents 繰り返しイベントを展開して1個ずつのイベントとして取得するか 展開する 展開しないと orderBy を 設定してクエリを実行した際エラーが返ってくる
orderBy ソートをする基準 イベント開始時刻 他に更新日時を基準とすることができる

その後、クエリを実行しています。

displayResultWithTicket:finishedWithObject:error:

- (void)displayResultWithTicket:(GTLServiceTicket *)ticket
             finishedWithObject:(GTLCalendarEvents *)events
                          error:(NSError *)error {
  if (error == nil) {
    NSMutableString *eventString = [[NSMutableString alloc] init];
    if (events.items.count > 0) {
      [eventString appendString:@"Upcoming 10 events:n"];
      for (GTLCalendarEvent *event in events) {
        GTLDateTime *start = event.start.dateTime ?: event.start.date;
        NSString *startString =
        [NSDateFormatter localizedStringFromDate:[start date]
                                       dateStyle:NSDateFormatterShortStyle
                                       timeStyle:NSDateFormatterShortStyle];
        [eventString appendFormat:@"%@ - %@n", startString, event.summary];
      }
    } else {
      [eventString appendString:@"No upcoming events found."];
    }
    self.output.text = eventString;
  } else {
    [self showAlert:@"Error" message:error.localizedDescription];
  }
}

クエリ実行結果を表示します。

GTLCalendarEvents 型の events を引数として受け取り、それを for文で回して個別のイベント(GTLCalendarEventのインスタンス event)を取得しています。

そして、event から開始日時とイベント名(summary)を取り出し、列挙して表示します。

もし events 内の要素が 0 だった場合は、この先イベントが見つからない旨表示します。

クエリ実行時エラーが帰ってきた場合は、エラー文を表示します。

createAuthController

// Creates the auth controller for authorizing access to Google Calendar API.
- (GTMOAuth2ViewControllerTouch *)createAuthController {
  GTMOAuth2ViewControllerTouch *authController;
  NSArray *scopes = [NSArray arrayWithObjects:kGTLAuthScopeCalendarReadonly, nil];
  authController = [[GTMOAuth2ViewControllerTouch alloc]
           initWithScope:[scopes componentsJoinedByString:@" "]
                clientID:kClientID
            clientSecret:kClientSecret
        keychainItemName:kKeychainItemName
                delegate:self
        finishedSelector:@selector(viewController:finishedWithAuth:error:)];
  return authController;
}

GTMOAuth2ViewControllerTouchのインスタンス(ログイン画面) を生成し、呼び出し元に返しています。

viewDidAppear: より

[self presentViewController:[self createAuthController] animated:YES completion:nil];

サンプルコードでは、ViewDidAppear:メソッドで実際にログイン画面の表示を行っています。

viewController:finishedWithAuth:error:

// Handle completion of the authorization process, and update the Google Calendar API
// with the new credentials.
- (void)viewController:(GTMOAuth2ViewControllerTouch *)viewController
      finishedWithAuth:(GTMOAuth2Authentication *)authResult
                 error:(NSError *)error {
  if (error != nil) {
    [self showAlert:@"Authentication Error" message:error.localizedDescription];
    self.service.authorizer = nil;
  }
  else {
    self.service.authorizer = authResult;
    [self dismissViewControllerAnimated:YES completion:nil];
  }
}

認証プロセスの終了をハンドリングし、認証情報(GTMOAuth2Authentication 型の authResult)を使って GTLServiceCalendar 型の self.service における authorizer をアップデートしています。ライブラリが送信するリクエストのヘッダに必要に応じて認証情報が追加されます。

サンプル改造

さて、ここから先はサンプルを改造して、カレンダーに予定を書き込んでみます。

まず、認証時に書き込みできる権限を取得する必要があります。

NSArray *scopes = [NSArray arrayWithObjects:kGTLAuthScopeCalendarReadonly, nil];

createAuthController メソッド内を見てみましょう。

ここで scopes とは、アクセスの範囲(スコープ)を示しています。

定数 kGTLAuthScopeCalendarReadonly は読み込み専用を表しています。

これを kGTLAuthScopeCalendar に書き換えましょう。

また、iOS シミュレータの Reset Content and Settingsを実行し、キーチェーンから認証情報を削除してください。

writeEvents

- (void)writeEvents {
    GTLCalendarEvent *event = [GTLCalendarEvent new];

    // 予定のタイトル
    [event setSummary:@"イベント"];

    // 予定の説明
    [event setDescriptionProperty:@"created by google calendar sample"];

    // 予定の日時を指定する準備
    NSDateFormatter *dateFormatter = [NSDateFormatter new];
    [dateFormatter setDateFormat:@"yyyy/MM/dd HH:mm:ss"];

    // 予定の開始日時
    GTLDateTime *startDateTime = [GTLDateTime dateTimeWithDate:[dateFormatter dateFromString:@"2015/07/19 11:00:00"] timeZone:[NSTimeZone systemTimeZone]];
    GTLCalendarEventDateTime *start = [GTLCalendarEventDateTime new];
    [start setDateTime:startDateTime];
    [start setTimeZone:@"Asia/Tokyo"];
    [event setStart:start];

    // 予定の終了日時
    GTLDateTime *endDateTime = [GTLDateTime dateTimeWithDate:[dateFormatter dateFromString:@"2015/07/19 15:00:00"] timeZone:[NSTimeZone systemTimeZone]];
    GTLCalendarEventDateTime *end = [GTLCalendarEventDateTime new];
    [end setDateTime:endDateTime];
    [end setTimeZone:@"Asia/Tokyo"];
    [event setEnd:end];

    // 
    GTLQueryCalendar *query = [GTLQueryCalendar queryForEventsInsertWithObject:event calendarId:@"xxxxxxxx@gmail.com"];

    [self.service executeQuery:query delegate:self didFinishSelector:@selector(displayWritingFinish:finishedWithObject:error:)];
}

2つのメソッドを加えます。1つ目はこのメソッドです。

Create Events | Google Calendar API | Google Developers からイベント作成のサンプルコードが見られるのですが、iOS 系の言語での例が、執筆時現在、書かれていません。今回は 当該ページの Java 版サンプルコードと、ライブラリのコードを参考に、コードを作成しました。

詳しく見て行きましょう。

GTLCalendarEvent *event = [GTLCalendarEvent new];

イベント内容を保持する GTLCalendarEvent のインスタンス event を生成しています。

// 予定のタイトル
    [event setSummary:@"イベント"];

    // 予定の説明
    [event setDescriptionProperty:@"created by google calendar sample"];

eventsummary (タイトル)と、予定の説明文を設定しています。

// 予定の日時を指定する準備
    NSDateFormatter *dateFormatter = [NSDateFormatter new];
    [dateFormatter setDateFormat:@"yyyy/MM/dd HH:mm:ss"];

NSDateFormatter 型の dataFormatter を生成し、後に文字列を使って年月日・時刻を設定する際の準備をしています。

// 予定の開始日時
    GTLDateTime *startDateTime = [GTLDateTime dateTimeWithDate:[dateFormatter dateFromString:@"2015/07/19 11:00:00"] timeZone:[NSTimeZone systemTimeZone]];
    GTLCalendarEventDateTime *start = [GTLCalendarEventDateTime new];
    [start setDateTime:startDateTime];
    [start setTimeZone:@"Asia/Tokyo"];
    [event setStart:start];

イベントの開始を、2015/07/19 11:00:00 としています。

eventsetStart: を使い、GTLCalendarEventDateTime 型の start をセットしています。

予定の終了時刻は、同様の処理なので省略します。

GTLQueryCalendar *query = [GTLQueryCalendar queryForEventsInsertWithObject:event calendarId:@"xxxxxxxx@gmail.com"];

書き込み用のクエリを生成します。ここで、カレンダーID を指定しています。通常は、アカウントの ID と同じです。

[self.service executeQuery:query delegate:self didFinishSelector:@selector(displayWritingFinish:finishedWithObject:error:)];

クエリを実行し、displayWritingFinish:finishedWithObject:error:を呼び出す処理です。

displayWritingFinish:finishedWithObject:error:

- (void)displayWritingFinish:(GTLServiceTicket *)ticket
             finishedWithObject:(GTLCalendarEvents *)events
                          error:(NSError *)error {
    if (error) {
        self.output.text = [NSString stringWithFormat:@"%@", error];
    } else {
        self.output.text = [NSString stringWithFormat:@"successfully added event %@.  Congratulations!", events.summary];
    }
}

クエリの実行結果を引数として受け取り、表示するメソッドです。

成功ならば、イベント名とともに書き込みが成功した旨を表示し、そうでなければエラー文を書き出します。

self.outputUITextView のインスタンスです。

viewDidAppear を変更

// [self fetchEvents];
        [self writeEvents];

fetchEventswriteEvents に置き換えましょう。

これで、起動時にイベントの追加書き込みが行われるようになります。

改造後の実行の結果

指定した日時にイベントが追加されました!

イベントの説明文も書き込まれています。

まとめ

今回は、Google Calendar API の iOS 用ライブラリと、Google が公開しているサンプルコードを使用して、iOS アプリからカレンダーに予定を書き込んでみました!

おまけ

アクセス権限を変更しない場合

NSArray *scopes = [NSArray arrayWithObjects:kGTLAuthScopeCalendarReadonly, nil];

createAuthController メソッドにおいて、アクセス範囲を示す定数を変更せず、 kGTLAuthScopeCalendarReadonly を指定してイベント書き込み処理を行った場合、このようなエラーが返って来ます。

複数回実行してみる

Screen Shot 2015-07-15 at 23.30.23

実行回数分のイベントが作成されました。上書きはされず、別個のイベントとして扱われるようですね。

クレジット

サンプルコードの著作権は Google Inc. にあり、Apache License, Version 2.0 ライセンスの下で使用させて頂いています。