質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Google カレンダー

Google カレンダーは、Google社が提供する無料のスケジュール管理ツールです。パソコンやスマートフォン、タブレットなどからアクセスし、スケジュールの追加・変更が可能。Googleアカウントがあれば誰でも使用できます。

Google API

Googleは多種多様なAPIを提供していて、その多くはウェブ開発者向けのAPIです。それらのAPIは消費者に人気なGoogleのサービス(Google Maps, Google Earth, AdSense, Adwords, Google Apps,YouTube等)に基づいています。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

1回答

1824閲覧

SwiftでGoogleカレンダーAPIを叩いてイベントを追加したい

tmyk1979

総合スコア145

Google カレンダー

Google カレンダーは、Google社が提供する無料のスケジュール管理ツールです。パソコンやスマートフォン、タブレットなどからアクセスし、スケジュールの追加・変更が可能。Googleアカウントがあれば誰でも使用できます。

Google API

Googleは多種多様なAPIを提供していて、その多くはウェブ開発者向けのAPIです。それらのAPIは消費者に人気なGoogleのサービス(Google Maps, Google Earth, AdSense, Adwords, Google Apps,YouTube等)に基づいています。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

0クリップ

投稿2022/03/26 21:53

編集2022/04/07 03:39

前提

SwiftでGoogleカレンダーAPIを叩いてイベントを追加したいのですが、GoogleSignInを使ってログインする所まではできたのですが、その先のイベントを追加する所でどのようにリクエストを送れば良いのか分からず困っています。

どうすればイベントを追加できるかご教示いただきたいと思います。

発生している問題・エラーメッセージ

下記のコードの通りだと以下のエラーログのようにmessageが「Bad Request」になるのですが、httpBodyを書かずにリクエストを送るとmessageが「missing end time」となります。ただ、Getメソッドでリクエストを送るとGetなりのレスポンスが返ってくるのでエンドポイントが間違っているわけではなさそうです。

Optional(["error": { code = 400; errors = ( { domain = global; message = "Bad Request"; reason = badRequest; } ); message = "Bad Request"; }])

該当のソースコード

Swift

1import UIKit 2import GoogleSignIn 3 4class ViewController: UIViewController { 5 6 let signInConfig = GIDConfiguration.init(clientID: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") 7 8 //サインインボタン 9 @IBAction func signIn(_ sender: AnyObject) { 10 11 GIDSignIn.sharedInstance.signIn(with: signInConfig, presenting: self) { user, error in 12 guard error == nil else { 13 return } 14 // If sign in succeeded, display the app's main content View. 15 16 print("ログイン") 17 let fullName = user?.profile?.name 18 let email = user?.profile?.email 19 20 let driveScopes = [ 21 "https://www.googleapis.com/auth/calendar.readonly", 22 "https://www.googleapis.com/auth/calendar", 23 "https://www.googleapis.com/auth/calendar.events.readonly", 24 "https://www.googleapis.com/auth/calendar.events" 25 ] 26 let grantedScopes = user?.grantedScopes 27 28 if grantedScopes == nil || !grantedScopes!.contains(driveScopes[0]) || !grantedScopes!.contains(driveScopes[1]) || !grantedScopes!.contains(driveScopes[2]) || !grantedScopes!.contains(driveScopes[3]) { 29 // Request additional Drive scope. 30 let additionalScopes = [ 31 "https://www.googleapis.com/auth/calendar.readonly", 32 "https://www.googleapis.com/auth/calendar", 33 "https://www.googleapis.com/auth/calendar.events.readonly", 34 "https://www.googleapis.com/auth/calendar.events" 35 ] 36 GIDSignIn.sharedInstance.addScopes(additionalScopes, presenting: self) { user, error in 37 guard error == nil else { return } 38 guard let user = user else { return } 39 40 // Check if the user granted access to the scopes you requested. 41 getEvents(token: user.authentication.accessToken) 42 } 43 } else { 44 getEvents(token: user!.authentication.accessToken) 45 } 46 } 47 } 48 49 //サインアウトボタン 50 @IBAction func signOut(_ sender: AnyObject) { 51 GIDSignIn.sharedInstance.signOut() 52 print("ログアウト") 53 } 54} 55 56private func getEvents(token: String) { 57 58 var urlComponents = URLComponents(string: "https://www.googleapis.com/calendar/v3/calendars/xxxxxxxxx(カレンダーID)/events") 59 urlComponents?.queryItems = [ 60 URLQueryItem(name: "calendarId", value: "primary") // 例) emailとpasswordを付与 61 ] 62 let config = URLSessionConfiguration.default 63 let session = URLSession(configuration: config) 64 var request = URLRequest(url: (urlComponents?.url)!) 65 request.httpMethod = "POST" 66 request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization") 67 68//==================================================== 69//そもそもここ以外に問題があるのかも知れませんが、現時点ではここの正しい 70//書き方が分からないためにうまく行っていないと思っています。 71 72 request.httpBody = "end=datetime:2022-03-27T00:00:00+09:00&timeZone:Asia/Tokyo".data(using: .utf8) 73 74//================================================ 75 76 let task = session.dataTask(with: request) { (data, response, error) in 77 if let data = data { 78 let json = try? (JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String:Any]) 79 print(json) 80 } 81 } 82 task.resume() 83}

試したこと

GoogleDevelopersのサイトを見ながらコードを書いたのですが、Swiftの例文が無いので他の言語の例文を見ても何をやっているのか想像するしかありませんでした。

「Swift Google CalendarAPI イベント 追加」など色々な言葉を組み合わせて検索し、片っ端から例文を試したのですが、エラーや警告に阻まれてどれもうまく行きませんでした。

ディベロッパーサイトの「Try it now」も試したのですが、そもそもどんな内容でリクエストを送れば事足りるのかも分からないため、色々試しましたが様々なエラーが返ってくるだけでした。

どんな内容をどのようにリクエストとして送れば正しくレスポンスを得られるのか、教えていただけると助かります。よろしくお願いします。

補足情報(FW/ツールのバージョンなど)

Xcode11.6
iPhoneSE
iOS 13.6

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tmyk1979

2022/03/28 07:49

アドバイスありがとうございます。 提示していただいたサイトは1回記事に沿ってコードを書いてみたのですが、ログイン画面を表示させる事ができずに断念していました。 とにかくもう一度やってみます。 取り急ぎお礼まで。
tmyk1979

2022/03/28 20:34

提示していただいたサイトのコードに沿ってもう一度やってみたのですが、ログイン画面は表示する事ができました。ただ、質問する時に作ったプロジェクトをコピーしたにも関わらず「承認エラー」となってしまい、リクエストの詳細には「このセクションのコンテンツはアプリデベロッパーが提供したものです。このコンテンツは、Googleで審査、検証されていません。」と表示されてしまいます。 質問する際に作ったプロジェクトでは自分のアカウントをOAuth ユーザーに登録したら、この認証エラーを解除できたのですが、今はすでに自分のアカウトはOAuthユーザーに登録されているので、認証エラーを解除する方法が分からない状況です。 もう少し自分で何とかできないか頑張ってみますので、もうしばらくお待ちください。m(__)m
tmyk1979

2022/03/29 19:26

質問を書いた時点ではredirectURLを認証済みドメインと同じ「example.com」にしていたのですが、(TwitterのcallbackURLのように同じにした方が良いと思っていた)サイトの例文の通りにした所、認証エラーも解消できました。もう少しでイベント追加もできるかもしれません。
guest

回答1

0

自己解決

hoshi-takanori様のアドバイスに記載されていたURLの通りにやってうまく行きました。

この質問をする前に、すでに提示されたURLの通りに自分でもやっておりましたが、うまく行っていなかったのです。
しかし、もう一度トライする事でうまく行きました。そのキッカケを与えてくださったhosi-takanori様にベストアンサーを付けたい所ですが、追記・修正依頼にはベストアンサーを付けられないので自己回答とします。

hoshi-takanori様にはこの場を借りてお礼を申し上げます。
ありがとうございました。

今回、すでにやってみて上手くいかなかったものが何故うまく行ったかというと、提示されているURLの例文では省略されている部分があるのですが、その部分を私は間違っていたためでした。

最初はビルドしたら自動的にログインしてイベントを追加できるようにViewDidLoad内にログインからイベントを追加する処理を書いていました。それだと

Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior

・・・というエラーが出てしまい、ログインもできませんでした。

このエラーは翻訳・検索ともにしたのですが、解消法は分かりませんでした。
ただ、何となくViewDidLoadが終わる前にログイン画面を出そうとしてしまっているのかな・・・と想像しました。

そこで、StoryBoardにログインボタンを作り、ボタンを押したらログインとイベントの追加を行うようにしたところ、うまく行きました。

以下がうまく行ったコードになります。

Swift

1import UIKit 2import AppAuth 3import GTMAppAuth 4import GoogleAPIClientForREST 5 6class ViewController: UIViewController { 7 8 private var authorization: GTMAppAuthFetcherAuthorization? 9 private let clientID = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 10 private let iOSUrlScheme = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 11 typealias showAuthorizationDialogCallBack = ((Error?) -> Void) 12 13 struct GoogleCalendaraEvent { 14 var id: String 15 var name: String 16 var startDate: Date? 17 var endDate: Date? 18 } 19 private var googleCalendarEventList: [GoogleCalendaraEvent] = [] 20 21 private func showAuthorizationDialog(callBack: @escaping showAuthorizationDialogCallBack) { 22 let scopes = [ 23 "https://www.googleapis.com/auth/calendar", 24 "https://www.googleapis.com/auth/calendar.readonly", 25 "https://www.googleapis.com/auth/calendar.events", 26 "https://www.googleapis.com/auth/calendar.events.readonly" 27 ] 28 29 let configuration = GTMAppAuthFetcherAuthorization.configurationForGoogle() 30 let redirectURL = URL.init(string: iOSUrlScheme + ":/oauthredirect") 31 let request = OIDAuthorizationRequest.init( 32 configuration: configuration, 33 clientId: clientID, 34 scopes: scopes, 35 redirectURL: redirectURL!, 36 responseType: OIDResponseTypeCode, 37 additionalParameters: nil 38 ) 39 40 let appDelegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate 41 appDelegate.currentAuthorizationFlow = OIDAuthState.authState( 42 byPresenting: request, 43 presenting: self, 44 callback: { (authState, error) in 45 if let error = error { 46 NSLog("\(error)") 47 } else { 48 if let authState = authState { 49 self.authorization = GTMAppAuthFetcherAuthorization.init(authState: authState) 50 GTMAppAuthFetcherAuthorization.save(self.authorization!, toKeychainForName: "authorization") 51 } 52 } 53 callBack(error) 54 }) 55 } 56 57 private func add(eventName: String, startDateTime: Date, endDateTime: Date) { 58 59 if GTMAppAuthFetcherAuthorization(fromKeychainForName: "authorization") != nil { 60 self.authorization = GTMAppAuthFetcherAuthorization(fromKeychainForName: "authorization")! 61 } 62 63 if self.authorization == nil { 64 showAuthorizationDialog(callBack: {(error) -> Void in 65 if error == nil { 66 self.addCalendarEvent(eventName: eventName, startDateTime: startDateTime, endDateTime: endDateTime) 67 } 68 }) 69 } else { 70 self.addCalendarEvent(eventName: eventName, startDateTime: startDateTime, endDateTime: endDateTime) 71 } 72 } 73 74 private func addCalendarEvent(eventName: String, startDateTime: Date, endDateTime: Date) { 75 76 let calendarService = GTLRCalendarService() 77 calendarService.authorizer = self.authorization 78 calendarService.shouldFetchNextPages = true 79 80 let event = GTLRCalendar_Event() 81 event.summary = eventName 82 83 let gtlrDateTimeStart: GTLRDateTime = GTLRDateTime(date: startDateTime) 84 let startEventDateTime: GTLRCalendar_EventDateTime = GTLRCalendar_EventDateTime() 85 startEventDateTime.dateTime = gtlrDateTimeStart 86 event.start = startEventDateTime 87 88 let gtlrDateTimeEnd: GTLRDateTime = GTLRDateTime(date: endDateTime) 89 let endEventDateTime: GTLRCalendar_EventDateTime = GTLRCalendar_EventDateTime() 90 endEventDateTime.dateTime = gtlrDateTimeEnd 91 event.end = endEventDateTime 92 93 let query = GTLRCalendarQuery_EventsInsert.query(withObject: event, calendarId: "primary") 94 calendarService.executeQuery(query, completionHandler: { (ticket, event, error) -> Void in 95 if let error = error { 96 NSLog("\(error)") 97 } 98 }) 99 } 100 101 func dateFromString(string: String, format: String) -> Date { 102 let formatter: DateFormatter = DateFormatter() 103 formatter.calendar = Calendar(identifier: .gregorian) 104 formatter.dateFormat = format 105 return formatter.date(from: string)! 106 } 107 108 @IBAction func signIn(_ sender: UIButton) { 109 self.showAuthorizationDialog(callBack: {(error) -> Void in 110 if error == nil { 111 print("ログイン") 112 113 // 元の日付の文字列 114 let startDateString = "2022/04/06 18:00:00 +00:00" 115 let endDateString = "2022/04/06 21:00:00 +00:00" 116 117 // Dateに変換 118 let startDateTime = self.dateFromString(string: startDateString, format: "yyyy/MM/dd HH:mm:ss Z") 119 let endDateTime = self.dateFromString(string: endDateString, format: "yyyy/MM/dd HH:mm:ss Z") 120 print(startDateTime) 121 print(endDateTime) 122 123 self.addCalendarEvent(eventName: "sampleイベント0", startDateTime: startDateTime, endDateTime: endDateTime) 124 } 125 }) 126 } 127}

日付をDateに変換しているのは、最初からDate型で書こうと思ったのですが、どのように書いたら良いか分からず、自分ではあっていると思った書き方でもエラーを吐いてしまうので、余計な処理かも知れませんが日付からDateに変換するような処理を噛ませました。

今は追加したいイベントの日時までコードに直に書いている上、日本時間にも修正しておらず、ログインボタンを押すとイベントの追加までが行われるようになっていますが、いずれログイン後の画面で日本の時間で日時を指定できるようにして、追加ボタンを押すとイベントを追加できるような流れにしたいと思います。

アドバイスをくださったhoshi-takanori様には他の質問でも何度もアドバイスをいただいております。
本当にいつもありがとうございます。

投稿2022/04/06 18:39

tmyk1979

総合スコア145

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問