【Unity】iOSにおけるApp Tracking Transparency (ATT)対応をする方法

1. App Tracking Transparency (ATT)とは

App Tracking Transparency(ATT)は、iOS 14.5以降で導入されたAppleのプライバシー保護機能です。アプリがユーザーのアクティビティを追跡したり、デバイスの広告識別子(IDFA)にアクセスしたりする前に、ユーザーの明示的な許可を得ることを義務付けています。

ユーザーがアプリを初めて起動した際、ATTダイアログが表示され、以下の選択肢が提示されます: - ラッキングを許可:アプリがIDFAにアクセスし、広告目的でのトラッキングが可能になります - Appにトラッキングしないように要求:IDFAへのアクセスが制限され、トラッキングが禁止されます

2. なぜ必要か

コンプライアンスの観点

iOS 14.5以降、ATT対応は必須要件となっています。対応していない場合、以下のリスクがあります:

  • App Storeの審査でリジェクトされる可能性:広告SDKを使用している場合、ATT実装なしではアプリが承認されません
  • 法的リスク:プライバシー規制違反により、罰金や法的措置の対象となる可能性があります
  • ユーザーの信頼性低下:プライバシーを尊重しないアプリとして評価が下がる恐れがあります

ビジネスの観点

ATTへの適切な対応により、以下のメリットが得られます:

  • 広告収益の最適化:許可を得たユーザーに対してターゲティング広告を配信でき、広告単価(eCPM)の向上が期待できます
  • ユーザー体験の向上:透明性のある許可プロセスにより、ユーザーとの信頼関係を構築できます
  • 分析精度の向上:IDFAを使用した正確なユーザー行動分析が可能になります

3. 実際のコード

UnityでATTを実装する際は、Unity Advertisement iOS Support パッケージを使用します。以下、UniTask版とIEnumerator版の両方の実装例を示します。

事前準備

Package Managerから以下のパッケージをインストールしてください: - iOS 14 Advertising Support (com.unity.ads.ios-support)

UniTask版を使用する場合は、UniTaskパッケージも必要です。

3.1 UniTask版

using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
#if UNITY_IOS
using Unity.Advertisement.IosSupport;
using UnityEngine.iOS;
#endif

namespace ATT
{
    /// <summary>
    /// App Tracking Transparencyの許可リクエストを管理するクラス
    /// </summary>
    public sealed class PrivacySystem : MonoBehaviour
    {
        /// <summary>
        /// セットアップ処理
        /// </summary>
        private async void Start()
        {
            // ATTリクエスト
            await RequestTrackingAuthorizationAsync();
        }

        /// <summary>
        /// ATTリクエスト(非同期処理)
        /// </summary>
        private async UniTask RequestTrackingAuthorizationAsync()
        {
            try
            {
                // エディター環境ではスキップ
#if UNITY_EDITOR
                await UniTask.NextFrame();
                Debug.Log("ATT: Skipped in Editor");
                return;
#endif

#if UNITY_IOS
                // 現在の認証ステータスを取得
                var currentAuthorizationStatus = ATTrackingStatusBinding.GetAuthorizationTrackingStatus();
                Debug.Log($"ATT Current Status: {currentAuthorizationStatus}");
                
                // 未確認の場合はリクエストダイアログを表示
                if (currentAuthorizationStatus == ATTrackingStatusBinding.AuthorizationTrackingStatus.NOT_DETERMINED)
                {
                    Debug.Log("ATT: Requesting authorization...");
                    ATTrackingStatusBinding.RequestAuthorizationTracking();
                }
                
                // ステータスが変わるまで待機(最大10秒)
                var timeout = 10f;
                var elapsed = 0f;
                
                while (currentAuthorizationStatus == ATTrackingStatusBinding.AuthorizationTrackingStatus.NOT_DETERMINED 
                       && elapsed < timeout)
                {
                    await UniTask.Delay(TimeSpan.FromSeconds(0.5f));
                    elapsed += 0.5f;
                    currentAuthorizationStatus = ATTrackingStatusBinding.GetAuthorizationTrackingStatus();
                }
                
                // 最終ステータスをログ出力
                Debug.Log($"ATT Final Status: {currentAuthorizationStatus}");
                
                // ステータスに応じた処理
                HandleTrackingStatus(currentAuthorizationStatus);
#endif
            }
            catch (Exception e)
            {
                Debug.LogError("ATT Status Error");
                Debug.LogException(e);
            }
        }
        
#if UNITY_IOS
        /// <summary>
        /// トラッキングステータスに応じた処理
        /// </summary>
        private void HandleTrackingStatus(ATTrackingStatusBinding.AuthorizationTrackingStatus status)
        {
            switch (status)
            {
                case ATTrackingStatusBinding.AuthorizationTrackingStatus.AUTHORIZED:
                    Debug.Log("ATT: トラッキングが許可されました");
                    // 広告SDKの初期化など
                    break;
                    
                case ATTrackingStatusBinding.AuthorizationTrackingStatus.DENIED:
                    Debug.Log("ATT: トラッキングが拒否されました");
                    // 制限された機能での動作
                    break;
                    
                case ATTrackingStatusBinding.AuthorizationTrackingStatus.RESTRICTED:
                    Debug.Log("ATT: トラッキングが制限されています");
                    // 保護者による制限など
                    break;
                    
                case ATTrackingStatusBinding.AuthorizationTrackingStatus.NOT_DETERMINED:
                    Debug.Log("ATT: ステータスが未決定です(タイムアウト)");
                    break;
            }
        }
#endif
    }
}

3.2 IEnumerator版

using System;
using System.Collections;
using UnityEngine;
#if UNITY_IOS
using Unity.Advertisement.IosSupport;
using UnityEngine.iOS;
#endif

namespace ATT
{
    /// <summary>
    /// App Tracking Transparencyの許可リクエストを管理するクラス(コルーチン版)
    /// </summary>
    public sealed class PrivacySystemCoroutine : MonoBehaviour
    {
        /// <summary>
        /// セットアップ処理
        /// </summary>
        private void Start()
        {
            // ATTリクエストコルーチンを開始
            StartCoroutine(RequestTrackingAuthorizationCoroutine());
        }

        /// <summary>
        /// ATTリクエスト(コルーチン処理)
        /// </summary>
        private IEnumerator RequestTrackingAuthorizationCoroutine()
        {
            // エディター環境ではスキップ
#if UNITY_EDITOR
            yield return null;
            Debug.Log("ATT: Skipped in Editor");
            yield break;
#endif

#if UNITY_IOS
            try
            {
                // 現在の認証ステータスを取得
                var currentAuthorizationStatus = ATTrackingStatusBinding.GetAuthorizationTrackingStatus();
                Debug.Log($"ATT Current Status: {currentAuthorizationStatus}");
                
                // 未確認の場合はリクエストダイアログを表示
                if (currentAuthorizationStatus == ATTrackingStatusBinding.AuthorizationTrackingStatus.NOT_DETERMINED)
                {
                    Debug.Log("ATT: Requesting authorization...");
                    ATTrackingStatusBinding.RequestAuthorizationTracking();
                }
                
                // ステータスが変わるまで待機(最大10秒)
                float timeout = 10f;
                float elapsed = 0f;
                
                while (currentAuthorizationStatus == ATTrackingStatusBinding.AuthorizationTrackingStatus.NOT_DETERMINED 
                       && elapsed < timeout)
                {
                    yield return new WaitForSeconds(0.5f);
                    elapsed += 0.5f;
                    currentAuthorizationStatus = ATTrackingStatusBinding.GetAuthorizationTrackingStatus();
                }
                
                // 最終ステータスをログ出力
                Debug.Log($"ATT Final Status: {currentAuthorizationStatus}");
                
                // ステータスに応じた処理
                HandleTrackingStatus(currentAuthorizationStatus);
            }
            catch (Exception e)
            {
                Debug.LogError("ATT Status Error");
                Debug.LogException(e);
            }
#else
            yield break;
#endif
        }
        
#if UNITY_IOS
        /// <summary>
        /// トラッキングステータスに応じた処理
        /// </summary>
        private void HandleTrackingStatus(ATTrackingStatusBinding.AuthorizationTrackingStatus status)
        {
            switch (status)
            {
                case ATTrackingStatusBinding.AuthorizationTrackingStatus.AUTHORIZED:
                    Debug.Log("ATT: トラッキングが許可されました");
                    // 広告SDKの初期化など
                    OnTrackingAuthorized();
                    break;
                    
                case ATTrackingStatusBinding.AuthorizationTrackingStatus.DENIED:
                    Debug.Log("ATT: トラッキングが拒否されました");
                    // 制限された機能での動作
                    OnTrackingDenied();
                    break;
                    
                case ATTrackingStatusBinding.AuthorizationTrackingStatus.RESTRICTED:
                    Debug.Log("ATT: トラッキングが制限されています");
                    // 保護者による制限など
                    OnTrackingRestricted();
                    break;
                    
                case ATTrackingStatusBinding.AuthorizationTrackingStatus.NOT_DETERMINED:
                    Debug.Log("ATT: ステータスが未決定です(タイムアウト)");
                    OnTrackingUndetermined();
                    break;
            }
        }
        
        /// <summary>
        /// トラッキングが許可された場合の処理
        /// </summary>
        private void OnTrackingAuthorized()
        {
            // 広告SDKの初期化
            // 例: InitializeAdvertisementSDK();
            
            // IDFAを使用した分析の開始
            // 例: StartAnalyticsWithIDFA();
        }
        
        /// <summary>
        /// トラッキングが拒否された場合の処理
        /// </summary>
        private void OnTrackingDenied()
        {
            // コンテクスチュアル広告への切り替え
            // 例: UseContextualAdvertising();
            
            // 制限付き分析の開始
            // 例: StartLimitedAnalytics();
        }
        
        /// <summary>
        /// トラッキングが制限されている場合の処理
        /// </summary>
        private void OnTrackingRestricted()
        {
            // 保護者による制限への対応
            // 例: HandleParentalRestrictions();
        }
        
        /// <summary>
        /// ステータスが未決定の場合の処理
        /// </summary>
        private void OnTrackingUndetermined()
        {
            // デフォルトの動作設定
            // 例: UseDefaultSettings();
        }
#endif
    }
}

実装時の注意点

Info.plistの設定

XcodeプロジェクトのInfo.plistに以下のキーを追加する必要があります:

<key>NSUserTrackingUsageDescription</key>
<string>このアプリは、より関連性の高い広告を表示するために、あなたのアクティビティを追跡する許可を求めています。</string>

この説明文はATTダイアログに表示されるため、ユーザーにとって分かりやすく、許可することのメリットが伝わる内容にすることが重要です。

タイミングの考慮

ATTリクエストを表示するタイミングは慎重に選ぶ必要があります:

  • アプリ起動直後は避ける:ユーザーがアプリの価値を理解する前に許可を求めると、拒否される可能性が高くなります
  • コンテキストを提供する:なぜトラッキングが必要なのか、事前に説明画面を表示することで承認率が向上します
  • 適切なタイミングチュートリアル完了後や、広告関連機能を使用する直前など、ユーザーが理解しやすいタイミングで表示します

テスト方法

  1. 実機でのテスト:シミュレーターではATTの動作が正確に再現されないため、必ず実機でテストしてください
  2. 設定のリセット:設定 > プライバシー > トラッキング から、アプリごとの許可をリセットできます
  3. 複数のiOSバージョンiOS 14.5以降の複数のバージョンでテストすることを推奨します

これらの実装により、Appleガイドラインに準拠したATT対応が可能になり、ユーザーのプライバシーを尊重しながら、必要な広告機能を維持することができます。