ハイライト
Apple は iOS 26 で Declared Age Range API を導入し、正確な誕生日ではなくユーザーの年齢層をプライバシーを保護する形で取得できるようにしました。基本的な仕組みは、App 側が関心のある年齢の境界(たとえば 13 歳と 16 歳)を提示すると、システムが正確な生年月日ではなく区間(たとえば「13〜15 歳」)を返す、というものです。これにより、コンテンツのレーティングのためにユーザーの年齢を知る必要があるが、正確な誕生日を保管するプライバシーリスクは負いたくないという、開発者が長年抱えていたジレンマが解消されます。
主要内容
コンテンツレーティングを扱う開発者は、長らく次のような板挟みに直面してきました。「このユーザーがソーシャル共有機能を使えるかどうか」を判定するには誕生日を聞くのが最も正確ですが、いったん正確な誕生日をデータベースに保存してしまうと、COPPA や GDPR-K といったコンプライアンス監査の連鎖に対応しなければなりません。結果として、多くの App は「13 歳以上であることを確認します」という単純なダイアログを出すだけで済ませ、ユーザーはそれを何の気なしにタップして通り過ぎる、という運用に落ち着いてしまっていました。これでは未成年者を実質的に保護することにも、コンプライアンス対応にもなりません。
iOS 26 はその間を埋める新しいレイヤーを提供します。Declared Age Range フレームワークでは、App は関心のある年齢の境界(最大 3 つ、各区間は 2 年以上)を提示し、システムが区間値を返します。たとえば 14 歳の Olivia に対して App が 13 と 16 を指定すれば、システムは「13〜15」と回答します。9 歳の Emily であれば「12 以下」、42 歳の Ann であれば「16 以上」となります。誕生日は常にシステム側に留まり、App は意思決定に必要な最小限の情報だけを受け取ります。年齢の宣言には保護者のフローも組み込まれています。子供向けアカウントの年齢は保護者が確認し、3 種類の共有モード(Always Share / Ask First / Never Share)はすべて保護者がスクリーンタイムやファミリー設定から管理します。レスポンスには 1 年間のキャッシュがあり、デバイス間で同期され、再プロンプトは 1 年経過後にのみ行われます。これにより App から繰り返しダイアログで煩わされることを避けつつ、何度も問い合わせて誕生日を逆算されることも防いでいます。
詳細
導入は 2 ステップです。まず Xcode の Signing & Capabilities で Declared Age Range capability を追加し、次に SwiftUI の environment 値経由でリクエストを発行します。以下は公式のサンプルです(08:03)。
// Request an age range
import SwiftUI
import DeclaredAgeRange
struct LandmarkDetail: View {
// ...
@State var photoSharingEnabled = false
@Environment(\.requestAgeRange) var requestAgeRange
var body: some View {
ScrollView {
// ...
Button("Share Photos") {}
.disabled(!photoSharingEnabled)
}
.task {
await requestAgeRangeHelper()
}
}
func requestAgeRangeHelper() async {
do {
// TODO: Check user region
let ageRangeResponse = try await requestAgeRange(ageGates: 16)
switch ageRangeResponse {
case let .sharing(range):
// Age range shared
if let lowerBound = range.lowerBound, lowerBound >= 16 {
photoSharingEnabled = true
}
// guardianDeclared, selfDeclared
print(range.ageRangeDeclaration)
case .declinedSharing:
// Declined to share
print("Declined to share")
}
} catch AgeRangeService.Error.invalidRequest {
print("Handle invalid request error")
} catch AgeRangeService.Error.notAvailable {
print("Handle not available error")
} catch {
print("Unhandled error: \(error)")
}
}
}
ポイントは次のとおりです。
import DeclaredAgeRangeで新しいフレームワークを取り込みます。capability の一覧でチェックを入れていないとビルドが通らない点に注意してください。@Environment(\.requestAgeRange)は非同期クロージャを返します。リクエストを environment にぶら下げているのは、システムがどのウィンドウでプロンプトを表示すべきか把握するためで、iPad のマルチウィンドウや Mac のマルチウィンドウもこの仕組みに依存しています。requestAgeRange(ageGates: 16)は年齢境界を 1 つだけ渡しており、これは App が「16 歳の境界」のみを気にしていることを意味します。より細かなレーティングが必要な場合は、複数の境界(最大 3 つ)を指定できます。case let .sharing(range)で取り出したrange.lowerBoundとrange.upperBoundは、いずれもnilになり得ます。上限がnilの場合は「X 歳以上」、下限がnilの場合は「X 歳以下」を意味します。そのため、コード上ではまずif let lowerBoundで取り出してから比較を行っています。range.ageRangeDeclarationはguardianDeclaredまたはselfDeclaredを返します。子供は常にguardianDeclared、iCloud ファミリーに所属する青少年もguardianDeclaredです。一方、ファミリーに所属しない青少年や成人はselfDeclaredとなるため、より厳格な保護者の確認が必要かどうかの判断材料に使えます。invalidRequestは開発者側のミスです(10:33)。よくある原因は、ある区間が 2 年に満たないことです。notAvailableは端末側の構成の問題で、たとえばユーザーが Apple アカウントにサインインしていない場合などが該当します。この 2 つのエラーは別々に処理する必要があり、一律にまとめて扱うべきではありません。
年齢区間の上限がその地域の成年年齢を下回る場合、レスポンスには activeParentalControls が含まれ、App はそれに応じて体験をさらに絞り込むことができます(11:49)。
// Request an age range
func requestAgeRangeHelper() async {
do {
// TODO: Check user region
let ageRangeResponse = try await requestAgeRange(ageGates: 16)
switch ageRangeResponse {
case let .sharing(range):
if range.activeParentalControls.contains(.communicationLimits) {
print("Communication Limits enabled")
}
// ...
case .declinedSharing:
// Declined to share
print("Declined to share")
}
} catch {
// ...
}
}
ポイントは次のとおりです。
activeParentalControlsは集合型で、現時点では保護者がスクリーンタイムで有効化した.communicationLimitsなどが含まれます。保護者が見知らぬ相手とのコミュニケーションを禁止している場合、App 側で友達招待や見知らぬ相手からの DM の入口を自動で閉じる必要があります。- 関連する PermissionKit を使うと、保護者がサードパーティ App 内の個別の連絡先リクエストを承認・拒否できるようになります(12:08)。Declared Age Range とは補完関係にあり、年齢層がデフォルトのオン/オフを決定し、PermissionKit が個別の承認を担当します。
- キャッシュ戦略について(11:21)。レスポンスは 1 年間有効で、iPhone と Mac 間で同期されます。App が頻繁に呼び出してもプロンプトが繰り返し表示されることはありません。ユーザーは Settings → Age Range for Apps から特定 App のキャッシュを手動でリセットでき、これにより誕生日当日に新しい区間を即座に取得できる(1 年待つ必要はない)ようになっています。
重要ポイント
-
何をするか: App 起動時に一括取得するのではなく、機能の入口で必要に応じて年齢層をリクエストする。
- なぜやる価値があるか: API 側に 1 年間のキャッシュがあるためリクエストコストは極めて低く、必要な場面で問うことで「なぜ今これを聞かれているのか」がユーザーにとって明確になり、許可率の向上にもつながります。
- 始め方: App 内で年齢の影響を受ける機能(ソーシャル共有、チャット、広告、In-App Purchase)を整理し、対応するビューに
.task { await requestAgeRangeHelper() }を仕込みます。各エントリーポイントでは、その機能に必要な最小限の年齢境界だけを問い合わせます。
-
何をするか: 「共有を拒否」を全面ブロックではなく、機能を絞ったフォールバック体験として実装する。
- なぜやる価値があるか: Never Share を選ぶユーザーにはプライバシーを重視する成人が多く含まれます。一律に「未成年扱い」してしまうと、通常のユーザーを失ってしまいます。
- 始め方:
.declinedSharingの分岐では、閲覧や検索といった基本機能を残し、年齢確認が必要な入口だけを隠します。UI のコピーには「年齢共有を有効にすると共有機能が解放される」旨を明記しましょう。
-
何をするか:
ageRangeDeclarationでguardianDeclaredとselfDeclaredを区別し、二次確認の強度を切り替える。- なぜやる価値があるか: 子供向けアカウントの区間は必ず保護者由来であり、信頼度が高くなります。一方、ファミリー外の青少年は自己申告であるため、In-App Purchase やライブ配信開設のようなハイリスク機能では追加の注意喚起をする価値があります。
- 始め方:
.sharing(range)分岐内でrange.ageRangeDeclarationを読み取り、selfDeclaredでかつ境界付近のユーザーには、保護者へのメール確認や開放までの遅延ポリシーといった追加ステップを設けます。
-
何をするか: レスポンスで
activeParentalControls.communicationLimitsがヒットしたら、見知らぬ相手とのやり取りの入口を自動で隠す。- なぜやる価値があるか: 保護者が Communication Limits を有効化しているにもかかわらず、App が見知らぬ相手への入口を露出し続けるのは、それ自体がコンプライアンスリスクです。
- 始め方: ビューモデルに
canMessageStrangersのようなフラグを持たせ、PermissionKit の承認フローと組み合わせて、「拒否済み」と「未許可」の 2 つの状態を分けて表示するようにします。
関連セッション
- Enhance child safety with PermissionKit — PermissionKit を使い、保護者がサードパーティ App 内の連絡先を 1 件ずつ承認できるようにする。
- What’s new in StoreKit and In-App Purchase — App Store の新しいレーティング(4+/9+/13+/16+/18+)下での In-App Purchase コンプライアンスのポイント。
- Filter and tunnel network traffic with NetworkExtension — システムレベルのネットワークフィルタ機能。年齢層ポリシーと組み合わせて家庭内ネットワーク管理に活用できる。
- Get ahead with quantum-secure cryptography — プライバシー保護のもう一つの側面。すでに収集済みのユーザーデータを、より強力な暗号で守る方法。
コメント
GitHub Issues · utterances