ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
位置情報へのアクセス許可の新機能
位置情報へのアクセス許可は新たな世代に移行しつつあります。必要とするユーザー許可を得る際の新しい推奨事項や手法や、新しい診断のシステムを通じて許可が得られない場合に通知を得る方法についてご紹介します。
関連する章
- 0:00 - Introduction
- 1:52 - Authorization goals
- 9:25 - Session lifecycle
- 13:29 - Diagnostic properties
リソース
-
ダウンロード
ようこそ Core Locationチームのエンジニア Adam Driscollです 本日は 位置情報へのアクセス許可に 関する新機能を紹介します ご存知のように Core Locationは Appleマップなどの アプリがユーザーの位置を 検出するための機能です 皆さんのアプリでも同様です Core Location APIを使用すれば 近隣の面白いイベントをユーザーに 知らせたり 展示会を案内したりできます 去年 チームメイトのSirajとNivashが 新しい2つのAPIオブジェクトを紹介しました CLLocationUpdateと CLMonitorです どちらもAsyncSequence インターフェイスを提供しており デバイスの移動時に 経度と緯度の最新情報を取得する あるいは 指定した空間条件が 満たされり満たされなくなったりするたびに 状態変更イベントを 容易に取得することができます
これらのAPIを使うには アプリの個々のユーザーから 許可を取る必要があります Core Location Authorization API によって実現しますが Swiftでは処理がかなり 複雑になる可能性があります 今日は この長いコードを このように短縮する方法を紹介します プロセスをより堅牢にする方法を紹介します 重要になるのはCLServiceSessionと 新システムのDiagnostic Propertiesです 3つのトピックに分けて詳しく説明します まず CLServiceSessionは アクセス許可の目標 つまり アプリの各機能に 必要なアクセス許可を アプリの実行中にCore Locationに 伝える新しい宣言方法です
次に セッションオブジェクトなので これを作成するタイミングや アプリを一時停止 または終了した時に 何が起きるかについても説明します
最後に まったく新しい Diagnostic Propertiesについて説明します これにより アプリは コンテキストに基づいて 現在の アクセス許可とその他のあらゆる Core Location APIオブジェクトの 機能を完全に把握します
まずは目標です 手続き型モデルから 宣言型モデルに移行し 位置情報への アクセス許可を扱います で LocationMonitorSampleアプリを 選択すると トグルと 上部のオプションの間に 位置情報の利用許可が このように表示されます 図にまとめて 少し簡素化すると このようになります
どのアプリもNotDeterminedから始まります ユーザーがそのアプリを承認して 次の状態に移行させると 次は 正確な位置情報または おおよその位置情報を選択できます
このプロセスを開始するにあたり これまではCLLocationManagerに加え requestWhenInUseAuthorizationなどの メソッドを使用して アクセス許可を求める必要がありました この場合 NotDeterminedから WhenInUseまたはDenialに移行できます これは明確なリクエスト方法ですが ユーザーが通常 おおよその位置情報だけを必要とする方法で アプリを使用しており そのためにその範囲で承認している 可能性があります しかし いずれターンバイターンの 詳細な道案内を要求したり 明らかに正確な位置情報を 必要とする機能を 使い始めるかもしれません そのときに requestTemporaryFullAccuracy (withPurposeKey:)を呼び出して 一時的に正確な位置情報を許可するよう 促すことができます ただし 一時的な許可は期限が切れます 00:03:06.902 --> 00:03:10.399 仮にWhenInUse許可が 一度しか許可されない設定で
一時的に付与され その後少しだけ機能を離脱し 友人とチャットしたとします その場合は もう一度 requestWhenInUseAuthorizationを 呼び出し おそらく requestTemporaryFullAccuracy (withPurposeKey:)も再使用します このサンプルでターンバイ ターン経路を実装しようとすると 完全精度のWhenInUse アクセス許可が不可欠になります そして このような 許可の目標により 図表の各ステップで やるべきことが決まってきます 今年登場したCLServiceSessionでは 許可の目標を直接表現して それをタスクに結びつけ 駆動させることができます この例で 一部のアプリのユーザーが通常は おおよその位置情報へのアクセスのみ 許可する可能性があると言いました それは一般的には問題ありません その選択を考慮して 目標を設定しましょう CLServiceSessionをこのように開始して .whenInUseを引数として使います Core Locationは各ユーザーに 適切なリクエストを提示して アプリをいずれかの状態にさせます ユーザーがナビゲーション機能を 使用する時が来たら fullAccuracyPurposeKeyを加えた CLServiceSessionを追加し 目標を絞り込みます これはレイヤー化のようなもので アプリの通常の目標が維持され さらに 一時的に完全精度を要求し 元のセッションを継続しながら 新たなセッションも追加できます ともあれ この「Nav」キーは 位置情報にアクセスするための リソースを参照します これで Core Locationが一時的な 完全精度の許可を 必要とした場合でも ナビゲーションの目的を説明する 文字列が提供され 完全精度が必要になった理由が 誰でもわかるようになります
ここで少し振り返りましょう まず CLServiceSessionオブジェクトは スクリプトを少し変更できます Core Locationに対して タスクではなく 必要なものが知らされます つまり アクセス許可を 要求したいタイミングで バックグラウンド化するなど テストが難しい問題について あれこれと心配する必要が なくなるのです これは回復可能です CLServiceSessionが維持されているので Core Locationは目標を認識しており 次にその目標が達成できそうな タイミングで動作します
したがって CLServiceSessionは プロアクティブに取得するべきです 例えば 特別な許可が必要になる機能を ユーザーが使用する際は アプリが既に完全精度で アクセスしていたとしても CLServiceSessionは維持します その機能が明示的に 完全精度を許可した タイミングを意識してください 大抵のユーザーはおおよその 位置情報しか使用せず アクセスを拒否することもできたのに なぜそうしなかったのでしょうか また ユーザーが特別な アクセス許可を得てまで 使おうとする機能とは 何でしょうか 完全精度のセッションはそのような 機能にのみ維持されるべきです 最後に CLServiceSessionは文字通り 不変で送信可能なオブジェクトです ただ 通常はニーズの変化を見越して 置き換えではなくレイヤー化を行います
もしアプリのコンポーネントが それぞれ異なる目標の 機能を担っており それらの機能がときどき レイヤー化または 入れ子化されるだけであれば それらのコンポーネントは 作成されるサービスセッションを 無視してまとめては なりません 必要なものだけを 必要なときにだけ 個別に作成してください レイヤー化された各機能は サービスセッション内で自己完結させ 残りはCore Locationに 処理させましょう
レイヤー化といえば 新登場したCLServiceSessionの 構造について説明してきましたが 去年紹介した各APIも liveUpdatesやイベントのシーケンスが 反復された際のある種の セッションを定義していました アプリがliveUpdatesを 反復しているなら 多くの場合 おそらく更新を 受信しようとしていますよね なのでCore Locationは CLServiceSessionを暗黙化して liveUpdatesまたは Monitor.eventsの反復処理に対応します これは非常に便利です コードを削除するだけで アプリを更新し Core Locationのアクセス許可を サービスセッションで 処理できるようになるからです これで CLLocationUpdateが必要だった 去年のサンプルアプリが 全面的にアップデートされます この動作はデフォルトで有効なので 暗黙的なサービスセッションで アクセス許可目標が 定義可能になります 最新のCore Location APIはそれぞれが 反復対象の非同期シーケンスを提供し 目標となる.whenInUseの状態を 反復中に表現します これにより 先ほど紹介した アクセス許可の処理時に 便利だったコードスニペットが さらに便利になります 気に入っていただければ幸いですが 特に大規模なプロジェクトでは アプリを使用するユーザーに アクセス許可を求めるのが 望ましくない場合もあるでしょう しかし そのような時でも コードによる更新の反復を 停止させるわけにはいきません もしAPIでアプリに暗黙的に アクセス許可のリクエストを 行わせたくなければ 暗黙的なセッションの影響を 完全に無効化しましょう アプリのInfo.plistで Explicit-Service-Sessionキーを
設定してください その後は明示的なセッションを 再び取得するだけです 暗黙的なオブジェクトではなく 実際のCLServiceSession オブジェクトが必要となる 主な理由は3つあります まず 暗黙的なサービスセッションでは 完全精度が不要です ほとんどの場合 ユーザーが設定した 精度制限を遵守する必要が あるからです 本当に完全精度が必要な時だけ 明示的に切り替えます 同様に Core Locationはアプリからの 常時アクセス許可を想定していません 本当にそのような アクセス許可が必要な時は 必要なタイミングで 明示的にCLServiceSessionを実行し 目標を.alwaysに設定します 今年から Always許可は 維持されている間のみ有効であり アプリがフォアグラウンドの時のみ 開始できることに注意してください 後でセッションのライフサイクルの 話題のときに詳しく説明します 後に info.plistキーを使うことで 特定のタイミングで 明示的なサービスセッションを作成できます 許可の目標を.noneに設定すれば Core Locationがアプリの代理でユーザーに アクセス許可を要求するタイミングを より詳細に制御できます 内部で何度もCore Locationを 使用するアプリや ユーザーに積極的に 位置情報へのアクセスを 選択させるアプリには これが最も簡単でしょう これでCLServiceSessionを実行したり liveUpdatesやイベントの反復から取得した 暗黙的なセッションを 活用できるようになりました これらのセッションはユーザー向けの 機能を念頭に置いています つまり アプリのライフサイクル全体で 発生するイベントとのやり取りがあります 説明しましょう
誰かがアプリを 実行しているとします Core Locationに依存する機能を 使うタイミングを見てみましょう 写真の地理情報のタグ付けなど 短時間の機能もあれば 長く続く機能もあります 例えば MapKitビューを 閲覧している時間などです さらに移動時間を調べる作業も 同時並行で発生しているかもしれません これらの機能のセッション中に CLServiceSessionオブジェクトを作成して 維持するのは簡単です 機能に実装するコンポーネントに コード内で関連付けて 必要に応じて レイヤー化するだけです もちろん デフォルトでこれを行う 暗黙的なセッションも実行されます
しかし これらの機能は必ずしも 最初から最後までアプリ内で使われません エクササイズをトラッキングして 記録するような機能の場合 数秒も経てば アプリがバックグラウンド状態に移行し ユーザーは別のタスクを開始するでしょう 例えば 別のアプリで 音楽を聴きながら運動し ワークアウトが終わった時点で エクササイズトラッキングアプリに 戻ってきます
機能が概念的に継続し 暗黙的なセッションも継続する場合 作成された明示的な CLServiceSessionも同じく継続します 3つの理由を 説明します まず こうすることで アプリに アクセス許可の目標を 継続的に認識させることができます Core Locationはバックグラウンドでは ユーザーに対してアプリのアクセス許可の 調整を求めませんが 必要なものがわかっていれば 突然フォアグラウンド状態に戻っても 必要に応じて即座に アクセス許可を回復可能です
また Alwaysアクセス許可だけだと LiveUpdatesとCLMonitor.eventsは 使用中にのみ結果を取得しますが フォアグラウンド状態で開始された セッションまたはその他の発効中の セッションが継続的な許可を 主張していれば結果を取得できます
最後に 上の2つの理由では 不十分であったとしても 簡単な理由があります セッションのライフサイクルが 機能のスパンと直接結びつき 考慮すべき要素が減るのです 暗黙的なセッションでもそうなります とてもお勧めです セッションをバックグラウンドで 維持する手法は 後でアプリが更新中に 一時停止されたときにも便利です ワークアウトのトラッキングを すぐに再開することができます
さらに重要なのは アプリが一時停止中に 終了されても機能する点です どういう場合であれワークアウトは トラッキングしなくてはなりません それがアプリに対する要求だからです しかし アプリのプロセスと それに含まれていたすべての CLServiceSessionオブジェクトや 暗黙的なセッションは消えています Core Locationは アプリに 提供する情報がなければ アプリを継続的に実行させません アプリに許可されていない更新を 生成または提供することもありません 例えば .whenInUse許可しかないか LiveActivityまたは CLBackgroundActivitySessionが 無効な状態で バックグラウンド化されている場合などです とはいえ 他の個々のAPIオブジェクトは 引き続きトラッキングされます CLServiceSession liveUpdates その他のシーケンスなどです 一時停止や終了が発生しても これらに基づいて機能します つまり 新たな情報の配信準備が整い アプリのアクセス許可もあれば アプリはバックグラウンドで 再開または再起動されます
この図で セッションを表している 青いバーが実際の処理にかかわらず 継続しているのには このような理由があります
ただし Core Locationはこれらの 明示的または暗黙的なセッションを 継続的にトラッキングしません アプリの再起動後 数秒間だけです その起動の起点が Core Location APIであれ ユーザー操作であれ変わりません ごく短時間ではありますが アプリが 機能を再開しない場合もあるので セッションの状態を漏れなく 管理するために重要です このようなアプリを記述する際は タスクとなっている機能が 処理の起動ロジックにおいて トラッキングおよび認識されるように 設定する必要があるほか セッションオブジェクトの再取得や liveUpdatesまたはMonitor.eventsの反復の 即時再開もできるようにする必要があります こうしてオブジェクトは上書きされ Core Locationはアプリが 引き続き使用されていることを認識します 次はDiagnostic Propertiesです Diagnostic Propertiesはアクセス許可の目標が 達成されない理由とタイミングを示します CLServiceSessionは 維持するだけで発効しますが 各インスタンスは diagnostics AsyncSequenceも 公開しています これは このコードのように 「for try await」で反復処理して 状態を確認することができます
ブール値のプロパティもあります これがDiagnostic Propertyと 呼ばれるもので 予期せぬ出来事があった タイミングを示しています この場合は アプリのユーザーが 位置情報の共有を拒否したようです .authorizationDenied以外にも .insufficientlyInUseは Core Locationがアプリの代理で アクセス許可を要求できていないことを示します .alwaysAuthorizationDeniedは Alwaysアクセス許可を 設定したのに未達成であると 示しています .authorizationDeniedGloballyに 注目してみましょう 位置情報サービスがシステム全体で 無効化されるとtrueになります 誰かがデバイスをそう設定すると アプリもその設定に従うので .authorizationDeniedもtrueになります Diagnostic Propertiesでは別物として 扱われますが 関連性はあります もし アプリからのアクセスが 明示的に拒否されたタイミングを 知りたいだけであれば 所要時間の推定をUIから省いて 汎用的な項目だけを 確認するようにしてください
しかし ユーザーの中には グローバルに移動をするために 細かいガイドが必要な 人もいるかもしれません その場合はより詳しい プロパティを確認できます どうなるか見てみましょう 再びコードを確認します 診断処理ロジックを もう少しニュートラルにすれば 流用できるでしょう ユーザーがいつそのような決断をしたのか 確認したいだけならば 注意点が1つあります このコードが実行された時 誰かが既にアプリのアクセスを 許可または拒否済みの場合 Core Locationは情報を取得しないので 最初の診断は.authorization RequestInProgressが 既にfalseの状態で到着し コードがブレークすることがあります 悪くないかもしれませんが アプリのアクセス許可は動的に変化します ユーザーが設定を調整したり 一時的な許可を 利用したりするからです そのため セッションの診断は アプリがそのセッションに対応する機能を 提供している間はできるだけ長く リッスンしておいた方が良いでしょう UIが反応できるように @Published varを設定するのもお勧めです
実行中にアプリが 位置情報を必要とする タスク内でのセッションと 反復まで対応し 完了したらキャンセルするという 形にすれば セッションの ライフサイクルは適切になり アプリのユーザーが選択した アクセス許可の下でも UIが適切に反応するようになります Diagnostic Propertiesにより CLServiceSessionの柔軟性は高まり アクセス許可の目標の状態が 達成できない場合でも 対応可能な方法が示されます そしてご存知のとおり liveUpdatesとCLMonitor.eventsも 暗黙的なセッションを提供します Diagnostic Propertiesも 各CLLocationUpdateまたは CLMonitor.Eventで使用できます 先ほど見たオブジェクトです 特にCLLocationUpdateには 既にブール値の 診断プロパティが .stationaryという名前で含まれています デバイスが移動を停止して 更新が一時停止される前の 最後の更新でtrueになります
しかし それぞれが他の ブール値プロパティも含んでいます 暗黙的なサービスセッションとしての ロールに起因して 発生するかもしれない問題に 対応するためです さらに見ていきましょう CLLocationUpdateでは さらに2種類 将来の更新が 到着しない理由の説明ができます .accuracyLimitedは おおよその位置情報が 15分から20分ほどの間隔でしか 更新されないことを示し .locationUnavailableは Core Locationが現在位置を 取得できていないことを示します CLMonitor.Eventでも 条件を満たすイベントが 無かったことを 精度の制限などの 理由から説明できます 監視対象の条件の数が多すぎるなどの 原因もあります 各APIオブジェクトの Diagnostic Propertiesが これらを組み合わせて活用することで かつてのタイムアウト処理や 推測は不要なものとなりました 仮にCore Locationが更新や イベントを期待どおりに 提供できなかったとしても Diagnostic Propertyを 設定することで その理由を知ることができます 以上です CLServiceSessionを使うことで 必要なアクセス許可を Core Locationに伝えることができます 自然に反復するAPIの更新やイベントで 許可が自動的に扱われて いなければそうしてください そして 更新 イベント サービスセッションが目標を 達成できなさそうな場合は 常にCore Locationが Diagnostic Propertiesで理由を知らせます 必要であればCLLocationUpdateと CLMonitorについて詳しく紹介した 去年のセッションをご覧ください それでは ぜひ新機能を試して 結果を私たちにお知らせください
-
-
0:31 - CLLocationUpdate and CLMonitor
// Iterating liveUpdates to reflect current location Task { let updates = CLLocationUpdate.liveUpdates() for try await update in updates { if let loc = update.location { updateLocationUI(location: loc) } } } // Iterating monitor events to report condition state changes Task { let monitor = await CLMonitor(monitorName) await monitor.add(CLMonitor.CircularGeographicCondition(center: applePark, radius: 50), identifier: "ApplePark") for try await event in await monitor.events { updateConditionsUI(for: event.identifier, state: event.state) } }
-
0:52 - Handle updates with CLLocationManagerDelegate
// Adapting location authorization to Swift with a MainActor singleton @MainActor class LocationReflector: NSObject, CLLocationManagerDelegate, ObservableObject { static let shared = LocationReflector() private let manager = CLLocationManager() override init() { super.init() manager.delegate = self } func locationManagerDidChangeAuthorization(_ manager: CLLocationManager){ if (manager.authorizationStatus == .notDetermined) { manager.requestWhenInUseAuthorization() } } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations:[CLLocation]) { // Process locations[0] } // ... }
-
1:07 - CLServiceSession simplifies
// CLServiceSession in action Task { let session = CLServiceSession(authorization: .whenInUse) for try await update in CLLocationUpdate.liveUpdates { // Process update.location or update.authorizationDenied } }
-
7:15 - Implicit service sessions
// CLServiceSession in action Task { let session = CLServiceSession(authorization: .whenInUse) for try await update in CLLocationUpdate.liveUpdates { // Process update.location or update.authorizationDenied } }
-
7:34 - Implicit service sessions
Task { for try await update in CLLocationUpdate.liveUpdates { // Process update.location or update.authorizationDenied } }
-
13:37 - Diagnostics – Following the progress of location authorization
// Following the progress of location authorization with CLServiceSession let mySession = CLServiceSession(authorization:.whenInUse) for try await diagnostic in mySession.diagnostics { if (diagnostic.authorizationDenied) { // Ok, let’s let them pick a location instead? } }
-
15:00 - Diagnostics – Following the progress of location authorization
// Following the progress of location authorization with CLServiceSession let mySession = CLServiceSession(authorization:.whenInUse) for try await diagnostic in mySession.diagnostics { if (!diagnostic.authorizationRequestInProgress) { // They’ve decided (maybe already). We can move on! break } }
-
15:25 - Diagnostics – Following the progress of location authorization
// Following the progress of location authorization with CLServiceSession let mySession = CLServiceSession(authorization:.whenInUse) for try await diagnostic in mySession.diagnostics { if (!diagnostic.authorizationRequestInProgress) { reactToChanges(authorized:!diagnostic.authorizationDenied) } }
-
15:46 - Diagnostics – Following the progress of location authorization
// Following the progress of location authorization with CLServiceSession Task { let mySession = CLServiceSession(authorization:.whenInUse) for try await diagnostic in mySession.diagnostics { if (!diagnostic.authorizationRequestInProgress) { reactToChanges(authorized:!diagnostic.authorizationDenied) } } }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。