这次,我想通过Google Calenear API从iOS应用访问Google日历。
环境:Xcode 12.0,Swift 5
准备
首先,转到Google Cloud Platform并启用Google Calendar API。
接下来,获取用于OAuth身份验证的OAuth客户端ID。
请按照以下文章中的"注册客户端ID"中所述的过程来申请客户端ID。
我尝试使用Swift 4击中Google Calendar API
使用的外部库
Google API本身是REST API,但是很难直接调用它,因此我将使用一个外部库。
这次,我们将使用以下库。
?Google身份验证
AppAuth
GTMAppAuth
?使用Google日历访问
GoogleAPIClientForREST /日历
以上所有库都可以与CocoaPods一起安装。
如下所示编写Podfile并执行pod install进行安装。
1 2 3 4 5 6 7 8 9 10 | platform :ios, '14.0' target 'GoogleCalendarSample' do use_frameworks! pod 'AppAuth' pod 'GTMAppAuth' pod 'GoogleAPIClientForREST/Calendar' end |
验证
实现使用AppAuth和GTMAppAuth执行Google身份验证的过程。
首先,将OIDExternalUserAgentSession类添加到AppDelegate。
AppDelegate.swift
1 2 3 4 5 6 7 8 9 | import UIKit import AppAuth import GTMAppAuth @main class AppDelegate: UIResponder, UIApplicationDelegate { var currentAuthorizationFlow: OIDExternalUserAgentSession? ---------------- (以下略) ---------------- |
然后在屏幕上描述用于Google身份验证的以下过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | import UIKit import AppAuth import GTMAppAuth import GoogleAPIClientForREST ---------------- (中略) ---------------- 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) }) } |
以下变量描述了获得OAuth客户端ID时获得的ID。
在clientID中输入OAuth 2.0客户端ID,在reverseClientID中输入反向客户端ID。
1 2 | private let clientID = "xxxxxxxxxxxxxxxxxxxx" private let reverseClientID = "xxxxxxxxxxxxxxxxxxxx" |
这次将所需权限设置为数组作用域。这次,我们正在请求搜索和更改Google日历的权限。
1 | 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"] |
当执行OIDAuthState类的authState方法时,将显示以下Google身份验证对话框。

如果用户在对话框中正确输入了gmail地址和密码,并且身份验证完成,则authState方法的回调函数将生成并保存GTMAppAuthFetcherAuthorization类。
在保留此GTMAppAuthFetcherAuthorization类的同时,无需重新显示身份验证对话框。
搜索事件
在
中,我想继续使用GoogleAPIClientForREST访问Google日历。
首先,我将描述从Google日历获取现有事件的过程。
如果将开始日期和时间以及结束日期和时间传递给get方法,则该程序会从Google日历中搜索开始日期和时间以及结束日期和时间之间的事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | import UIKit import AppAuth import GTMAppAuth import GoogleAPIClientForREST ---------------- (中略) ---------------- 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) { ---------------- (中略) ---------------- } 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)) } } } }) } |
首先,检查Goole身份验证是否完成。
检查是否已保存GTMAppAuthFetcherAuthorization类,如果未保存,请调用先前创建的showAuthorizationDialog函数以显示Google身份验证对话框并获取GTMAppAuthFetcherAuthorization类。
如果保存了GTMAppAuthFetcherAuthorization类,请按原样使用它。
然后使用GoogleAPIClientForREST从Googl日历获取事件。
首先,生成GTLRCalendarService类以访问Goole日历,并在authorizer属性中设置GTMAppAuthFetcherAuthorization类。
1 2 3 | let calendarService = GTLRCalendarService() calendarService.authorizer = self.authorization calendarService.shouldFetchNextPages = true |
接下来,生成GTLRCalendarQuery_EventsList类以从Google日历中搜索事件,并将开始日期和时间以及结束日期和时间设置为搜索条件。
1 2 3 | let query = GTLRCalendarQuery_EventsList.query(withCalendarId: "primary") query.timeMin = GTLRDateTime(date: startDateTime) query.timeMax = GTLRDateTime(date: endDateTime) |
然后,以该GTLRCalendarQuery_EventsList类作为参数,执行GTLRCalendarService类的executeQuery方法以从Google日历获取事件。
当可以获取事件时,executeQuery方法的Callback函数将返回GTLRCalendar_Events类,因此将从此处获取事件信息。
1 2 3 4 5 6 7 8 9 10 | 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)) } } |
特别是标识符(事件的唯一ID)很重要。
更改或删除事件时,此标识符是关键。
新增活动
接下来,我想在
中向Google日历添加事件。
它是通过将事件名称,开始日期和时间以及结束日期和时间传递给add方法在Google日历中创建事件的程序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | import UIKit import AppAuth import GTMAppAuth import GoogleAPIClientForREST ---------------- (中略) ---------------- private var authorization: GTMAppAuthFetcherAuthorization? typealias showAuthorizationDialogCallBack = ((Error?) -> Void) private func showAuthorizationDialog(callBack: @escaping showAuthorizationDialogCallBack) { ---------------- (中略) ---------------- } 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)") } }) } |
生成GTLRCalendarService类的过程与搜索时的过程相同,因此我们将在后续部分中进行说明。
生成GTLRCalendar_Event类以设置要添加的事件的信息。
这次,设置了事件名称,开始日期和时间以及结束日期和时间,因此在GTLRCalendar_Event类的summary属性,start属性和end属性中进行了设置。
1 2 3 4 5 6 7 8 9 10 11 12 | 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 |
另外,在添加新事件的情况下,成为事件唯一ID的标识符由Google日历自动分配,因此无需在此处进行设置。
然后,以GTLRCalendar_Event类作为参数,生成一个GTLRCalendarQuery_EventsInsert类以添加到Google日历,并执行GTLRCalendarService类的executeQuery方法将一个新事件添加到Google Calendar。
活动变更
接下来,让我们更改现有事件的信息。
当将事件标识符,事件名称,开始日期和时间,结束日期和时间传递给更新方法时,该程序会更改Google日历中相应标识符的事件信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | import UIKit import AppAuth import GTMAppAuth import GoogleAPIClientForREST ---------------- (中略) ---------------- private var authorization: GTMAppAuthFetcherAuthorization? typealias showAuthorizationDialogCallBack = ((Error?) -> Void) private func showAuthorizationDialog(callBack: @escaping showAuthorizationDialogCallBack) { ---------------- (中略) ---------------- } 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)") } }) } |
对于
更新,请在GTLRCalendar_Event类的identifier属性中设置相应事件的ID。然后,将要更改的值设置为GTLRCalendar_Event类的属性。
之后,生成GTLRCalendarQuery_EventsUpdate类以GTLRCalendar_Event类作为参数来更新Google日历事件,并以GTLRCalendarService类的executeQuery方法作为参数。
删除活动
最后,删除Google日历中的事件。
当事件的标识符传递给delete方法时,该程序将从Google日历中删除相应的事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | import UIKit import AppAuth import GTMAppAuth import GoogleAPIClientForREST ---------------- (中略) ---------------- private var authorization: GTMAppAuthFetcherAuthorization? typealias showAuthorizationDialogCallBack = ((Error?) -> Void) private func showAuthorizationDialog(callBack: @escaping showAuthorizationDialogCallBack) { ---------------- (中略) ---------------- } 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)") } }) } |
删除可以通过生成以事件标识符为参数的GTLRCalendarQuery_EventsDelete类并以GTLRCalendarService的executeQuery方法为参数来完成。
样例程序
这次创建的示例程序可在GitHub上找到。
https://github.com/naosekig/GoogleCalendarSample
参考
CocoaDocs.org --GoogleAPIClientForRest
Qiita:我尝试使用Swift 4
击中Google Calendar API