読者です 読者をやめる 読者になる 読者になる

A Day In The Life

とあるプログラマの備忘録

iOS のイベント駆動をライフサイクルイベントとユーザアクションイベントにわけて理解する

ios objective-c/objc

iOS は タッチパネル式端末用に最適化された OS で ユーザが端末を操作しやすいように GUI の仕組みが提供されています。iOS アプリ開発では主に GUI を操作するプログラムを実装していきます。iOSGUI プログラムはイベント駆動型と言われるプログラミング方式に則ってプログラムを実装していきます。
iOS に限らず Mac OS 用のアプリ開発でも同じようにイベント駆動型のプログラムを実装しますが、 iOS はタッチパネル式モバイル端末用の OS という性質上 Mac OS のイベント駆動プログラムと少し違うところがあります。
この記事では iOS のイベント駆動の仕組みを「ライフサイクルイベント」と「ユーザアクションイベント」にわけて説明します。iOS のイベント駆動がどういったものなのか理解してその仕組みの上で自由にプログラムできるようになることが目的です。

イベント駆動って何?

イベント駆動とはイベントと呼ばれるアプリや端末上で起きた出来事に対して処理を行うプログラムの実行形式のことです。
イベント駆動ではイベントが発生すると OS がイベントを検知してプログラマに通知(コールバック)してくれます。イベントが発生すると、ある特定のオブジェクトのメソッドが呼び出されます。一つのイベントで一つのメソッドが呼びだされる時もあれば、複数メソッドが呼びだされる時もあります。イベント駆動型のプログラミングではイベントによって呼び出されたメソッド内にプログラムを実装していきます。
本記事では、イベントによって呼び出されるメソッドのことをイベントメソッドと呼びます。

イベントは UIApplication オブジェクトで制御されている

iOS ではアプリを起動してから終了するまで UIApplication オブジェクトがイベントやアプリの状態を管理しています。iOS はイベントが発生するとイベントキューと呼ばれるキューにイベントを溜めていきます。そこに溜まったイベントをイベントループが順番に取り出し UIApplication オブジェクトがイベントメソッドの通知を行います。
イベント通知の仕組み
イベントループとはイベントを検知するためのループ処理(for または while を使った無限ループ)のことです。実行ループや RunLoop と呼ばれることがあります。

イベントの種類

上の説明でイベントとはアプリや端末上で起きた出来事だと書きましたが、実際イベントにはどういったものがあるのでしょうか。
一口にイベントといっても iOS からは様々なイベントが通知されます。イベントを大きく分類すると以下の2種類に分別されます*1

  1. 端末やアプリで起こった出来事に対するイベント(ライフサイクルイベント)
    電話がかかってきた、メモリの警告、アプリが起動した、ビューのロードが終わった、ビューを表示したなど
  2. ユーザからの動作で通知されるイベント(ユーザアクションイベント)
    ユーザが端末をタッチした、ユーザが端末を傾けたなど

アップルの公式ドキュメントでは特に2のことをイベントと呼んでいます。しかし一般的なイベント駆動プログラミングでは1も含めてイベントと呼ぶことが多いです。
本記事では上記1のイベントをライフサイクルイベント、2をユーザアクションイベントと呼び区別することにします。

ライフサイクルイベント

ライフサイクルイベントは端末やアプリで起こった出来事に対するイベントです。
イベントループでライフサイクルイベントが検知されると UIApplication オブジェクトが決められた順番で UIApplicationDelegate オブジェクトや UIViewController オブジェクトのメソッドを呼び出します。*2
イベントメソッドの呼び出し
アプリ全体に関係するイベントメソッドは UIApplicationDelegate プロトコルに、画面に関係するイベントメソッドは UIViewController クラスに定義されています。
ライフサイクルイベントは場面に応じてたくさんのイベントメソッドが呼び出されます。すべてを紹介することは出来ませんがアプリ開発で必ず押さえておきたいイベントメソッドの流れを以下の3つにわけて説明していきます。

  • アプリを起動してからホームボタンを押して閉じるまで
  • 画面を遷移したとき
  • メモリワーニングが発生したとき
アプリを起動してからホームボタンを押して閉じるまでに発生するライフサイクルイベント

アプリを起動するとはじめに UIApplicationDelegate オブジェクトに通知されます。そのあと UIViewController オブジェクトに通知されます。画面の表示が終わった後は OS に制御が戻り次のイベントが発生するまで待機します。
またユーザがホームボタンを押してアプリを閉じると UIApplicationDelegate オブジェクトに通知されます。アプリを閉じた時は UIViewController オブジェクトには何も通知されないので注意が必要です。
ユーザがアプリを起動してからホームボタンを押して閉じるまでのライフサイクルイベントを図にすると以下のようになります。
アプリが起動してから画面が表示されるまでのイベントの流れ
アプリ起動から一時停止までのイベントで呼び出されるイベントメソッドの説明です。

  • UIApplicationDelegate
    • application: willFinishLaunchingWithOptions:(iOS6追加)
      アプリの初期化が終わってストリーボードの読み込みが終わった後に呼び出される。アプリの状態復帰処理メドッドが発生する前に呼び出される
    • application: shouldRestoreApplicationState:(iOS6追加)
      リストア用データが保存されている(application: willEncodeRestorableStateWithCoder: メソッドの処理が存在する)時に呼び出される
    • application: didDecodeRestorableStateWithCoder:(iOS6追加)
      application: shouldRestoreApplicationState: メソッドの戻り値を YES に設定すると呼び出される。ここでデータの復帰処理を行う
    • application: didFinishLaunchingWithOptions:
      アプリの初期化が終わってストリーボードの読み込みが終わった後に呼び出される。アプリの状態復帰後、画面表示の直前に呼び出される
    • applicationWillEnterForeground:
      アプリケーションがバックグラウンド状態から復帰する直前に呼ばれる
    • applicationDidBecomeActive:
      アプリケーションがフォアグラウンド(前面実行)状態になったら呼ばれる
    • applicationWillResignActive:
      アプリケーションがフォアグラウンド状態からバックグラウンド状態に変わる直前に呼ばれる
    • applicationDidEnterBackground:
      アプリケーションがバックグラウンド状態になったら呼ばれる
    • application: shouldSaveApplicationState:(iOS6追加)
      アプリケーションがバックグラウンド状態になりホーム画面が表示される直前に呼ばれる
    • application: willEncodeRestorableStateWithCoder:(iOS6追加)
      application: shouldSaveApplicationState: メソッドの戻り値を YES に設定すると呼び出される。リストアデータの保存処理を行う
  • UIViewController
    • loadView
      Storyboard の画面デザインを読み込むタイミングで呼ばれる。
    • viewDidLoad
      ビューの読み込みが終わったら呼ばれる
    • viewWillAppear:
      ビューの読み込みが終わって画面描画アニメーションが始まる前に呼ばれる
    • viewWillLayoutSubviews
      ビューのレイアウトがはじまる直前に呼ばれる
    • viewDidLayoutSubviews
      ビューのレイアウトが終わると呼ばれる
    • viewDidAppear:
      画面描画アニメーションが完全に終わると呼ばれる
画面を遷移したときに発生するライフサイクルイベント

下記の図のように画面が既に表示されている状態から新たに別の画面をモーダル*3で表示した場合、画面を管理するそれぞれの UIViewContoller オブジェクトにイベントが通知されます。
画面遷移のイメージ
画面をモーダルで表示したときのライフサイクルイベントを図にすると以下のようになります。
モーダルビューで画面遷移したときのイベントの流れ
画面遷移イベントで呼び出されるイベントメソッドの説明です。

  • UIViewController
    • prepareForSegue: sender:
      画面遷移がはじまったら呼ばれる
    • viewWillDisappear:
      画面が閉じるアニメーションが始まる前に呼ばれる。遷移先ビューの読み込みが終わってなければ、先に遷移先 UIViewController オブジェクトの画面読み込みメソッド(loadView, viewDidLoad)が呼ばれる
    • viewDidDisappear:
      画面が閉じるアニメーションが終わって遷移先画面の表示が終わると呼ばれる

viewWillLayoutSubviews と viewDidLayoutSubviews メソッドは画面の初回表示時と画面が回転して再レイアウトが必要になったときに呼び出されます。画面の再表示時には呼び出されません(画面2が閉じて画面1が再表示されるときには呼ばれない)。

メモリワーニングが発生したときに発生するライフサイクルイベント

端末のメモリを使いすぎるとメモリワーニングが発生します。メモリワーニングのイベントが発生するとまず UIApplicationDelegate オブジェクトの applicationDidReceiveMemoryWarning: メソッドと画面1を管理する UIViewController オブジェクトの didReceiveMemoryWarning メソッドが呼ばれます。
画面構成が2画面でメモリワーニングが発生したときのライフサイクルイベントを図にすると以下のようになります。
メモリワーニングが発生したときのイベントの流れ
メモリワーニングイベントで呼び出されるメソッドの説明です。

  • UIApplicationDelegate
    • applicationDidReceiveMemoryWarning:
      メモリワーニングが発生したときに呼ばれる。一時的にメモリに保存しているデータをディスクに保存して不要メモリを解放するなど解放出来そうなオブジェクトがあればここで解放する
  • UIViewController
    • didReceiveMemoryWarning
      メモリワーニングが発生したときに呼ばれる。UIApplicationDelegate の applicationDidReceiveMemoryWarning: と役割的に同じ。画面単位で管理しているオブジェクトを解放するとよい

iOS6 から viewWillUnload と viewDidUnload メソッドが非推奨になりメモリワーニング時に呼ばれなくなりました。また iOS6 以降はメモリワーニング時のビューの解放処理を OS 側が自動的に行ってくれるため画面再表示時に loadView や viewDidLoad が呼ばれなくなりました。

ライフサイクルイベントの実装

それではライフサイクルイベントを実装する方法を見ていきましょう。UIApplicationDelegate オブジェクトに通知されるイベントと UIViewController オブジェクトに通知されるイベントで実装方法が少し違うのでそれぞれわけて説明していきます。

UIApplicationDelegate

アプリ全体に関わるライフサイクルイベントを実装するには、はじめに UIApplicationDelegate プロトコルを実装したクラスを作成する必要があります。
UIApplicationDelegateプロトコルを実装
上記の図をプログラムで書くと以下のようになります。

@interface AppDelegate <UIApplicationDelegate>

@end

次に UIApplicationDelegate プロトコルに定義されているイベントメソッドを必要に応じて実装します。
以下はイベントメソッドの実装例です。

@implementation AppDelegate
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  // アプリが起動した時の処理(リストア前)
  return YES;
}
- (BOOL) application:(UIApplication *)application shouldRestoreApplicationState:(NSCoder *)coder
{
  // リストアデータを使ってアプリの状態を復帰させるかどうか
  return YES;
}
- (void) application:(UIApplication *)application didDecodeRestorableStateWithCoder:(NSCoder *)coder
{
  // データ復帰処理
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  // アプリが起動した時の処理
  return YES;
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
  // アプリがバックグラウンド状態になる直前の処理
}
- (BOOL) application:(UIApplication *)application shouldSaveApplicationState:(NSCoder *)coder
{
  // リストアデータを保存するかどうか
  return YES;
}
- (void) application:(UIApplication *)application willEncodeRestorableStateWithCoder:(NSCoder *)coder
{
  // リストアデータの保存処理
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
  // アプリがバックグラウンド状態から復帰した時の処理
}
@end
UIViewController

画面に関わるライフサイクルイベントを実装するには、はじめに UIViewController クラスを継承したクラスを作成する必要があります。
UIViewControllerクラスを継承
上記の図をプログラムで書くと以下のようになります。

@interface ViewController : UIViewController

@end

次に UIViewController クラスに定義されているイベントメソッドを必要に応じてオーバーライドします。
以下はイベントメソッドのオーバーライドの例です。

@implementation ViewController
- (void)viewDidLoad
{
  // スーパークラスで定義されているメソッドの呼び出し
  [super viewDidLoad];
  // ビューの読み込みが終わった後の処理
}
- (void)viewWillAppear:(BOOL)animated
{
  [super viewWillAppear:animated];
  // 画面が表示される直前の処理
}
- (void)viewWillDisappear:(BOOL)animated
{
  [super viewWillDisappear:animated];
  // 画面の表示が終わる直前の処理
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
  [super prepareForSegue:segue sender:sender];
  // ビューコントローラの遷移直前の処理
}
- (void)didReceiveMemoryWarning
{
  [super didReceiveMemoryWarning];
  // メモリワーニング時の処理
  if ([self.view window] == nil) {
    // ビューが解放された時の処理を書く
    // viewDidLoad で独自に生成した UI 部品オブジェクトの解放を行う

    // 最後に view プロパティの解放を行う
    self.view = nil;
  }
}
@end

UIViewController 関連のイベントメソッドをオーバーライドする時は必ずスーパークラスで定義されているメソッドの呼び出しを行ってください。

ユーザアクションイベント

ユーザアクションイベントはユーザが端末に対して何か操作をしたときに発生するイベントです。
iOS では以下の3つの操作に対してユーザアクションイベントが発生します。

  1. タッチイベント
    ユーザが端末の画面をタッチした
  2. モーションイベント
    ユーザが端末を傾けた。シェイク(端末を振る)モーションイベントのみ UIKit で取得可能。その他モーションは Core Motion フレームワークを使って取得する
  3. リモートコントロールイベント
    ユーザがリモコン(標準イヤホンに付属のコントローラなど)を使って音楽の再生や停止命令を出した

本記事ではタッチイベントとシェイクモーションイベント、リモートコントロールイベントについて説明します*4
ユーザアクションイベントはイベントループで検出されるとまず iOS がイベント送信先を判定します(イベント送信先の判定方法はタッチイベントとそれ以外のイベントでことなります)。
イベント送信先が決まると UIApplication オブジェクトが UIWindow オブジェクトにイベントを通知します。その後 UIWindow オブジェクトが UIView オブジェクト(正確には後述するヒットテストビューまたはファーストレスポンダ)のイベントメソッドを呼び出します。
イベントメソッドの呼び出し
イベントメソッドを呼び出してそのオブジェクトが処理できなければ、レスポンダチェインと呼ばれるイベント処理の階層をたどってイベントメソッドを処理できるオブジェクトを探します。
それではイベントごとに詳細を説明してきます。

タッチイベント

タッチイベントはユーザがタッチしたビューを探すところからはじまります。タッチされたビューがわからないとイベントの送信先がわからないからです。タッチイベントが発生するとまず UIWindow オブジェクトがヒットテストという手法を使ってタッチされたビューを探します。
タッチされたビューが判明すると UIApplication から UIWindow オブジェクトを経由してタッチされたビューに対してイベントが送信されます。イベントが送信されたビューでイベントが処理できない場合はレスポンダチェイン(Responder Chain)をたどってイベントを処理できるオブジェクトがないか探していきます。
それでは具体的にどのようにヒットテストとレスポンダチェインが実行されているのか以下のサンプルアプリを例に説明していきます。
サンプルアプリの画面イメージ
6個の UIView オブジェクトを背景色を変えて貼付けただけのアプリです。ビュー構造は以下のようになっています*5
ビュー階層
ビューの構造について補足すると iOS では階層の上のビューのことをスーパービュー(superview)、階層の下のビューのことをサブビュー(subview)と呼びます。
スーパービューとサブビュー
一つのビューについてスーパービューは必ず一つ存在し、サブビューは0から複数個存在します。

ヒットテストでイベント送信先を判定する

タッチイベントが発生するとユーザが端末のどこをタッチしたかがわかる座標情報が iOS から送られてきます。UIWindow オブジェクトはイベントから送信された座標情報をもとにビュー階層をたどってタッチされたビューを探していきます。このように UIWindow オブジェクトがタッチされたビューを探す手法のことをヒットテスト(当たり判定とも言われます)といいます。ヒットテストで検出されたビュー(タッチされたビュー)のことをヒットテストビューといいます。
ヒットテストでは UIWindow オブジェクトがサブビューをたどってタッチされたビューを探していきます。ヒットテストしてタッチ範囲にそのビューが存在していればさらにその下のサブビューをたどっていきます(サブビューが複数存在するときは配列の後ろからヒットテストしていきます)。
例えばサンプルアプリでユーザが黄色のビューをタッチした場合、ヒットテストの動きがどうなるか図にすると以下のようになります。
ヒットテストのイメージ

レスポンダチェインをたどってイベントを処理できるオブジェクトを探す

レスポンダチェインとはレスポンダオブジェクト同士の連鎖のことを指します。レスポンダオブジェクトはユーザアクションイベントを処理することができるオブジェクトのことです。レスポンダオブジェクトは UIResponder クラスのサブクラスのオブジェクトでなければいけないという決まりがあります。iOS では UIResponder クラスのサブクラスとして以下のクラスが提供されています。

  • UIApplication
  • UIViewController
  • UIView(UIView クラスのサブクラスである UIWindow も含む)

上記以外に iOS 5 以降ではプロジェクト作成時に自動生成される UIApplicationDelegate の実装クラス(初期設定だと AppDelegate クラス)も UIResponder クラスを継承しています。
ユーザアクションイベントで関係するクラスと UIResponder クラスの関係を図にすると以下のようになります。
UIResponderクラス周辺のクラス図
UIResponder クラスではユーザアクションイベント用の各種メソッドやレスポンダチェインのためのメソッドが定義されています。
レスポンダチェインでは UIView の階層構造に加えてレスポンダオブジェクトである UIViewController オブジェクトや UIApplication オブジェクトがその階層に加わります。とあるレスポンダオブジェクトの次のレスポンダオブジェクトのことをネクストレスポンダ(Next Responder)と呼びます。UIViewController オブジェクトや UIApplication オブジェクトがレスポンダチェインでつながっていることでプログラマは好きなタイミングでタッチイベントを処理することができます。
例えばサンプルアプリのレスポンダチェインの階層構造を図にすると以下のようになります。
レスポンダチェイン階層構造
タッチイベントではまずヒットテストビューにイベントが送信され、そのビューでイベントが処理できなければネクストレスポンダをたどって処理できるビューを探していきます(ビュー階層のなかではスーパービューがネクストレスポンダになります)。ビューで処理できない場合はレスポンダチェインをたどって UIViewController, UIWindow, UIApplication, AppDelegate オブジェクトと順番にたどっていきます。
例えばサンプルアプリでヒットテストビューからレスポンダチェインをたどっていく動きを図にすると以下のようになります。
レスポンダチェインの例
イベントが処理できるかできないかはレスポンダオブジェクトでタッチイベントメソッド(後ほど説明します)が実装されているかどうかで判定されます。
レスポンダオブジェクトでタッチメソッドが実装されていればレスポンダチェインをたどる処理はそこで終了します。また最終的に AppDelegate オブジェクトでイベントの処理が出来ない場合はそのイベントは無視されます。

タッチイベントで呼び出されるメソッド

タッチイベントの実装は UIResponder クラスで定義されている以下の4つのメソッドをオーバーライドします。

  • touchesBegan: withEvent:
    タッチが開始されたときに呼び出されるメソッド
  • touchesMoved: withEvent:
    タッチしたまま指を動かしたときに呼び出されるメソッド
  • touchesEnded: withEvent:
    タッチした指が端末からはなれたときに呼び出されるメソッド
  • touchesCancelled: withEvent:
    タッチ中に電話がかかってくるなどタッチ処理が中断されたときに呼び出されるメソッド
タッチイベント以外はファーストレスポンダオブジェクトにイベントが送信される

シェイクモーションイベントとリモートコントロールイベントはイベントが発生するとファーストレスポンダ(First Responder)オブジェクトにイベントが送信されます。
ファーストレスポンダオブジェクトはタッチイベント以外のイベントをはじめに受けるレスポンダオブジェクトのことです。
ファーストレスポンダはプログラマが明示的に指定する必要があります(イベントの実装編で詳しく説明します)。ファーストレスポンダを指定しない場合、イベントは無視されます。
シェイクモーションイベントとリモートコントロールイベントではイベント送信後、タッチイベントと同じようにレスポンダチェインをたどって処理できるオブジェクトを探します。
※シェイクモーションイベントとリモートコントロールイベントではイベント送信後レスポンダチェインをたどらないと書きましたが検証の結果、間違っていたことがわかりました。訂正します。

シェイクモーションイベント

シェイクモーションイベントの実装は UIResponder クラスで定義されている以下の3つのメソッドをオーバーライドします。

  • motionBegan: withEvent:
    端末が振られたら(シェイク)呼ばれるメソッド
  • motionEnded: withEvent:
    シェイクが終わったら呼ばれるメソッド
  • motionCancelled: withEvent:
    電話がかかってくるなどシェイクが中断されたら呼ばれるメソッド
リモートコントロールイベント

リモートコントロールイベントを受け取るにはファーストレスポンダオブジェクトであることに加えて、アプリがリモートコントロールイベントを受け取ることができる旨を通知する必要があります。
UIApplication オブジェクトの beginReceivingRemoteControlEvents メソッドを呼んで通知します。
リモートコントロールイベントの実装は UIResponder クラスで定義されている以下のメソッドをオーバーライドします。

  • remoteControlReceivedWithEvent:

上記メソッドの引数としてわたってくる UIEvent オブジェクトにリモコンからの情報(再生、停止、早送りなど)が送られてきます。

ユーザアクションイベントの実装

ユーザアクションイベント関連のイベントメソッドは UIResponder クラスに定義されています。ユーザアクションイベントを実装するには UIResponder クラスを直接継承しているクラスまたは UIResponder クラスのサブクラスである UIView, UIViewController, UIApplication のサブクラスで実装できます。実際の開発では以下のように UIView のサブクラスまたは UIViewController のサブクラスで実装することがほとんどです。
ユーザアクションイベントの実装
それでは UIViewController クラスのサブクラスでユーザアクションイベントを実装する例を見てみましょう。
タッチイベント、モーションイベント、リモートコントロールイベントで実装方法が少し違うのでそれぞれわけて説明していきます。

タッチイベントの実装

タッチイベントを実装するには UIResponder クラスに定義されているタッチイベント関連のイベントメソッドをオーバーライドします。UIViewController クラスのサブクラスでタッチイベントの実装をした場合、画面に対するすべてのタッチが通知されます。UIView クラスのサブクラスで実装した場合は UIView オブジェクトの表示範囲に対するタッチのみ通知されます。
以下は UIViewController クラスのサブクラスでタッチイベントの実装をするプログラム例です。

@implementation ViewController
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
  // タッチが開始されたときに呼び出されるメソッド
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
  // タッチしたまま指を動かしたときに呼び出されるメソッド
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
  // タッチした指が端末からはなれたときに呼び出されるメソッド
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
  // タッチ中に電話がかかってくるなどタッチ処理が中断されたときに呼び出されるメソッド
}
@end

UIViewController や UIView の直接のサブクラスでタッチイベントを実装するときは必ず上記4つのメソッドを実装する必要があります。特に処理がない場合は空で実装します。またこのときスーパークラスのタッチイベントメソッドを呼んではいけないという決まりがあります。

シェイクモーションイベントの実装

シェイクモーションイベントを実装するためには、はじめにファーストレスポンダの設定する必要があります。
以下は UIViewController クラスのサブクラスをファーストレスポンダに設定するためのプログラム例です。

@implementation ViewController

- (BOOL)canBecomeFirstResponder
{
  return YES;
}
- (void)viewDidAppear:(BOOL)animated
{
  [super viewDidAppear:animated];
  [self becomeFirstResponder];
}
- (void)viewDidDisappear:(BOOL)animated
{
  [self resignFirstResponder];
  [super viewDidDisappear:animated];
}

@end

次に UIResponder クラスに定義されているシェイクモーションイベント関連のイベントメソッドをオーバーライドします。

- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
  // シェイクが開始されたときの処理
}
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
  // シェイクが終わったときの処理
}
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
  // シェイクがキャンセルされたときの処理
}

これでユーザが端末を振った時に上記のメソッドが呼ばれるようになります。

リモートコントロールイベントの実装例

リモートコントロールイベントを実装するためには、はじめにファーストレスポンダの設定に加えてリモートコントロールイベントの通知を受け取るための設定をする必要があります。
リモートコントロールイベントの通知を受け付けるためには UIApplication オブジェクトの beginReceivingRemoteControlEvents メソッドを呼ぶ必要があります。なお UIApplication オブジェクトは sharedApplication メソッドでオブジェクトを取得することが出来ます。

@implementation ViewController

- (BOOL)canBecomeFirstResponder
{
  return YES;
}
- (void)viewDidAppear:(BOOL)animated
{
  [super viewDidAppear:animated];
  // リモートコントロールイベントの通知を受け付ける
  [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
  [self becomeFirstResponder];
}
- (void)viewDidDisappear:(BOOL)animated
{
  // リモートコントロールイベントの通知を終了する
  [[UIApplication sharedApplication] endReceivingRemoteControlEvents];
  [self resignFirstResponder];
  [super viewDidDisappear:animated];
}

@end

次に UIResponder クラスに定義されているリモートコントロールイベント関連のイベントメソッドをオーバーライドします。

- (void)remoteControlReceivedWithEvent:(UIEvent *)receivedEvent
{
  if (receivedEvent.type == UIEventTypeRemoteControl) {
    switch (receivedEvent.subtype) {
      case UIEventSubtypeRemoteControlTogglePlayPause:
        // プレイまたはポーズボタンを押したときの処理
        break;
      case UIEventSubtypeRemoteControlPreviousTrack:
        // 戻るボタンを押したときの処理
        break;
      case UIEventSubtypeRemoteControlNextTrack:
        // 次へボタンを押したときの処理
        break;
      default:
        break;
    }
  }
}

receivedEvent オブジェクトの subtype にどのボタンが押されたか情報が渡ってきますので押されたボタンの種類によって処理を分岐することが出来ます。

まとめ

イベントはライフサイクルイベントとユーザアクションイベントにわけることが出来きます。
またさらに細かくわけると以下の用になります。

  • ライフサイクルイベント
    • アプリ全体に関わるイベント
    • 画面に関わるイベント
  • ユーザアクションイベント
    • タッチイベント
    • モーションイベント
    • リモートコントロールイベント

ライフサイクルイベントもユーザアクションイベントもイベントループで監視されています。イベントが発生することによって呼び出されるメソッドのことをイベントメソッドと呼びます。
ライフサイクルイベントは場面によって呼び出されるイベントメソッドと順番が決まっています。
ユーザアクションイベントはイベントがどこに伝達されるのか理解するのがポイントです。この仕組みがわかっていないとイベントメソッドを実装したのに呼び出されないなんてこともありえます。

ブログの記事が本になりました!

A Day In The Life の iOS に関する記事が書籍になりました。この記事の内容は書籍で読むことができます(2015年1月17日にSwiftに対応した改訂版がでました)。

本の内容に関する詳しい記事はこちらです。

記事の検証に使用したサンプルプログラムを公開します

ライフサイクルイベントの検証に使用したプログラムです。

ユーザアクションイベントの検証に使用したプログラムです。

*1:その他に位置情報の取得などプログラマが明示的にイベントの受け取りを設定するイベントもありますここでは割愛します

*2:UIViewController の viewDidAppear: と viewDidDisappear: メソッドは例外でUIApplicationオブジェクトからではなく GraphicsServices というフレームワークから呼び出されます

*3:一度開いたビューを閉じるまで、他の操作をできなくするタイプのビュー。iOS の画面遷移で頻繁に使用される方法の一つ

*4:Core Motion は独立した一つのフレームワークであり、今回紹介するイベントの仕組みとは違う方法でイベント取得します。Core Motion について説明すると記事の内容とはなれてしまうため割愛します

*5:サンプルアプリに限らず iOS アプリのビュー構造は必ず UIWindow オブジェクトが親になります(UIWindow オブジェクトもビューの1種です)。UIWindow オブジェクトの下に UIView(またはそのサブクラス)オブジェクトが階層で存在します。