1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Unity】iOS でアプリのライフサイクルイベントやビューのイベントを受け取れるようにする

Last updated at Posted at 2025-12-23

この記事は Applibot Advent Calendar 2025 24日目の記事です。

はじめに

Unity で iOS 向けのネイティブプラグインを開発する中で「ネイティブのビュー関連のイベントを受け取る方法」について調査していると、iOS ビルドで生成される Xcode プロジェクト内の Classes/PluginBase に含まれるリスナー機能を利用すれば、簡単に実現できることが分かりました。

具体的には以下 3 つの機能が提供されており、本記事ではこれらの概要とプラグインの実装例について解説します。

機能 概要
UnityViewControllerListener UIViewController に関連するライフサイクルイベント
LifeCycleListener アプリのライフサイクルイベント
AppDelegateListener UIApplicationDelegate に関連するシステムイベント

先に注意点として書いておくと、今回解説する機能は公式ドキュメントには載っていない内容となります。1

ひょっとしたら internal な機能である可能性も無きにしも非ずなので、その点だけ注意して読み進めていただけると幸いです。2 :bow:

検証環境

  • Unity 2022.3.62f2, 6000.0.64f1, 6000.3.1f1
  • Xcode 26.2

各種リスナーについて

UnityViewControllerListener

UnityViewControllerListener は、UIViewController に関連するライフサイクルイベントを受け取るためのリスナーです。3

こちらを用いれば「ネイティブ側のレイアウトが変更された際のイベント」や、「他の UIViewController に切り替えた際のイベント」などを検知できるようになります。

プロトコルの定義と受け取れるイベント

こちらは Classes/PluginBase/UnityViewControllerListener.h に定義されており、一部引用すると以下のようなプロトコル4が定義されてます。

UnityViewControllerListener.h
#pragma once

#import <Foundation/NSNotification.h>

// view changes on the main view controller

@protocol UnityViewControllerListener<NSObject>
@optional
- (void)viewWillLayoutSubviews:(NSNotification*)notification;
- (void)viewDidLayoutSubviews:(NSNotification*)notification;
- (void)viewWillDisappear:(NSNotification*)notification;
- (void)viewDidDisappear:(NSNotification*)notification;
- (void)viewWillAppear:(NSNotification*)notification;
- (void)viewDidAppear:(NSNotification*)notification;

- (void)interfaceWillChangeOrientation:(NSNotification*)notification;
- (void)interfaceDidChangeOrientation:(NSNotification*)notification;
@end

// 中略

void UnityRegisterViewControllerListener(id<UnityViewControllerListener> obj);
void UnityUnregisterViewControllerListener(id<UnityViewControllerListener> obj);

詳細は後述しますが、基本的にはこちらのプロトコルを実装したクラスをプラグイン側で用意し、そのクラスのインスタンスを UnityRegisterViewControllerListener に渡して登録する流れとなります。

また、イベント自体もネイティブ側の UIViewController が持つものとほぼ同一であり、具体的には以下のようなイベントを受け取ることができます。5

メソッド 対応するイベント
viewWillLayoutSubviews UIViewController.viewWillLayoutSubviews()
viewDidLayoutSubviews UIViewController.viewDidLayoutSubviews()
viewWillAppear UIViewController.viewWillAppear(_:)
viewDidAppear UIViewController.viewDidAppear(_:)
viewWillDisappear UIViewController.viewWillDisappear(_:)
viewDidDisappear UIViewController.viewDidDisappear(_:)

LifeCycleListener

LifeCycleListener は、アプリのライフサイクルイベントを受け取るためのリスナーです。

具体的に言うと「バックグラウンド <-> フォアグラウンド移行時のイベント」や「アプリ終了時に発火されるイベント」などの検知が可能です。

一応 Unity 標準でもバックグラウンド <-> フォアグラウンド移行の大まかなタイミングであれば OnApplicationPause などを用いることで検知することが可能ですが、さらに細かいタイミングで制御が必要な場合には覚えておくと活用できるかもしれません。

プロトコルの定義と受け取れるイベント

こちらは Classes/PluginBase/LifeCycleListener.h に定義されており、一部引用すると以下のようなプロトコルが定義されてます。

LifeCycleListener.h
#pragma once

// important app life-cycle events

@protocol LifeCycleListener<NSObject>
@optional
- (void)didFinishLaunching:(NSNotification*)notification;
- (void)didBecomeActive:(NSNotification*)notification;
- (void)willResignActive:(NSNotification*)notification;
- (void)didEnterBackground:(NSNotification*)notification;
- (void)willEnterForeground:(NSNotification*)notification;
- (void)willTerminate:(NSNotification*)notification;
- (void)unityDidUnload:(NSNotification*)notification;
- (void)unityDidQuit:(NSNotification*)notification;
@end

void UnityRegisterLifeCycleListener(id<LifeCycleListener> obj);
void UnityUnregisterLifeCycleListener(id<LifeCycleListener> obj);

イベントの方は LifeCycleListener.mm のコードを読むと、OS 標準で定義されている以下の通知を登録していることが確認できました。

メソッド 対応するイベント・通知
didFinishLaunching UIApplicationDidFinishLaunchingNotification
didBecomeActive UIApplicationDidBecomeActiveNotification
willResignActive UIApplicationWillResignActiveNotification
didEnterBackground UIApplicationDidEnterBackgroundNotification
willEnterForeground UIApplicationWillEnterForegroundNotification
willTerminate UIApplicationWillTerminateNotification

また、これ以外にも Unity 独自で定義したイベントも持っており、内部的にアンロードが走るタイミングでイベントが発火されているのが確認できました。

メソッド 説明
unityDidUnload Unity がアンロードされた直後に呼ばれる
unityDidQuit Unity が終了した直後に呼ばれる

AppDelegateListener

AppDelegateListener は、UIApplicationDelegate に関連するシステムイベントを受け取るためのリスナーです。

こちらのプロトコルは前章で解説した LifeCycleListener を継承しておりLifeCycleListenerのイベントに加えて、以下の UIApplicationDelegate のイベントも受け取ることができます。

プロトコルの定義と受け取れるイベント

こちらは Classes/PluginBase/AppDelegateListener.h に定義されており、一部引用すると以下のようなプロトコルが定義されてます。

AppDelegateListener.h
#pragma once

#include "LifeCycleListener.h"


@protocol AppDelegateListener<LifeCycleListener>
@optional
// these do not have apple defined notifications, so we use our own notifications

// notification will be posted from
// - (BOOL)application:(UIApplication*)application openURL:(NSURL*)url sourceApplication:(NSString*)sourceApplication annotation:(id)annotation
// notification user data is the NSDictionary containing all the params
- (void)onOpenURL:(NSNotification*)notification;

// notification will be posted from
// - (BOOL)application:(UIApplication*)application willFinishLaunchingWithOptions:(NSDictionary*)launchOptions
// notification user data is the NSDictionary containing launchOptions
- (void)applicationWillFinishLaunchingWithOptions:(NSNotification*)notification;
// notification will be posted from
// - (void)application:(UIApplication*)application handleEventsForBackgroundURLSession:(nonnull NSString *)identifier completionHandler:(nonnull void (^)())completionHandler
// notification user data is NSDictionary with one item where key is session identifier and value is completion handler
- (void)onHandleEventsForBackgroundURLSession:(NSNotification*)notification;

// these are just hooks to existing notifications
- (void)applicationDidReceiveMemoryWarning:(NSNotification*)notification;
- (void)applicationSignificantTimeChange:(NSNotification*)notification;
- (void)applicationWillChangeStatusBarFrame:(NSNotification*)notification;
- (void)applicationWillChangeStatusBarOrientation:(NSNotification*)notification;
@end

void UnityRegisterAppDelegateListener(id<AppDelegateListener> obj);
void UnityUnregisterAppDelegateListener(id<AppDelegateListener> obj);

受け取ることができるイベントは OS 標準で定義されている通知に加え、幾つかは Unity が内部的に実装している UIApplicationDelegate の実装クラス (UnityAppController.mm 辺り) より呼び出されます。

メソッド 対応するイベント・通知
onOpenURL UIApplicationDelegate.application:openURL:options:
applicationWillFinishLaunchingWithOptions UIApplicationDelegate.application:willFinishLaunchingWithOptions:
onHandleEventsForBackgroundURLSession UIApplicationDelegate.application:handleEventsForBackgroundURLSession:completionHandler:
applicationDidReceiveMemoryWarning UIApplicationDidReceiveMemoryWarningNotification
applicationSignificantTimeChange UIApplicationSignificantTimeChangeNotification
applicationWillChangeStatusBarFrame UIApplicationWillChangeStatusBarFrameNotification
applicationWillChangeStatusBarOrientation UIApplicationWillChangeStatusBarOrientationNotification

Unity で活用する際の実装例

それでは、実際に Unity でこれらのリスナーを活用する方法について簡単に解説していきます。
本記事では UnityViewControllerListener を中心に解説しますが、他のリスナーも同様のパターンで実装されています。

また、今回解説したリスナーの登録周りを汎用的にしたパッケージの方も公開しており、こちらの方を実装サンプルとして参考にしていただけると幸いです。
(執筆時点では v1.0.3 をベースに解説)

ネイティブプラグインの実装

まずはイベントを受け取るためのクラスとして、UnityViewControllerListener.h に定義されている UnityViewControllerListener プロトコルを実装したクラスを用意します。6

UnityViewControllerListenerBridge.mm
#include "PluginBase/UnityViewControllerListener.h" 
#include <stdint.h>

// view changes on the main view controller
typedef void (*ViewWillLayoutSubviewsCallback)(void* context);
typedef void (*ViewDidLayoutSubviewsCallback)(void* context);
// (中略)

@interface UnityViewControllerListenerBridge : NSObject<UnityViewControllerListener>
@property (nonatomic, assign) ViewWillLayoutSubviewsCallback viewWillLayoutSubviewsCallback;
@property (nonatomic, assign) ViewDidLayoutSubviewsCallback viewDidLayoutSubviewsCallback;
// (中略)
@end

@implementation UnityViewControllerListenerBridge

- (void)viewWillLayoutSubviews:(NSNotification*)notification
{
    if (self.viewWillLayoutSubviewsCallback) {
        self.viewWillLayoutSubviewsCallback((__bridge void*)self);
    }
}

- (void)viewDidLayoutSubviews:(NSNotification*)notification
{
    if (self.viewDidLayoutSubviewsCallback) {
        self.viewDidLayoutSubviewsCallback((__bridge void*)self);
    }
}

// (中略)

あとはこちらを P/Invoke から呼び出すために、上述のクラスを生成・破棄するメソッドと、 UnityViewControllerListener.h にある UnityRegisterViewControllerListener, UnityUnregisterViewControllerListener へ登録するためのメソッドも用意します。

UnityViewControllerListenerBridge.mm
#ifdef __cplusplus
extern "C" {
#endif

// イベントを受け取るためのクラスの生成
void* iOSUtility_NativeEventListener_CreateUnityViewControllerListenerBridge(
    ViewWillLayoutSubviewsCallback viewWillLayoutSubviewsCallback,
    ViewDidLayoutSubviewsCallback viewDidLayoutSubviewsCallback,
    // (中略)
{
    // 生成したクラスに対し、引数から渡されたコールバックを登録していく
    UnityViewControllerListenerBridge* bridge = [[UnityViewControllerListenerBridge alloc] init];
    bridge.viewWillLayoutSubviewsCallback = viewWillLayoutSubviewsCallback;
    bridge.viewDidLayoutSubviewsCallback = viewDidLayoutSubviewsCallback;
    // (中略)
    
    return (__bridge_retained void*)bridge;
}

// 生成したクラスの破棄
void iOSUtility_NativeEventListener_ReleaseUnityViewControllerListenerBridge(void* ptr)
{
    UnityViewControllerListenerBridge* bridge = (__bridge_transfer UnityViewControllerListenerBridge*)ptr;
    bridge.viewWillLayoutSubviewsCallback = nil;
    bridge.viewDidLayoutSubviewsCallback = nil;
    // (中略)
}

void iOSUtility_NativeEventListener_UnityRegisterViewControllerListener(void* ptr)
{
    UnityViewControllerListenerBridge* bridge = (__bridge UnityViewControllerListenerBridge*)ptr;

    // NOTE: UnityViewControllerListener.h にある登録用関数
    UnityRegisterViewControllerListener(bridge);
}

void iOSUtility_NativeEventListener_UnityUnregisterViewControllerListener(void* ptr)
{
    UnityViewControllerListenerBridge* bridge = (__bridge UnityViewControllerListenerBridge*)ptr;
    UnityUnregisterViewControllerListener(bridge);
}

#ifdef __cplusplus
}
#endif

ちなみに iOS 向けのネイティブプラグインは Swift で実装することも可能ですが、本実装では ObjC++ を採用しています。

理由としては PluginBase 以下のヘッダが Swift から直接参照できず、Swift で対応する場合は UnityFramework.h(Umbrella Header)への変更が必要となるためです。

UnityFramework は Unity によって自動生成・更新される成果物であるため、保守性とアップデート耐性を考慮し、ObjC++ による実装を選択しています。

C# 側の実装

対応する C# 側の実装は次のようになります。

ポイントとしては、複数のインスタンスの登録に対応できるように、自身のインスタンスのポインタと interface を Dictionary で持っておき、呼び出しに応じて対象のインスタンスを指定できるようにしてあります。

UnityViewControllerListenerBridge.cs
internal sealed class UnityViewControllerListenerBridge : IDisposable
{
    private static readonly Dictionary<IntPtr, IUnityViewControllerListener> Listeners = new();
    private readonly IntPtr _ptr;
    private bool _disposed;

    public UnityViewControllerListenerBridge(IUnityViewControllerListener lifecycleListener)
    {
        var ptr = CreateUnityViewControllerListenerBridge(
            ViewWillLayoutSubviewsCallbackStatic,
            ViewDidLayoutSubviewsCallbackStatic,
            // (中略)
            );

        Assert.IsNotNull(lifecycleListener);
        Listeners[ptr] = lifecycleListener;
        UnityRegisterViewControllerListener(ptr);
        
        _ptr = ptr;
    }

    ~UnityViewControllerListenerBridge()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            Assert.IsTrue(_ptr != IntPtr.Zero);

            if (disposing)
            {
                // release managed resources
            }

            Listeners.Remove(_ptr);
            UnityUnregisterViewControllerListener(_ptr);
            ReleaseUnityViewControllerListenerBridge(_ptr);
            _disposed = true;
        }
    }


    [DllImport("__Internal", EntryPoint = "iOSUtility_NativeEventListener_CreateUnityViewControllerListenerBridge")]
    private static extern IntPtr CreateUnityViewControllerListenerBridge(
        ViewWillLayoutSubviewsCallback viewWillLayoutSubviewsCallback,
        ViewDidLayoutSubviewsCallback viewDidLayoutSubviewsCallback,
        // (中略)
        );

    [DllImport("__Internal", EntryPoint = "iOSUtility_NativeEventListener_ReleaseUnityViewControllerListenerBridge")]
    private static extern void ReleaseUnityViewControllerListenerBridge(IntPtr ptr);

    [DllImport("__Internal", EntryPoint = "iOSUtility_NativeEventListener_UnityRegisterViewControllerListener")]
    private static extern void UnityRegisterViewControllerListener(IntPtr ptr);

    [DllImport("__Internal", EntryPoint = "iOSUtility_NativeEventListener_UnityUnregisterViewControllerListener")]
    private static extern void UnityUnregisterViewControllerListener(IntPtr ptr);


    private delegate void ViewWillLayoutSubviewsCallback(IntPtr context);
    private delegate void ViewDidLayoutSubviewsCallback(IntPtr context);

    [MonoPInvokeCallback(typeof(ViewWillLayoutSubviewsCallback))]
    private static void ViewWillLayoutSubviewsCallbackStatic(IntPtr context)
    {
        if (Listeners.TryGetValue(context, out var listenerInstance))
        {
            listenerInstance.OnViewWillLayoutSubviewsCallbacks();
        }
    }

    [MonoPInvokeCallback(typeof(ViewDidLayoutSubviewsCallback))]
    private static void ViewDidLayoutSubviewsCallbackStatic(IntPtr context)
    {
        if (Listeners.TryGetValue(context, out var listenerInstance))
        {
            listenerInstance.OnViewDidLayoutSubviewsCallbacks();
        }
    }
}

具体的な呼び出し例についてはサンプルプロジェクトの Assets/_Example/Scenes/SampleScene.unity シーンと関連コードをご覧ください。

おわりに

本記事では PluginBase 以下にある各種リスナーとそれをプラグインから活用する方法について解説しました。

とはいえ、以下に挙げるような幾つかのイベントは Unity 標準の機能からでも受け取ることが可能であり、特殊なケースを除けばあまり使わなくても済む場面の方が多いのかなとは思います。

ただ、プラグイン側で細かい制御などを行おうとすると、標準機能だけでは痒い所に手が届かないケースもあるかと思われるので、そういった場合に備えて参考にしていただけると幸いです。

おまけ: Unity 6.6 から入る変更について

ちょうど一ヶ月ぐらい前に開催された Unite 2025 Barcelona のロードマップ講演にて Unity と iOS のブリッジとなるレイヤーを刷新すると言うアナウンスがありました。

それに加えて Swift への移行を対応するほか、ライフサイクルやプラットフォームアクセスを容易にするための新しい API を追加するとの話がありました。

特に後者については今回解説したリスナー機能も関連してそうな気がするので、もし最新情報をキャッチアップできた際には随時更新していければと思います。

 2025-12-21 21.58.36.png


株式会社アプリボットでは、 エンジニアをはじめ全職種で積極採用中 です。
記事を読んで少しでも興味を持たれた方やお話を聞きたい方は、是非お気軽にご連絡ください!

  1. Structure of a Unity Xcode project には記載されておらず、他にも Unity Discussions なども探ってみてもあまり情報が見つからなかった...。

  2. 名前に PluginBase と付いており、ある程度の拡張性を見込んだ機能かとは思われるので、大丈夫だとは思いたい所ですが...。

  3. ちなみに Unity も内部的には UIViewController を生成し、その中の UIView を持つような構成となってます。

  4. protocol とは C# で言うところの interface に相当するもの。

  5. 上記のプロトコルには他にも interfaceWillChangeOrientationinterfaceDidChangeOrientation がありますが...関連する情報が見当たらなかった上に、どこで呼び出されているのかも不明だったため、説明中では取り扱ってません...。 :bow:

  6. 全部定義すると数が多いので、ここでは解説向けに viewWillLayoutSubviews, viewDidLayoutSubviews だけ定義してます。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?