Googleの User Messaging Platform (UMP) SDK でGDPRに対応したら、AppStoreの審査でリジェクトされた
私の作っているiOSアプリでは、Googleが提供する User Messaging Platform (UMP) SDK を使って、数ヶ月前にGDPRに対応していました。
UMP SDKでは、以下のように、GDPRメッセージの[Consent(同意)]ボタンを押下した後に、App Tracking Transparency (ATT)メッセージを表示する仕様になっています。
GDPRメッセージ | ATTメッセージ |
---|---|
しかし、先日、GDPRとは無関係のアプリの変更でAppStoreの審査を受けたところ、リジェクトされてしまいました。
リジェクト時のメッセージは以下の通りです
Guideline 5.1.1 - Legal - Privacy - Data Collection and Storage
We noticed your app includes App Tracking Transparency permissions requests, but it encourages or directs users to accept tracking. Specifically, your app directs the user to accept tracking in the following way(s):
- A message appears before the permission request, the user should always proceed to the permission request after the message.
Permission requests give users control of their personal information. It is important to respect their decision about how their data is used.
Next Steps
To resolve this issue, please revise the permission request process in your app to not display messages before the tracking request with inappropriate words on buttons.
If necessary, you may provide more information about why you are requesting permission to track before the request appears. If the user is trying to use a feature in your app that won't function without tracking, you may include a notification to inform the user and provide a link to the Settings app.
日本語訳
ガイドライン5.1.1 - 法的 - プライバシー - データの収集と保存
あなたのアプリには、App Tracking Transparencyの許可要求が含まれていますが、トラッキングを受け入れるようユーザーに促したり、指示したりしています。具体的には、あなたのアプリは以下の方法でトラッキングを受け入れるようユーザーに指示しています:
- 許可リクエストの前にメッセージが表示され、ユーザーは常にメッセージの後に許可リクエストに進む必要があります。
許可リクエストは、ユーザーに個人情報のコントロールを与えます。自分のデータがどのように使用されるかについて、ユーザーの決定を尊重することが重要です。
次のステップ
この問題を解決するには、トラッキングリクエストの前に不適切な単語を含むメッセージをボタンに表示しないよう、アプリの許可リクエストプロセスを修正してください。
ようするに、ATTメッセージを表示する前に、同意を促すようなメッセージ(ここではGDPRメッセージ)を表示してはいけないようです。
対応方法
ATTメッセージのAllow(許可)ボタンを押下した後に、GDPRメッセージを表示するように変更したら、AppStoreの審査に通りました。
SwiftUI と UMP SDKでの実装例は以下の通りです。
import SwiftUI
import AppTrackingTransparency
import GoogleMobileAds
import UserMessagingPlatform
struct RootView: ConnectedView {
@State private var didShowATT = false
func body(props: Props) -> some View {
TabView(selection: tabSelectionBinding) {
//...
}
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in
// ATTが2回表示されるのを防ぐ
if !didShowATT {
didShowATT = true
showATT()
}
}
}
// ATT(App Tracking Transparency)メッセージを表示
func showATT() {
ATTrackingManager.requestTrackingAuthorization(completionHandler: { status in
switch status {
case .authorized:
// Tracking authorization dialog was shown
// and we are authorized
print("ATT:Authorized")
showUserMessagingPlatformMessage()
case .denied:
// Tracking authorization dialog was
// shown and permission is denied
print("ATT:Denied")
case .notDetermined:
// Tracking authorization dialog has not been shown
print("ATT:Not Determined")
case .restricted:
print("ATT:Restricted")
@unknown default:
print("ATT:Unknown")
}
})
}
// UMP SDKを使って、GDPAメッセージを表示
// https://developers.google.com/admob/ios/privacy?hl=ja
// iOS 14.5からのユーザトラッキング2: User Messaging Platform(UMP)とファンディングチョイス
// https://zenn.dev/hituziando/articles/3938fac7588853
func showUserMessagingPlatformMessage() {
// Create a UMPRequestParameters object.
let parameters = UMPRequestParameters()
// Set tag for under age of consent. Here false means users are not under age.
parameters.tagForUnderAgeOfConsent = false
//region テスト用
// let debugSettings = UMPDebugSettings()
// debugSettings.testDeviceIdentifiers = ["XXX"]
// debugSettings.geography = UMPDebugGeography.EEA
// parameters.debugSettings = debugSettings
//endregion
// Request an update to the consent information.
UMPConsentInformation.sharedInstance.requestConsentInfoUpdate(
with: parameters,
completionHandler: { error in
if error != nil {
// Handle the error.
print("requestConsentInfoUpdate error: \(String(describing: error?.localizedDescription))")
} else {
// The consent information state was updated.
// You are now ready to see if a form is available.
let formStatus = UMPConsentInformation.sharedInstance.formStatus
if formStatus == UMPFormStatus.available {
loadForm()
}
}
})
}
func loadForm() {
// Loads a consent form. Must be called on the main thread.
UMPConsentForm.load(
completionHandler: { form, loadError in
if loadError != nil {
// Handle the error
print("UMPConsentForm.load error: \(String(describing: loadError?.localizedDescription))")
} else {
guard let controller = UIApplication.shared.topMostViewController() else { return }
// Present the form. You can also hold on to the reference to present
// later.
if UMPConsentInformation.sharedInstance.consentStatus == UMPConsentStatus.required {
form?.present(
from: controller,
completionHandler: { dismissError in
if UMPConsentInformation.sharedInstance.consentStatus == UMPConsentStatus.obtained {
// App can start requesting ads.
}
// Handle dismissal by reloading form.
loadForm();
})
} else {
// Keep the form available for changes to user consent.
}
}
})
}
}