[SWIFT] Access Google Calendar from the iOS app

This time I would like to access Google Calendar from the iOS app through the Google Calenear API.

Environment: Xcode 12.0, Swift 5

Preparation

First, go to Google Cloud Platform and enable the Google Calendar API. Next, get the OAuth client ID for OAuth authentication. Follow the procedure described in "Registering a client ID" in the following article to apply for a client ID.

I hit Google Calendar API with Swift4

External library to use

The Google API itself is a REST API, but it's hard to call it directly, so I'll use an external library. This time we will use the following libraries.

・ Google authentication AppAuth GTMAppAuth

・ Access to Google Calendar GoogleAPIClientForREST/Calendar

All of the above libraries can be installed at CocoaPods. Write the Podfile as shown below and execute pod install to install it.

platform :ios, '14.0'

target 'GoogleCalendarSample' do
  use_frameworks!

  pod 'AppAuth'
  pod 'GTMAppAuth'
  pod 'GoogleAPIClientForREST/Calendar'

end

Authentication

Now, we will implement the process of performing Google authentication using AppAuth and GTMAppAuth.

First, add the OIDExternalUserAgentSession class to AppDelegate.

AppDelegate.swift


import UIKit
import AppAuth
import GTMAppAuth

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    var currentAuthorizationFlow: OIDExternalUserAgentSession?

---------------- (The following is omitted) ----------------

Then describe the following process on the screen for Google authentication.

import UIKit
import AppAuth
import GTMAppAuth
import GoogleAPIClientForREST

---------------- (Omission) ----------------

private var authorization: GTMAppAuthFetcherAuthorization?
private let clientID = "xxxxxxxxxxxxxxxxxxxx"
private let reverseClientID = "xxxxxxxxxxxxxxxxxxxx"
typealias showAuthorizationDialogCallBack = ((Error?) -> Void)

private func showAuthorizationDialog(callBack: @escaping showAuthorizationDialogCallBack) {
    let scopes = ["https://www.googleapis.com/auth/calendar","https://www.googleapis.com/auth/calendar.readonly","https://www.googleapis.com/auth/calendar.events","https://www.googleapis.com/auth/calendar.events.readonly"]
        
    let configuration = GTMAppAuthFetcherAuthorization.configurationForGoogle()
    let redirectURL = URL.init(string: reverseClientID + ":/oauthredirect")
        
    let request = OIDAuthorizationRequest.init(configuration: configuration,
                                                   clientId: clientID,
                                                   scopes: scopes,
                                                   redirectURL: redirectURL!,
                                                   responseType: OIDResponseTypeCode,
                                                   additionalParameters: nil)
        
    let appDelegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate
    appDelegate.currentAuthorizationFlow = OIDAuthState.authState(
        byPresenting: request,
        presenting: self,
        callback: { (authState, error) in
            if let error = error {
                NSLog("\(error)")
            } else {
                if let authState = authState {
                    self.authorization = GTMAppAuthFetcherAuthorization.init(authState: authState)
                    GTMAppAuthFetcherAuthorization.save(self.authorization!, toKeychainForName: "authorization")
                }
            }
            callBack(error)
    })
}

The following variables describe the ID obtained when the OAuth client ID was obtained. Enter the OAuth 2.0 client ID in clientID and the reverse client ID in reverseClientID.

private let clientID = "xxxxxxxxxxxxxxxxxxxx"
private let reverseClientID = "xxxxxxxxxxxxxxxxxxxx"

Set the permissions you need this time to the array scopes. This time we are requesting permission to search and change Google Calendar.

let scopes = ["https://www.googleapis.com/auth/calendar","https://www.googleapis.com/auth/calendar.readonly","https://www.googleapis.com/auth/calendar.events","https://www.googleapis.com/auth/calendar.events.readonly"]

When the authState method of the OIDAuthState class is executed, the following Google authentication dialog will be displayed. Google認証ダイアログ If the user correctly enters the gmail address and password in the dialog and authentication is complete, the Callback function of the authState method will generate and save the GTMAppAuthFetcherAuthorization class. There is no need to redisplay the authentication dialog while this GTMAppAuthFetcherAuthorization class remains.

Search for events

Next, I would like to access Google Calendar using GoogleAPIClientForREST. First of all, I will describe the process of getting an existing event from Google Calendar. If you pass the start date and time and end date and time to the get method, it is a program that searches for events between the start date and time and the end date and time from Google Calendar.

import UIKit
import AppAuth
import GTMAppAuth
import GoogleAPIClientForREST

---------------- (Omission) ----------------

private var authorization: GTMAppAuthFetcherAuthorization?
private let clientID = "xxxxxxxxxxxxxxxxxxxx"
private let reverseClientID = "xxxxxxxxxxxxxxxxxxxx"
typealias showAuthorizationDialogCallBack = ((Error?) -> Void)
struct GoogleCalendaraEvent {
    var id: String
    var name: String
    var startDate: Date?
    var endDate: Date?
}
private var googleCalendarEventList: [GoogleCalendaraEvent] = []

private func showAuthorizationDialog(callBack: @escaping showAuthorizationDialogCallBack) {
---------------- (Omission) ----------------
}

private func get(startDateTime: Date, endDateTime: Date) {
    if GTMAppAuthFetcherAuthorization(fromKeychainForName: "authorization") != nil {
        self.authorization = GTMAppAuthFetcherAuthorization(fromKeychainForName: "authorization")!
    }
        
    if self.authorization == nil {
        showAuthorizationDialog(callBack: {(error) -> Void in
            if error == nil {
                self.getCalendarEvents(startDateTime: startDateTime, endDateTime: endDateTime)
            }
        })
    } else {
        self.getCalendarEvents(startDateTime: startDateTime, endDateTime: endDateTime)
    }
}
    
private func getCalendarEvents(startDateTime: Date, endDateTime: Date) {
    let calendarService = GTLRCalendarService()
    calendarService.authorizer = self.authorization
    calendarService.shouldFetchNextPages = true
        
    let query = GTLRCalendarQuery_EventsList.query(withCalendarId: "primary")
    query.timeMin = GTLRDateTime(date: startDateTime)
    query.timeMax = GTLRDateTime(date: endDateTime)
        
    calendarService.executeQuery(query, completionHandler: { (ticket, event, error) -> Void in
        if let error = error {
            NSLog("\(error)")
        } else {
            if let event = event as? GTLRCalendar_Events, let items = event.items {
                self.googleCalendarEventList.removeAll()
                for item in items {
                    let id: String = item.identifier ?? ""
                    let name: String = item.summary ?? ""
                    let startDate: Date? = item.start?.dateTime?.date
                    let endDate: Date? = item.end?.dateTime?.date
                    self.googleCalendarEventList.append(GoogleCalendaraEvent(id: id, name: name, startDate: startDate, endDate: endDate))
                }
            }
        }
    })
}

First, check if Google authentication is complete. Check if the GTMAppAuthFetcherAuthorization class is saved, and if it is not saved, call the showAuthorizationDialog function created earlier to display the Google authentication dialog and get the GTMAppAuthFetcherAuthorization class. If the GTMAppAuthFetcherAuthorization class is saved, use it as it is.

Then use GoogleAPIClientForREST to get the event from the Googl calendar. First, generate the GTLRCalendarService class to access the Goole calendar and set the GTMAppAuthFetcherAuthorization class in the authorizer property.

let calendarService = GTLRCalendarService()
calendarService.authorizer = self.authorization
calendarService.shouldFetchNextPages = true

Next, generate the GTLRCalendarQuery_EventsList class to search for events from Google Calendar, and set the start date and time and end date and time as search conditions.

let query = GTLRCalendarQuery_EventsList.query(withCalendarId: "primary")
query.timeMin = GTLRDateTime(date: startDateTime)
query.timeMax = GTLRDateTime(date: endDateTime)

Then, with this GTLRCalendarQuery_EventsList class as an argument, execute the executeQuery method of the GTLRCalendarService class to get the event from Google Calendar. When the event can be acquired, the GTLRCalendar_Events class is returned by the Callback function of the executeQuery method, so the event information is acquired from here.

if let event = event as? GTLRCalendar_Events, let items = event.items {
    self.googleCalendarEventList.removeAll()
    for item in items {
        let id: String = item.identifier ?? ""
        let name: String = item.summary ?? ""
        let startDate: Date? = item.start?.dateTime?.date
        let endDate: Date? = item.end?.dateTime?.date
        self.googleCalendarEventList.append(GoogleCalendaraEvent(id: id, name: name, startDate: startDate, endDate: endDate))
    }
}

Especially the identifier (unique ID of the event) is important. This identifier is the key when changing or deleting an event.

Add event

Next, I would like to add an event to Google Calendar. It is a program that creates an event in Google Calendar by passing the event name, start date and time, and end date and time to the add method.

import UIKit
import AppAuth
import GTMAppAuth
import GoogleAPIClientForREST

---------------- (Omission) ----------------

private var authorization: GTMAppAuthFetcherAuthorization?
typealias showAuthorizationDialogCallBack = ((Error?) -> Void)

private func showAuthorizationDialog(callBack: @escaping showAuthorizationDialogCallBack) {
---------------- (Omission) ----------------
}

private func add(eventName: String, startDateTime: Date, endDateTime: Date) {
        
    if GTMAppAuthFetcherAuthorization(fromKeychainForName: "authorization") != nil {
        self.authorization = GTMAppAuthFetcherAuthorization(fromKeychainForName: "authorization")!
    }
        
    if self.authorization == nil {
        showAuthorizationDialog(callBack: {(error) -> Void in
            if error == nil {
                self.addCalendarEvent(eventName: eventName, startDateTime: startDateTime, endDateTime: endDateTime)
            }
        })
    } else {
        self.addCalendarEvent(eventName: eventName, startDateTime: startDateTime, endDateTime: endDateTime)
    }
}
    
private func addCalendarEvent(eventName: String, startDateTime: Date, endDateTime: Date) {
        
    let calendarService = GTLRCalendarService()
    calendarService.authorizer = self.authorization
    calendarService.shouldFetchNextPages = true
    
    let event = GTLRCalendar_Event()
    event.summary = eventName
        
    let gtlrDateTimeStart: GTLRDateTime = GTLRDateTime(date: startDateTime)
    let startEventDateTime: GTLRCalendar_EventDateTime = GTLRCalendar_EventDateTime()
    startEventDateTime.dateTime = gtlrDateTimeStart
    event.start = startEventDateTime
        
    let gtlrDateTimeEnd: GTLRDateTime = GTLRDateTime(date: endDateTime)
    let endEventDateTime: GTLRCalendar_EventDateTime = GTLRCalendar_EventDateTime()
    endEventDateTime.dateTime = gtlrDateTimeEnd
    event.end = endEventDateTime

    let query = GTLRCalendarQuery_EventsInsert.query(withObject: event, calendarId: "primary")
    calendarService.executeQuery(query, completionHandler: { (ticket, event, error) -> Void in
        if let error = error {
            NSLog("\(error)")
        }
    })
}

The process up to the generation of the GTLRCalendarService class is the same as in the case of search, so we will explain from the subsequent part. Generate GTLRCalendar_Event class to set the information of the event to be added. Since the event name, start date and time, and end date and time are set this time, they are set in the summary property, start property, and end property of the GTLRCalendar_Event class.

let event = GTLRCalendar_Event()
event.summary = eventName
        
let gtlrDateTimeStart: GTLRDateTime = GTLRDateTime(date: startDateTime)
let startEventDateTime: GTLRCalendar_EventDateTime = GTLRCalendar_EventDateTime()
startEventDateTime.dateTime = gtlrDateTimeStart
event.start = startEventDateTime
        
let gtlrDateTimeEnd: GTLRDateTime = GTLRDateTime(date: endDateTime)
let endEventDateTime: GTLRCalendar_EventDateTime = GTLRCalendar_EventDateTime()
endEventDateTime.dateTime = gtlrDateTimeEnd
event.end = endEventDateTime

In addition, the identifier that becomes the unique ID of the event is automatically assigned by Google Calendar in the case of new addition, so it is not necessary to set it here.

Then, generate a GTLRCalendarQuery_EventsInsert class for adding a new event to Google Calendar with the GTLRCalendar_Event class as an argument, and execute the executeQuery method of the GTLRCalendarService class to add a new event to Google Calendar.

Event changes

Next, let's change the information of the existing event. It is a program that changes the event information of the corresponding identifier in Google Calendar when the event identifier, event name, start date and time, end date and time are passed to the update method.

import UIKit
import AppAuth
import GTMAppAuth
import GoogleAPIClientForREST

---------------- (Omission) ----------------

private var authorization: GTMAppAuthFetcherAuthorization?
typealias showAuthorizationDialogCallBack = ((Error?) -> Void)

private func showAuthorizationDialog(callBack: @escaping showAuthorizationDialogCallBack) {
---------------- (Omission) ----------------
}

private func update(eventId: String, eventName: String, startDateTime: Date, endDateTime: Date) {
        
    if GTMAppAuthFetcherAuthorization(fromKeychainForName: "authorization") != nil {
        self.authorization = GTMAppAuthFetcherAuthorization(fromKeychainForName: "authorization")!
    }
        
    if self.authorization == nil {
        showAuthorizationDialog(callBack: {(error) -> Void in
            if error == nil {
                self.updateCalendarEvent(eventId: eventId, eventName: eventName, startDateTime: startDateTime, endDateTime: endDateTime)
            }
        })
    } else {
        self.updateCalendarEvent(eventId: eventId, eventName: eventName, startDateTime: startDateTime, endDateTime: endDateTime)
    }
}
    
private func updateCalendarEvent(eventId: String, eventName: String, startDateTime: Date, endDateTime: Date) {
    let calendarService = GTLRCalendarService()
    calendarService.authorizer = self.authorization
    calendarService.shouldFetchNextPages = true
        
    let event = GTLRCalendar_Event()
    event.identifier = eventId
    event.summary = eventName
        
    let gtlrDateTimeStart: GTLRDateTime = GTLRDateTime(date: startDateTime)
    let startEventDateTime: GTLRCalendar_EventDateTime = GTLRCalendar_EventDateTime()
    startEventDateTime.dateTime = gtlrDateTimeStart
    event.start = startEventDateTime
        
    let gtlrDateTimeEnd: GTLRDateTime = GTLRDateTime(date: endDateTime)
    let endEventDateTime: GTLRCalendar_EventDateTime = GTLRCalendar_EventDateTime()
    endEventDateTime.dateTime = gtlrDateTimeEnd
    event.end = endEventDateTime

    let query = GTLRCalendarQuery_EventsUpdate.query(withObject: event, calendarId: "primary", eventId: eventId)
    calendarService.executeQuery(query, completionHandler: { (ticket, event, error) -> Void in
        if let error = error {
            NSLog("\(error)")
        }
    })
}

For update, set the ID of the corresponding event in the identifier property of the GTLRCalendar_Event class. Then set the value you want to change to a property of the GTLRCalendar_Event class. After that, generate GTLRCalendarQuery_EventsUpdate class for updating Google Calendar events with GTLRCalendar_Event class as an argument, and execute the executeQuery method of GTLRCalendarService class with that as an argument.

Delete event

Finally, delete the event in Google Calendar. It is a program that deletes the corresponding event from Google Calendar when the identifier of the event is passed to the delete method.

import UIKit
import AppAuth
import GTMAppAuth
import GoogleAPIClientForREST

---------------- (Omission) ----------------

private var authorization: GTMAppAuthFetcherAuthorization?
typealias showAuthorizationDialogCallBack = ((Error?) -> Void)

private func showAuthorizationDialog(callBack: @escaping showAuthorizationDialogCallBack) {
---------------- (Omission) ----------------
}

private func delete(eventId: String) {
        
    if GTMAppAuthFetcherAuthorization(fromKeychainForName: "authorization") != nil {
        self.authorization = GTMAppAuthFetcherAuthorization(fromKeychainForName: "authorization")!
    }
        
    if self.authorization == nil {
        showAuthorizationDialog(callBack: {(error) -> Void in
            if error == nil {
                self.deleteCalendarEvent(eventId: eventId)
            }
        })
    } else {
        self.deleteCalendarEvent(eventId: eventId)
    }
}
    
private func deleteCalendarEvent(eventId: String) {
    let calendarService = GTLRCalendarService()
    calendarService.authorizer = self.authorization
    calendarService.shouldFetchNextPages = true
        
    let query = GTLRCalendarQuery_EventsDelete.query(withCalendarId: "primary", eventId: eventId)
    calendarService.executeQuery(query, completionHandler: { (ticket, event, error) -> Void in
        if let error = error {
            NSLog("\(error)")
        }
    })
}

The deletion can be done by generating the GTLRCalendarQuery_EventsDelete class with the event identifier as an argument and executing the executeQuery method of GTLRCalendarService with that as an argument.

Sample program

The sample program created this time is available on GitHub. https://github.com/naosekig/GoogleCalendarSample

References

CocoaDocs.org - GoogleAPIClientForRest I tried hitting Google Calendar API with Qiita: Swift4

Recommended Posts

Access Google Calendar from the iOS app
Import the schedule obtained from "Schedule-kun" into Google Calendar
Generate daily reports from Google Calendar
How to access the Datastore from the outside
Get holidays with the Google Calendar API
Operate the schedule app using python from iphone
Access the variables defined in the script from the REPL
Get the value from the [Django] Form
Access Google Calendar from the iOS app
Read the dissertation Deep Self-Learning From Noisy Labels
Get the latest schedule from Google Calendar and notify it on LINE every morning
Access bitcoind from python