ShieldActionDelegate でシールドのボタン処理を書いていると、「ここからメインアプリを開いてアンロック画面を出したい」という場面が来る。でも、それは素直には書けない。iOS 開発を始めたばかりだと「なぜできないのか」が理解しにくい部分なので、背景から整理する。
なぜ Extension からアプリを直接開けないか
ShieldActionDelegate が返せる値は .close と .defer の2つだけだ。他のプラットフォームなら URL スキームでアプリを起動するコードが使える場面でも、シールドの文脈ではその経路が動かない。
Apple がこの経路を閉じているのは、シールド体験の一貫性を守るためだと思われる。シールドが「抜け穴」になってしまうとブロックの意味がなくなる。Extension から直接アプリジャンプを許可すると、コードの書き方次第で制限を自己解除できてしまうためだ。iOS の Extension 全般に言えることだが、Extension からメインアプリへの直接的な操作は基本的に制限されている。
App Group 経由のハンドオフパターン
現実的な実装は、ストレージを介した非同期のハンドオフだ。
App Group とは、メインアプリと Extension がデータを共有するための仕組みだ。通常 iOS のアプリとプロセスは互いのデータにアクセスできないが、同じ App Group に所属していれば共有の UserDefaults やファイルコンテナを読み書きできる。
Shield Action Extension 側で、App Group の共有コンテナに「アンロック要求が出ている」というフラグを書き込む。ユーザーがアプリを手動で開いたとき、メインアプリがそのフラグを読み取ってアンロックフローを開始する。
// Shield Action Extension 内override func handle( action: ShieldAction, for webDomain: WebDomainToken, completionHandler: @escaping (ShieldActionResponse) -> Void) { let defaults = UserDefaults(suiteName: "group.com.example.stoicdns") defaults?.set(true, forKey: "pendingUnlockRequest") defaults?.set(Date(), forKey: "unlockRequestedAt") completionHandler(.close)}// メインアプリの起動時 / フォアグラウンド復帰時func checkPendingUnlock() { let defaults = UserDefaults(suiteName: "group.com.example.stoicdns") guard defaults?.bool(forKey: "pendingUnlockRequest") == true else { return } defaults?.removeObject(forKey: "pendingUnlockRequest") showUnlockFlow()}シールドは .close で閉じる。ユーザーは一度ホーム画面に戻り、手動でアプリを開く必要がある。
タイムスタンプを一緒に保存する理由
要求の書き込みと同時に日時を保存しておくと、古い要求を無視するロジックが書きやすくなる。「30分以上前のリクエストは無効」という処理を入れると、アプリを長時間開かなかったときに意図しないアンロック画面が出る問題を防げる。
UX の設計として受け入れる
「シールドからワンタップでアンロック画面へ」という流れを期待すると、このパターンは物足りなく感じるかもしれない。でも、これが Apple のインターフェースが許容する範囲だ。
考え方を変えると、「シールドに気づいてアプリを開く」という摩擦自体がセルフコントロールの一部でもある。ワンタップで解除できるより、一度立ち止まるステップが入る方が、プロダクトの目的に合っているとも言える。
実装として割り切って進む方が早い。
hsb.horse