- 投稿日:2020-09-27T23:45:37+09:00
iOS14 encrypted DNS(DNS over TLS, DNS over HTTPS)
概要と注意事項
- iOS14から追加されたDNSSettingsペイロードによる暗号化DNS利用プロファイルの例
- macOS11からでも使えるっぽい
- DNSフィルタリングと組み合わせれば簡単なフィルタリングがOS標準機能で実現できる
- 例えばサンプルのDoHの方であれば、エロサイトを(完全ではないだろうが)ブロックできる
- 解除できちゃうからやっぱりSupervisorでガチガチにするしかねぇ
- ProhibitDisablementがtrueの場合は要Supervisor
- falseの場合、「設定→一般→VPNとネットワーク」で変更が可能
- ちゃんと切り替わっているかはCloudflareのDebug Informationでしか見ていない
- 値を書き換えればGoogle Public DNS、IIS Public DNSは使えたっぽい
- こちらはちゃんと切り替わっているかの裏まではきちんと確認していない
- 上のリファレンスのうち最低限しか記述していないため、すべての通信のDNSが切り替わる
- 特定のDNSサーバでしか名前解決できないようなものがあるともちろんコケる
- 社内Wi-Fiとかキャリアサービスとか、VVMとMMS辺りが怪しげ
- OnDemandRulesを書けば「モバイルの時だけ」とか「このWi-Fi APに繋いだ時だけ」とかルールが作れるっぽい
- 複数設定が入る
- サンプルの二つはPayloadIdentifierが同一なので互いに上書きし合う(重複せずどちらか一方)
- 値を書き換えて複数入れ、切り替えは可能
- 設定は最後に入れたものに切り替わるっぽい
- 一つのプロファイルに複数のDNS設定を記載することも可能
- これを入れたから通信速度が速くなるとかそんなことを期待してはいけない
- 人柱になりたい奇特な方がいれば、末尾の「参考元とか」リンクからプロファイルの入手が可能
- どうでもいい話、Android EnterpriseでもAPI 29(Android 10)から同様の機能あり
- プロファイルを入れる方法でなく、アプリから制御する方法もあるっぽい(NextDNSがこれ?)
- Wi-Fi設定の警告が消せるかもしれないけど未確認
DNS over TLS
Cloudflare 1.1.1.1 のTLSを利用する場合
DoT_Cloudflare.mobileconfig<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>PayloadContent</key> <array> <dict> <key>PayloadDescription</key> <string>DNS Settings</string> <key>PayloadDisplayName</key> <string>DNS設定(DoT)</string> <key>PayloadIdentifier</key> <string>com.apple.dnsSettings.managed.91C836B1-5749-4C12-B613-384008F96D92</string> <key>PayloadType</key> <string>com.apple.dnsSettings.managed</string> <key>PayloadUUID</key> <string>91C836B1-5749-4C12-B613-384008F96D92</string> <key>PayloadVersion</key> <integer>1</integer> <key>DNSSettings</key> <dict> <key>DNSProtocol</key> <string>TLS</string> <key>ServerAddresses</key> <array> <string>2606:4700:4700::1111</string> <string>2606:4700:4700::1001</string> <string>1.1.1.1</string> <string>1.0.0.1</string> </array> <key>ServerName</key> <string>cloudflare-dns.com</string> </dict> <key>ProhibitDisablement</key> <false/> </dict> </array> <key>PayloadDisplayName</key> <string>DNSSettings</string> <key>PayloadIdentifier</key> <string>com.apple.dnsSettings.managed</string> <key>PayloadRemovalDisallowed</key> <false/> <key>PayloadType</key> <string>Configuration</string> <key>PayloadUUID</key> <string>2C3575D8-8480-4AD0-B6A9-803D9FBEDB5E</string> <key>PayloadVersion</key> <integer>1</integer> </dict> </plist>DNS over HTTPS
Cloudflare 1.1.1.1 for Families(Block Malware And Adult Content) のHTTPSを利用する場合
DoH_Cloudflare_Families3.mobileconfig<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>PayloadContent</key> <array> <dict> <key>PayloadDescription</key> <string>DNS Settings</string> <key>PayloadDisplayName</key> <string>DNS設定(DoH)</string> <key>PayloadIdentifier</key> <string>com.apple.dnsSettings.managed.91C836B1-5749-4C12-B613-384008F96D92</string> <key>PayloadType</key> <string>com.apple.dnsSettings.managed</string> <key>PayloadUUID</key> <string>91C836B1-5749-4C12-B613-384008F96D92</string> <key>PayloadVersion</key> <integer>1</integer> <key>DNSSettings</key> <dict> <key>DNSProtocol</key> <string>HTTPS</string> <key>ServerURL</key> <string>https://family.cloudflare-dns.com/dns-query</string> </dict> <key>ProhibitDisablement</key> <false/> </dict> </array> <key>PayloadDisplayName</key> <string>DNSSettings</string> <key>PayloadIdentifier</key> <string>com.apple.dnsSettings.managed</string> <key>PayloadRemovalDisallowed</key> <false/> <key>PayloadType</key> <string>Configuration</string> <key>PayloadUUID</key> <string>2C3575D8-8480-4AD0-B6A9-803D9FBEDB5E</string> <key>PayloadVersion</key> <integer>1</integer> </dict> </plist>キーと値
- PayloadDisplayName
- DNS設定名、切替画面のリストとプロファイル内での表示
- DNSProtocol
- DoTならばTLS、DoHならHTTPS
- DoT用設定値
- ServerAddresses
- DNSサーバのIPアドレスを記載する(ホスト名でも通るがリファレンス上は「IPv4 and IPv6 addresses.」)
- ServerName
- たぶんサーバ証明書のSANs
- DoH用設定値
- ServerURL
- クエリを投げる先のURL
ProhibitDisablement
- 手動変更を禁止するのであればtrue
PayloadDisplayName
- プロファイル名
組織名とか説明書きとかその他諸々は省略
参考元とか
- DNSSettings | Apple Developer Documentation
- 公式リファレンス
- GitHub paulmillr/encrypted-dns
- いくつかサンプルがある様子
- iOS 14でAdGuard DNSを使う方法 | AdGuard
- 既にこのプロファイルを利用するサービスあり
- iOS14の暗号化DNSサポートと広告ブロック | 280blocker
- 投稿日:2020-09-27T15:57:25+09:00
SwiftUIでフォトライブラリ
SwiftUIでフォトライブラリを表示する
ボタンをタップして、フォトライブラリを表示します。
フォトライブラリで写真を選択すると、全画面に表示します。環境
Swift 5.3
Xcode 12.0.1
macOS 10.15.7フォトライブラリ
フォトライブラリをSwiftUIでラップした構造体を定義します。
ImagePicker.swiftimport SwiftUI struct ImagePicker: UIViewControllerRepresentable { var sourceType: UIImagePickerController.SourceType = .photoLibrary func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController { let imagePicker = UIImagePickerController() imagePicker.allowsEditing = false imagePicker.sourceType = sourceType return imagePicker } func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) { } }最初の画面
アプリの基本となる画面の構造体です。
ステート変数の基づいて、フォトライブラリをシート形式で表示します。ContentView.swiftimport SwiftUI struct ContentView: View { @State private var image = UIImage() @State private var isShowPhotoLibrary = false var body: some View { VStack { Image(uiImage: self.image) Button(action: { self.isShowPhotoLibrary = true }, label: { Text("Photo Library") .padding() }) } .sheet(isPresented: $isShowPhotoLibrary, content: { ImagePicker(sourceType: .photoLibrary) }) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }フォトライブラリの写真を選択する
UIImagePickerControllerDelegate
プロトコルに準拠します。ImagePicker.swiftimport SwiftUI struct ImagePicker: UIViewControllerRepresentable { // MARK: - Working with UIViewControllerRepresentable var sourceType: UIImagePickerController.SourceType = .photoLibrary func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController { let imagePicker = UIImagePickerController() imagePicker.allowsEditing = false imagePicker.sourceType = sourceType imagePicker.delegate = context.coordinator // Coordinater to adopt UIImagePickerControllerDelegate Protcol. return imagePicker } func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) { } // MARK: - Using Coordinator to Adopt the UIImagePickerControllerDelegate Protocol @Binding var selectedImage: UIImage @Environment(\.presentationMode) private var presentationMode func makeCoordinator() -> Coordinator { Coordinator(self) } final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate { var parent: ImagePicker init(_ parent: ImagePicker) { self.parent = parent } func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage { parent.selectedImage = image } parent.presentationMode.wrappedValue.dismiss() } } }選択した写真を表示する
Image
ビューにモディファイアを追加します。
画面遷移するタイミングで、選択した写真をimage
プロパティに設定します。ContentView.swiftstruct ContentView: View { @State private var image = UIImage() @State private var isShowPhotoLibrary = false var body: some View { VStack { Image(uiImage: self.image) .resizable() .scaledToFill() .frame(minWidth: 0, maxWidth: .infinity) .edgesIgnoringSafeArea(.all) Button(action: { self.isShowPhotoLibrary = true }, label: { Text("Photo Library") .padding() }) } .sheet(isPresented: $isShowPhotoLibrary, content: { ImagePicker(sourceType: .photoLibrary, selectedImage: self.$image) }) } }
- 投稿日:2020-09-27T13:23:06+09:00
iOSアプリからGoogleカレンダーにアクセスする
今回はiOSアプリからGoogle Calenear APIを通してGoogleカレンダーにアクセスしてみたいと思います。
環境: Xcode 12.0、Swift 5
準備
まずGoogle Cloud PlatformにアクセスしてGoogle Calendar APIを有効にします。
続いてOAuth認証を行うためのOAuthクライアントIDを取得します。
以下の記事の「クライアントIDを登録」に書かれている手順に従ってクライアントIDの取得の申請を行います。Swift4でGoogleCalendarAPIを叩いてみた
使用する外部ライブラリ
Google API自体はREST APIになっていますが、これを直接呼び出すのは大変なので外部ライブラリを使用することにします。
今回は以下のライブラリを使用します。・Google認証
AppAuth
GTMAppAuth・Google Calendarとのアクセス
GoogleAPIClientForREST/Calendar上記のライブラリはいずれもCocoaPodsで導入できます。
以下のようにPodfileを記述しpod installを実行して導入します。platform :ios, '14.0' target 'GoogleCalendarSample' do use_frameworks! pod 'AppAuth' pod 'GTMAppAuth' pod 'GoogleAPIClientForREST/Calendar' end認証
ではAppAuthとGTMAppAuthを使用してGoogle認証を行う処理を実装します。
まずAppDelegateにOIDExternalUserAgentSessionクラスを追加します。
AppDelegate.swiftimport UIKit import AppAuth import GTMAppAuth @main class AppDelegate: UIResponder, UIApplicationDelegate { var currentAuthorizationFlow: OIDExternalUserAgentSession? ---------------- (以下略) ----------------その上でGoogle認証を行う画面で以下の処理を記述します。
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を記載します。
clientにはOAuth 2.0クライアントIDを、reverseClientIDにはリバースクライアントIDを記載します。private let clientID = "xxxxxxxxxxxxxxxxxxxx" private let reverseClientID = "xxxxxxxxxxxxxxxxxxxx"今回必要とする権限を配列scopesに設定します。今回はGoogleカレンダーを検索・変更する為の権限を要求しています。
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メソッドのCallback関数でGTMAppAuthFetcherAuthorizationクラスを生成し保存します。
このGTMAppAuthFetcherAuthorizationクラスが残っているうちは認証ダイアログを再度表示する必要はありません。イベントの検索
では続いてGoogleAPIClientForRESTを使用してGoogleカレンダーにアクセスしてみたいと思います。
まずはGoogleカレンダーから既存のイベントを取得する処理を記載します。
getメソッドに開始日時、終了日時を渡すとGoogleカレンダーから開始日時 〜 終了日時の間にあるイベントを検索するプログラムです。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カレンダーからイベントを取得します。
まずGooleカレンダーにアクセスする為のGTLRCalendarServiceクラスを生成しauthorizerプロパティにGTMAppAuthFetcherAuthorizationクラスを設定します。let calendarService = GTLRCalendarService() calendarService.authorizer = self.authorization calendarService.shouldFetchNextPages = true続いてGoogleカレンダーからイベントを検索するためのGTLRCalendarQuery_EventsListクラスを生成し、検索条件として開始日時と終了日時を設定します。
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クラスが返ってきますのでここからイベントの情報を取得します。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)) } }特にidentifier(イベントのユニークID)が重要です。
イベントの変更や削除を行う時はこのidentifierがキーになります。イベントの追加
では次はGoogleカレンダーにイベントを追加してみたいと思います。
addメソッドにイベント名、開始日時、終了日時を渡すとGoogleカレンダーにイベントを作成するプログラムです。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プロパティに設定しています。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となるidentifierは新規追加の場合はGoogleカレンダーによって自動的に割り当てられますのでここで設定する必要はありません。
そしてGoogleカレンダーに新規追加する為のGTLRCalendarQuery_EventsInsertクラスをGTLRCalendar_Eventクラスを引数にして生成し、GTLRCalendarServiceクラスのexecuteQueryメソッドを実行することでGoogleカレンダーにイベントが新規追加されます。
イベントの変更
続いて既存のイベントの情報を変更してみます。
updateメソッドにイベントのidentifier、イベント名、開始日時、終了日時を渡すとGoogleカレンダーにある該当のidentifierのイベントの情報を変更するプログラムです。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クラスのプロパティに設定します。
その後、Googleカレンダーのイベントを更新する為のGTLRCalendarQuery_EventsUpdateクラスをGTLRCalendar_Eventクラスを引数にして生成し、それを引数にGTLRCalendarServiceクラスのexecuteQueryメソッドを実行します。イベントの削除
最後にGoogleカレンダーのイベントの削除を行います。
deleteメソッドにイベントのidentifierを渡すとGoogleカレンダーから該当のイベントを削除するプログラムです。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クラスをイベントのidentifierを引数にして生成し、それを引数にGTLRCalendarServiceのexecuteQueryメソッドを実行することで行うことができます。
サンプルプログラム
今回作成したサンプルプログラムはGitHubで公開しています。
https://github.com/naosekig/GoogleCalendarSample参考文献
CocoaDocs.org - GoogleAPIClientForRest
Qiita:Swift4でGoogleCalendarAPIを叩いてみた
- 投稿日:2020-09-27T09:23:48+09:00
React NativeでのModalの作り方
はじめに
現在、React Nativeでアプリを開発しており、いい感じのModal(ボタンを押したら下から画面が出てくるやつ)を作ろうとしたのですが、悪戦苦闘したため、記事に残し備忘録の1つとして残します。
作り方
まずはModal画面に遷移するボタンを作成しましょう。ちなみに、今回はuseStateを使用します。
App.jsexport default function App() { const [modalOpen, setModalOpen] = useState(false); return ( <View> <View> <Text style={{ fontSize: 24 }}>Lists</Text> <TouchableHighlight style={styles.openButton} onPress={() => { setModalOpen(true); }} > <Text>Add</Text> </TouchableHighlight> </View> ); } // スタイル const styles = StyleSheet.create({ openButton: { backgroundColor: "#F194FF", borderRadius: 20, padding: 10, paddingLeft: 20, paddingRight: 20, elevation: 2, }, });Buttonタグでもいいのですが、ボタンらしいスタイルにしたいので今回はTouchableHighlightを使用しました。以下のスタイルのようになります。
Modalを追加
App.jsreturn ( <View> {/* 投稿画面 */} <Modal visible={modalOpen} animationType="slide"> <View style={StyleSheet.modelContent}> <Text style={{ fontSize: 30, paddingTop: 50, marginLeft: "auto", marginRight: "auto", marginTop: "auto", marginBottom: 70, }} > Modal画面 </Text> </View> </Modal> <View> ~ </View> </View> );先ほどのコードの上にModalを追加すれば、完成です。Modal画面とボタンがある画面は別のファイルにコード記載した方が良さそうですね。今回は説明のためにあえて一緒に記述させました。以下がModal画面です。
これが下から出てきます。あとがき
本当はMaterial Iconを押した時に、Modalが出てくるようにしたいのですが、調べてもよくわからなかったので、自作でボタンを作成して実装しました。分かる方がいましたら、教えて欲しいです。
参考
- 投稿日:2020-09-27T07:42:44+09:00
Visionで画像の位置合わせ
パノラマ画像や手ブレ補正に使えます
手順
2枚目の画像で登録リクエストを作成します。
let request = VNTranslationalImageRegistrationRequest(targetedCIImage: image2, options: [:])1枚目の画像で実行します。
let handler = VNImageRequestHandler(ciImage: image1, options: [:]) do { try handler.perform([request]) } catch let error { print(error) }結果は2枚目の画像を1枚目の画像に位置合わせするためのCGAffineTransformで返されます。
guard let observation = request.results?.first as? VNImageTranslationAlignmentObservation else { return } let alignmentTransform = observation.alignmentTransform print(alignmentTransform) // CGAffineTransform(a: 1.0, b: 0.0, c: 0.0, d: 1.0, tx: -777.0, ty: 74.0)結果を元に、2枚の画像を合成します。
image2 = image2.transformed(by: alignmentTransform) let compositedImage = image1.applyingFilter("CIAdditionCompositing", parameters: ["inputBackgroundImage": image2])注意点
・何回か試してみたところ、画像内の顕著なオブジェクトを基準に合わせる傾向がある気がします。
・カメラで撮った画像の向きに注意しましょう。90度回転していたりします。
・水平移動には強いですが、カメラが回転すると、結果が変にずれやすいです。
上記のせいで不安定な挙動をすることがあります。?
お仕事のご相談こちらまで
rockyshikoku@gmail.comCore MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。