Hatena::ブログ(Diary)

A Day In The Life RSSフィード Twitter

2012-06-03

近況のご報告

私事で誠に恐縮ですが、5月21日付けでグリー株式会社入社しましたことをご報告させていただきます。

次の仕事を探すにあたり Web サービス系の会社も受けたのですがソーシャルゲームの仕事からたった1年半で離れてしまうのはなんかもったいなぁと感じていました。そんな時にちょうどグリーの大阪オフィス開設の噂を聞きました。関西でソーシャルゲームの仕事ができるとは考えていなかったので渡りに船とばかりに応募した次第です。

前職ではアプリ開発一色でサーバ側の開発にあまり関わりませんでしたが、今後はアプリ開発に加えてサーバ側の開発にも積極的に関わっていきたいと考えています。

今後はグリーの大阪オフィスに所属してスマホ系のアプリ開発に関わる予定です。

予定と書いたのは大阪オフィスは開設しているのですが、本格的にアプリ開発が始まるのが8月以降らしくそれまでの間、東京本社で研修と仕事をしています。

一応、大阪採用エンジニア第1号らしいです。なかなか良いタイミングで入社できたのではないかと思っています。

現在、中途入社エンジニア向け研修のまっただ中で、まだまだ半人前ではありますが会社ひいては社会に貢献できるようにがんばりたいと思います。

関連

グリー株式会社 | 採用情報

関連記事

2012-06-01

iOS アプリの画面開発の基礎を理解する

前回の記事「iOS アプリの構造がどのようになっているか紐解いてみる」で iOS アプリの構造について説明しました。iOS アプリがどんなオブジェクトで形成されていてどんな風に動くのかある程度理解できたのではないかと思います。アプリの構造がわかったところで次は iOS で実際どんな画面が開発できるのか。どうやって開発するのかといったところを説明して行きたいと思います。

アプリの種類

それでは iOS で開発することが出来るアプリの種類についてみてみましょう。

以下は iOS で開発することが出来るアプリの種類です。Xcode で用意されているアプリテンプレートを元に分類してみました。

  • Single View Application
    画面が1つだけのシンプルなアプリ
  • Utility Application
    メインの画面と設定画面で構成されるアプリ
  • Master Detail Application
    一覧画面と詳細画面で構成されるアプリ
  • Tabbed Application
    タブを使って画面を切り替えるタイプのアプリ
  • Page-Based Application
    ページめくりを使うアプリ
  • OpenGL Game
    OpenGL を使ったゲーム。ゲームについては記事の範囲外になりますので詳細は割愛します。

上記以外にも開発者のアイデア次第で全く新しい種類のアプリや上記を組み合わせてアプリを発展させることができます。上記はあくまでアプリの基本形として考えてください。

画面と画面遷移の種類

先の説明でゲームを除くとアプリの基本的な種類は5種類に分類できることがわかりました。iOS アプリの中身をもう少し細かく画面と画面遷移に注目して分類してみます。

以下は iOS アプリの代表的な画面と画面遷移の種類を分類したものです。

画面の種類

iOS アプリの画面の種類は大きくわけて2つに分類できます。

  • テーブルビュー(Table View)
    リスト形式でデータを表示するビュー
    テーブルビューの例
  • テーブルビューを使わないビュー
    文字通りテーブルビューを使わない画面全般のことです
    テーブルビュー以外の例
画面遷移の種類

アプリにおける画面遷移とは一体なんでしょうか?一般的にはある画面から別の画面に移る(=遷移する)ことを画面遷移と呼びます。それだけだと少し分類しにくいので、この記事では画面遷移を大きくとらえて画面の上に別の画面を重ねて表示することや複数の画面を切り替えることも画面遷移と呼ぶことにします。

それをふまえて画面遷移を分類すると以下のようになります。

  • モーダル(Modal)
    画面の上に新たに画面を重ねていく画面遷移方法。重ねられた(下位にある)画面は新たに開いた画面が閉じるまで操作できない。一番上に表示されているユーザが操作可能なビューのことをモーダルビューと呼ぶ
    モーダルの例
  • ポップオーバー(Popover)
    画面の上に小さな画面を重ねて表示する。機能的にはモーダルと同じ。iPad で使用される
    ポップオーバーの例
  • ナビゲーション(Navigation)
    階層構造のあるデータを詳細画面に掘り下げていく画面遷移方法。ドリルダウンと呼ばれることもある。テーブルビューとセットで使用することが多い
    ナビゲーションの例
  • スプリット(Split)
    画面を2つの領域にわけて表示する。左側の項目を選択すると右側のページが切り替わる。iPad でナビゲーションの代わりに使用する。横向き(Landscape)と縦向き(Portrait)で表示が変わる。縦向きの時は右側の画面のみ表示され左側の画面はポップオーバーで表示する
    スプリットの例
  • タブ(Tab)
    タブを使って画面を切り替える画面遷移方法
    タブの例
  • ページめくり
    電子書籍などページをめくって画面を移動する画面遷移方法
    ページめくりの例

画面 = ビュー + ビューコントローラ

iOS アプリの画面はユーザに見せる画面そのものと、それを制御するビューコントローラで構成されています。ユーザに見せる画面のことをビューと呼びます。ビューの実態は UIView クラス(またはそのサブクラス)のオブジェクトです。ビューを管理しているのがビューコントローラでその実態は UIVIewController クラス(またはそのサブクラス)のオブジェクトです。

画面に配置したビューはツリー構造になってビュー同士が関連しています。ビューコントローラはビュー階層の一番もとのビューを管理しています。

以下は画面いっぱいに表示した UIView オブジェクトの上に適当な大きさの UIView オブジェクトを重ねてさらにその上に UILabel オブジェクトと UIButton オブジェクトを配置したアプリのサンプルです。

アプリのイメージ

上記アプリのビューとビューコントローラの関係は以下のようになっています。

ビューとビューコントローラの階層

ビューの上にビューを重ねると、重ねたビューがビュー階層に追加されます。

iOS アプリにおける画面遷移とは

ビューコントローラが遷移すると管理しているビューが変わるので画面が切り替わるというのが iOS における画面遷移の正体です。ユーザから見た画面遷移はプログラム上ではビューコントローラの制御が切り替わっていることになります。見た目上の画面遷移はプログラムの視点から言うとビューコントローラの遷移になります。iOS ではビューコントローラの遷移のことをセグエ(Segue)と呼びます。

遷移についての用語をまとめると以下のようになります。

セグエ(iOS 上の名称) = 画面遷移(ユーザ目線) = ビューコントローラの遷移(開発者目線)

これ以降、アプリの画面説明では単に画面遷移と表現しますが、プログラム説明の時にはビューコントローラの遷移またはセグエという表現を使います。

画面デザインとストーリーボード

ストリーボードはビューの配置やビューとビューコントローラの関係、ビューコントローラの遷移(セグエ)をプログラムを書かずビジュアルに定義することができます。では実際にストーリーボードを使って画面デザインをする方法をみてみましょう。

先ほど「画面 = ビュー + ビューコントローラ」の説明で使ったアプリを例に画面デザインの方法を説明します。

ビューを配置する

ビューの配置方法から見ていきましょう。

プロジェクトの .storyboard 拡張子のファイルをクリックしてストリーボードを開きます。

以下のように、右下にある Objects 欄の「View」をマウスで選択してステータスバーのついた画面の上にドラックドラッグします(Objects 欄が表示されない方は Xcode メニューの View > Utilities > Show File Inspector を選択すると表示されます)。

ビューの配置

同じ要領で Label や Round Rect Button など画面に配置したい UI 部品を選んでドラッグします。

サンプルの例では View, Label, Round Rect Button を配置しています。

ビューコントローラの遷移を追加する

Objects 欄の「View Controller」をマウスで選択して方眼紙領域ドラッグしてください。

ビューコントローラの追加

元々あった画面のボタンをマウスで選択して「control」ボタンを押しながら新しく追加した画面にドラッグします。

セグエの追加

マウスを離すと小さなポップアップが表示されますのでそこから「Modal」を選択してください。

セグエの選択

以上でビューの配置とビューコントローラの遷移が定義できました。アプリを起動するとボタンがラベルとボタンが表示されボタンを押すと画面が遷移します。

遷移先画面から元の画面に戻る処理を追加する

ここからはストリーボードの設定に加えて少しだけプログラムを書く必要があります。

まずはじめに UIViewController クラスを継承した新たなクラスをプロジェクトに追加します。クラス名は ModelViewController とします。

次にストーリーボードを開いて新たに追加した画面の下部にある「Modal View Controller」アイコンを選択します。

Modal View Controllerを選択

下記のように、右側のCustom Class グループの Class 項目を UIViewController から ModalViewController に変更します。

クラスを変更する

次に以下のように ModalViewController クラスにボタンがクリックされた時の処理を追加します。

@interface ModalViewController : UIViewController
// ボタンが押された時に呼ばれるメソッド
- (IBAction)respondToButtonClick:(id)sender;

@end

@implementation ModalViewController

- (IBAction)respondToButtonClick:(id)sender
{
  // 画面を閉じる処理
  [self dismissViewControllerAnimated:YES completion:nil];
}

@end

respondToButtonClick: メソッド戻り値の IBAction はストリーボードのボタンオブジェクトプログラムをつなぐために必要な設定です。実際は void と同じです。ちなみに IB は Interface Builder の略です。

最後にストーリーボードのボタンとプログラムを接続します。Xcode のメニューから「View」「Assistant Editor」「Show Assistant Editor」の順に選択して Assistant Editor を開きます。Assistant Editor を開くと右側にプログラムが表示されます。その後、以下のように Modal View Controller のボタンオブジェクトを選択して「control」を押しながらマウスメソッドの定義までドラッグします。

IBActionを接続

以上で新たに表示した画面を閉じる処理の定義ができました。

UIView クラス

UIView は画面に矩形を表示する機能とサブビューを管理する機能を提供してくれるクラスです。UIView オブジェクト自体は矩形を表示することしかできませんが、iOS ではボタンやラベルなど UIView を拡張したクラスが多数提供されています。

ビューの追加方法

画面にビューを追加する方法は2つあります。一つは先ほど紹介したストーリーボードを使う方法、もう一つはプログラムで追加する方法です。ここではプログラム上でビューを追加する方法を説明します。

プログラム上でビューを追加するにはまず UIViewController クラスのサブクラスを作成してそのクラスの viewDidLoad メソッドにビュー追加のプログラムを書いていきます。

以下はその例です。

@implementation ViewController
- (void)viewDidLoad
{
  [super viewDidLoad];
  /*
   * ここにビューを追加するプログラムを書く
   */
}
@end

次にビューをインスタンス化します。UIView クラスにはビュー初期化用の initWithFrame: メソッドが用意されていますのでそのメソッドを使ってインスタンス化します。initWithFrame: メソッドは CGRect 構造体のインスタンス引数にとります。

以下は initWithFrame メソッドを使ってオブジェクトを生成する例です。

// xy 座標 (0, 0)、幅100、高さ100の UIView クラスのインスタンスを生成する
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];

最後に表示するビューを追加します。ビューコントローラは自身の管理するビューを view プロパティとして持っています。この view プロパティにサブビューを追加します。

サブビューの追加には UIView クラスの addSubview: メソッドを使います。このメソッドは UIView オブジェクト引数にとり、引数の UIView オブジェクトをビュー階層に追加します。

以下は UIView クラスのオブジェクトを生成後、背景色をセットしてビューを追加するプログラムの例です。

@implementation ViewController
- (void)viewDidLoad
{
  [super viewDidLoad];
  UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
  // UIView オブジェクトの背景色のデフォルト値は透明なため背景色を別の色に設定
  view.backgroundColor = [UIColor redColor];
  [self.view addSubview:view];
}
@end

上記プログラムを実行すると以下のような画面が表示されます。

ビュー追加後の画面

ビュー追加後のオブジェクトの構成は以下のようになります。

ビュー追加後のビューの階層

ビューの種類

先ほど iOS には UIView を拡張したクラスが多数あると説明しましたがどのようなクラスがあるのか見てみましょう。

以下は UIView クラスを継承したクラスの関係を表した図です。

UIView関連のクラス

上記図には iOS で提供されている UIView のサブクラスすべて含まれています。すべてのクラスをリストアップしてみます。

表示系

文字や画像などデータを表示する

  • UILabel
  • UIImageView
コントロール系

ユーザがタッチしてデータを入力またはデータを選択する

  • UIControl
  • UIButton
  • UISwitch
  • UISlider
  • UIStepper
  • UIPageControl
  • UISegmentedControl
  • UITextField
ピッカー系

ピッカー形式でデータ表示する

  • UIDatePicker
  • UIPickerView
バー系

画面の上部や下部に表示するバー

  • UINavigationBar
  • UIToolbar
  • UITabBar
  • UISearchBar
ユーザ待機系

ユーザに処理中であることを通知する

  • UIProgressView
  • UIActivityIndicatorView
ユーザ対話系

ユーザにアクションを提案して次の行動をうながす

  • UIActionSheet
  • UIAlertView
スクロール系

画面サイズより大きなデータをスクロールする

  • UIScrollView
  • UITextView
  • UIWebView
テーブル系

リスト形式でデータを表示する

  • UITableView
  • UITableViewCell

UIViewController クラス

UIViewController クラスは iOS アプリの画面と紐づいて画面や画面遷移を管理するクラスです。iOS アプリには必ず画面が一つ以上存在しているためアプリ開発者は UIViewController クラスを必ず使用しなければなりません。UIViewController は iOS アプリ開発の中心となるクラスです。

UIViewController クラスが提供する機能は大きくわけると2つあります。1つは画面に表示しているビューを管理する機能、もう1つは他のビューコントローラに遷移させる機能です。

ビューを管理する機能

UIViewController クラスにはビューが生成されるタイミングやビューが表示されるタイミングで呼び出されるイベントメソッドが定義されています。またメモリワーニング時にビューが破棄されるタイミングで呼び出されるメソッドなども定義されています。これらのイベントメソッドをオーバーライドしてビューのライフサイクルを監視することができます。

他のビューコントローラに遷移させる機能

UIViewController オブジェクトはユーザアクションに応じてストリーボードに定義されているセグエ(Segue)を参照して別のビューコントローラに制御を移します。

セグエにはモーダル(Model)、プッシュ(Push)、カスタム(Custom)の3種類あります。UIViewController クラスはモーダルで画面遷移する機能を提供します。モーダルセグエには Cover Vertical や Flip Horizontal など4種類のアニメーションを設定することが出来ます。

ビューの管理

UIViewController クラスはビューを管理するためにビューのライフサイクルに応じたイベントメソッドが定義されています。

UIViewController クラスのイベントメソッドをオーバーライドすることでビューを管理することができます。

UIViewController のライフサイクルイベントについての詳細は以下の記事を参考にしてください。

画面を追加して遷移させる方法

ここではストーリーボードを使わずにプログラム上で画面を追加してそこに遷移させる方法について説明します。プログラムは「ビューの追加方法」で使った ViewController クラスをベースにプログラムを追加していきます。

はじめに ViewController クラスの viewDidLoad メソッドにボタンを追加する処理とボタンが押された時に呼ばれるメソッドを追加します。

@implementation ViewController

- (void)viewDidLoad
{
  [super viewDidLoad];
  :
  : ビューの追加処理は省略
  :
  // ボタンオブジェクトを生成する
  UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
  // ボタンの大きさ表示させる場所を指定する
  button.frame = CGRectMake(0, 0, 100, 44);
  // ボタンに表示させる文字列の設定
  [button setTitle:@"button" forState:UIControlStateNormal];
  // ボタンが押された時に呼び出されるメソッドの設定
  [button addTarget:self action:@selector(respondToButtonClick:) forControlEvents:UIControlEventTouchUpInside];
  // ボタンをビュー階層に追加
  [self.view addSubview:button];
}

- (void)respondToButtonClick:(id)sender
{
  // ビューコントローラ遷移の処理を書く
}

@end

UIButton クラス(正確には親クラスの UIControl のメソッドです)の addTarget: action: forControlEvents: はボタンが押された時にどのオブジェクトのどのメソッドを呼び出すか設定することが出来るメソッドです。ボタン押下などユーザアクションが発生した時に処理を別のオブジェクトメソッドに依頼する方法をターゲットアクションパターンと呼びます。

次に遷移先ビューコントローラインポートします。

#import "ViewController.h"
#import "ModalViewController.h"

@implementation ViewController
:
: 省略
:
@end

最後にボタンが押された時の処理を追加します。

@implementation ViewController
:
: 省略
:
- (void)respondToButtonClick:(id)sender
{
  // ビューコントローラオブジェクトを生成する
  ModalViewController *controller = [[ModalViewController alloc] init];
  // 背景色を白に設定する
  controller.view.backgroundColor = [UIColor whiteColor];
  // ビューコントローラをモーダルで遷移させる
  [self presentViewController:controller animated:YES completion:nil];
}

@end

以上でビューコントローラに遷移させることができます。遷移先画面から戻る処理については、遷移先ビューコントローラでボタンオブジェクトを生成する以外ストーリーボードを使う時と同じなので省略します。

ビューコントローラの種類

iOS には UIViewController クラスを拡張した便利なクラスが存在します。これらのクラスを使用すると、より複雑な画面管理や画面遷移を簡単に作成できます。ビューコントローラは画面表示を支援するものと画面遷移を支援するものにわけることができます。

以下は UIViewController クラスを継承したクラスの関係を表した図です。

ビューコントローラの種類

それでは上記図のクラスをリストアップしてみます。

画面表示支援系
  • UITableViewController
    テーブルビューを表示するための機能を提供するクラス。UITableView と組み合わせて使う
画面遷移支援系
  • UINavigationController
    ナビゲーションを使った画面遷移の機能を提供するクラス
  • UISplitViewController
    スプリットを使った画面遷移の機能を提供するクラス
  • UITabBarController
    タブを使ったを使った画面遷移の機能を提供するクラス
  • UIPageViewController
    ページめくりを使った画面遷移の機能を提供するクラス

UIViewController と UITableViewController クラスはアプリ開発者がクラスを継承して使用します。他のクラスは直接使用します。

図に表すと以下のようになります。

UIViewControllerのサブクラス

画面表示支援系ビューコントローラは開発者が継承して使う、画面遷移支援系ビューコントローラは直接使うと覚えるとわかりやすいと思います。

UIView と UIViewController クラスの共通の機能

UIView と UIViewController クラスの共通の機能としてユーザアクションイベントを処理する機能があります。この機能は UIResponder クラス が提供している機能です。UIView と UIViewController クラスはともに UIResponder クラスを継承しているのでユーザアクションイベントを処理することができます。

UIView と UIViewController の共通の機能

ユーザアクションイベントの詳細は下記の記事を参照してください。

2012-05-07

iOS アプリの構造がどのようになっているか紐解いてみる

iOS アプリの構造がどのようになっているのか理解しなくても簡単なアプリを開発することは可能です。実際自分も iOS アプリの開発をはじめたことろはそうでした。しかしアプリの構造を理解していないと複雑なアプリ、例えばタブとナビゲーションを組み合わせた画面遷移をするアプリマルチタッチやジェスチャーを使ったアプリなどを作ろうとしたときにハマることが多いです。

本記事では iOS アプリの構造について説明します。

一番単純なアプリの構造

それでは iOS アプリの中でも一番単純なアプリの構造がどうなっているのか見てみましょう。

iOS で一番単純なアプリは画面を一つ表示するアプリです。画面を一つ表示するアプリはシングルビューアプリケーション(Single View Application)といいます。

ラベルもボタンもなく、ただ真っ白な画面を表示するだけのアプリがどのような構造になっているのかみてみましょう。

以下はシングルビューアプリケーションで使用されるクラスの関係を図にしたものです。

アプリに使われるクラス

図の中の青色のクラスは開発者が自分で作成するクラスです。その他のクラスは iOS から提供されているクラスです。図中の UIWindow クラスと UIStoryboard クラスは iOS が内部的に使用するクラスで開発者が直接これらクラスのオブジェクトを使うことはあまりありません。上記クラスの中でユーザが直接目に触れる部分の機能を提供しているのは UIView クラスです。それ以外のクラスは内部的な処理をするための機能を提供しています。

iOS から提供されているクラスの説明

それでは上記の図の中で iOS から提供されているクラスについて詳しく見ていきましょう。

UIApplication クラス

ウインドウの管理やイベント送信を行うクラス。UIApplication オブジェクトはステータスバーを表示するウインドウ*1アプリ本体を表示するウインドウを管理しています。ステータスバーを表示するウインドウは開発者が直接参照することはできません。アプリを表示するウインドウのみ参照できるようになっています。

また UIApplication クラスのオブジェクトはイベントキューにたまったイベントを UIApplicationDelegate オブジェクトや UIWindow オブジェクトに送信します。

iOS では1アプリにつき必ず1つの UIApplication が存在します。このクラスのオブジェクトはシングルトンになっていて以下のように sharedApplication クラスメソッドを呼び出すことでインスタンスを取得することができます。

[UIApplication sharedApplication]

このクラスは開発者が独自に継承してイベント処理をカスタマイズすることが可能です。

UIApplicationDelegate プロトコル

アプリから通知されるイベントメソッドが定義されているプロトコルプロトコルとは Java や C♯でいうところのインタフェースのこと。開発者はこのプロトコルを実装したクラスを作成する必要があります。このプロトコルに定義されているイベントメソッドの詳細は以下の記事を参照してください。

UIView クラス

画面に矩形領域を表示するための機能を提供するクラス。iOS アプリ開発で使用するあらゆる UI 部品のクラス(UIButton,UILabelなど)は UIView クラスを継承しています*2

このクラスに関する詳細は以下の記事を参照してください。

UIWindow クラス

ウインドウを表示する機能を提供するクラス。UIWindow クラスは UIView クラスを継承していてビューとして機能します。しかしウインドウはユーザからは見えず直接操作することもできません。UIWindow オブジェクトはユーザが画面やアプリに対して行ったアクション(画面をタッチしたり端末をシェイクしたり)に対するイベントを UIApplication オブジェクトから受け取り、イベントを処理できるオブジェクトを探して送信します。

UIViewController クラス

画面に表示しているビューを監視する機能と他のビューコントローラに遷移させる機能を提供するクラス。このクラスに関する詳細は以下の記事を参照してください。

UIStoryboard クラス

Interface Builder のストーリーボードファイルに格納されているビューコントローラオブジェクトを格納する機能を持ったクラス。この記事の後半で詳しく説明します。

開発者が作成するクラス

シングルビューアプリケーションで使用するクラスの中で AppDelegate と ViewController クラスはアプリ開発者が作成するクラスです。2つのクラスは Xcode でプロジェクトを作成したときに自動生成されます。2つのクラスの名前は開発者が変更することもできます。今回使用したクラス名はプロジェクト作成時に自動生成される名前をそのまま使用しています。

以下は開発者が作成する2つのクラスの説明です。

AppDelegate クラス

UIApplicationDelegate プロトコルを実装したクラス。アプリが起動したり終了したりバックグラウンド状態になったりバックグラウンド状態から復帰したりしたときに UIApplication オブジェクトが AppDelegate オブジェクトのイベントメソッドを呼び出します。

ViewController クラス

UIViewController クラスを継承したクラス。開発者は1画面ごとに UIViewController クラスを継承したクラスを作成します。UI 部品のアクション(例えばボタンを押したときの動きなど)を定義したり、複数の UI 部品が連携した時の動きを定義したりします。

実際に動いているアプリオブジェクトはどうなっているのか

シングルビューアプリケーションで使用されているクラスについて理解できたと思うので、実際にアプリが動いているときにオブジェクトがどのようになっているか見てみたいと思います。

まずはアプリ起動から画面表示までに各オブジェクトがどんな順番で生成されているか流れをまとめてみます。

以下はアプリが起動するまでの流れを図にしたものです。

アプリ起動の流れ

  1. main 関数から UIApplicationMain 関数が呼ばれる
  2. UIApplication クラスのインスタンスが生成される
  3. AppDelegate クラスのインスタンスが生成される
  4. Info.plist ファイルが読み込まれる
  5. UIApplication オブジェクトがイベントループ(Run Loop または実行ループとも呼ばれる)を実行する
    イベントループについての詳細は下記の記事を参照してください。
    -iOS のイベント駆動をライフサイクルイベントとユーザアクションイベントにわけて理解する
  6. Storyboard ファイルが読み込まれる
  7. UIApplication オブジェクトが ViewController クラスのインスタンスを生成する
  8. AppDelegate オブジェクトが UIWindow クラスのインスタンスを生成する
  9. AppDelegate オブジェクトの application: didFinishLaunchingWithOptions: メソッドが呼び出される

アプリの画面が表示された時点のオブジェクトとその関係を図にすると以下のようになります。

アプリを構成しているオブジェクト

ストーリーボードファイルの内容は UIStoryboard オブジェクトが保持します。

UIWindow オブジェクトはユーザからは見えませんがすべてのビューの親になるオブジェクトです。UIWindow オブジェクトには必ず1つの UIView オブジェクトがサブビューとして配置されています。

シングルビューアプリケーションのビュー階層を図にすると以下のようになります*3

ビューの階層

UIApplication オブジェクトの上に UIWindow オブジェクト、 UIView オブジェクトの順に重なっていてユーザからは UIView オブジェクトだけが見えるようになっています。

シングルビューアプリケーションにラベルとボタンを追加してみる

真っ白な画面を表示するシングルビューアプリケーションにラベルとボタンを追加するとアプリの構成がどうなるか見てみましょう。

以下の画面のアプリを例に説明していきます。

ラベルとボタンを表示するアプリ

上記のアプリの構成がどのようになっているかまずはクラス図を見てみます。以下はシングルビューアプリケーションにラベルとボタンを追加する場合に使用するクラスのクラス図です。

ラベルとボタン追加クラス図

真っ白な画面を表示するシングルビューアプリケーションのクラス図と比べると、画面にラベルを表示する UILabel クラスと画面にボタンを表示する UIButton クラスが追加されました。UILbael クラスは UIView クラスを直接継承していますが UIButton クラスは UIView クラスを直接継承せずにその子クラス UIControl クラスを継承しています。

それでは上記クラスがどのようにインスタンス化されるか見てみましょう。

以下はラベルとボタンを追加した画面を表示した時のオブジェクトを図にしたものです。

ラベルとボタン追加オブジェクト図

UILabel オブジェクトと UIButton オブジェクトが UIView オブジェクトのサブビューとして追加されたのがわかると思います。

シングルビューアプリケーションにラベルとボタンを追加した時のビュー階層を図にすると以下のようになります。

ラベルボタンを追加したビューの階層

シングルビューアプリケーションに画面を一つ追加してみる

ボタンを押すと新たに画面を表示するアプリの場合、その構造がどのようになるか見てみましょう。

以下の画面のアプリを例に説明していきます。

画面遷移するアプリ

ラベルとボタンを追加したアプリと比べてアプリの構成がどのようになっているかまずはクラス図で確認してみます。

以下はシングルビューアプリケーションに画面を追加する場合に使用するクラスのクラス図です。

画面を追加したときのクラス図

シングルビューアプリケーションに画面を追加するには UIViewController クラスを拡張したクラスを新たに作成します。図では新しい画面管理用の ModalViewController という名前のクラスを追加しています。

それでは追加した画面をモーダル(Model)で表示した時にオブジェクトの状態がどうなるのか見てみましょう。

以下は新規画面を表示したときのオブジェクトの状態を図にしたものです。

画面を追加したときのオブジェクト図

上記の図では、はじめに表示されていた画面のビューを管理している ViewController オブジェクトに rootViewController、新たに表示された画面のビューを管理する ModalViewController オブジェクトに destinationViewController という名前をつけています。

ユーザから画面が遷移したように見える動きは、画面を管理しているビューコントローラの制御が別のビューコントローラに切り替わったということになります。

ビューコントローラが遷移すると UIWindow オブジェクトの subview は ModalViewController オブジェクトの管理する UIView オブジェクトに変わります。

また遷移後は rootViewController オブジェクトの presentedViewController プロパティで遷移先の UIViewController オブジェクト(destinationViewController オブジェクト)を参照、destinationViewController オブジェクトの presentingViewController プロパティで遷移元の UIViewController オブジェクト(rootViewController オブジェクト)を参照することが出来ます。

なお「present」は表示や提示するという意味の動詞です。presentedViewController は遷移元からみて新たに表示されたビューコントローラー、presentingViewController は現在表示中のビューコントローラーを表示しているビューコントローラーという意味合いです*4

ストーリーボードとアプリの関係

iOS アプリのユーザインターフェースの設計は Interface Builder というツールを使って行います。アプリ開発者は Interface Builder を使ってストーリーボードと呼ばれるファイル(拡張子は .storyboard)にアプリのユーザインターフェースを作成していきます。

ストリーボードには画面のデザイン(例えば UI 部品の配置や背景色の設定など)に加えビューコントローラの遷移を定義することが出来ます。iOS ではビューコントローラの遷移のことをセグエ(Segue)と呼びます。

それではストリーボードで設計された内容をアプリがどのように読み込んで情報を管理しているか見ていきましょう。

クラス構成

まずはストーリーボードの情報を管理するクラスにどのようなものがあるか見ていきます。

ストーリーボード関連のクラスを図にまとめると以下のようになります。

ストーリーボード関連のクラス

画面デザインの情報は UIStoryboard オブジェクトが管理し、ビューコントローラの遷移情報は UIStoryboardSegueTemplate オブジェクトが管理します。画面デザインの情報は複数の UIViewController が共有するのに対し、ビューコントローラの遷移情報は UIViewController ごとに個別で保持します。また1つのビューコントローラに対して複数の遷移が定義できるため UIViewController クラスと UIStoryboardSegueTemplate クラスの関係は1対多になっています。

それではストリーボード関連のクラスの詳細を見ていきましょう。

UIStoryboard クラス

ストーリーボードファイルに格納されているビューコントローラオブジェクトを格納する機能を持ったクラス。アプリ起動時にストーリーボードファイルの情報からこのクラスのオブジェクトが生成されます。画面ごとに UINib オブジェクトに分割して管理します。

UINib クラス

ストーリーボードの画面デザインに関する情報を保持する機能を持ったクラス。ストーリーボードの情報を元にこのクラスのオブジェクトが UIViewController オブジェクトを生成します。

UIStoryboardSegueTemplate クラス

ビューコントローラの遷移情報を管理する機能を持ったクラス。このクラスは非公開クラスであり開発者がこのクラスのオブジェクトを直接参照することは出来ません。

UIStoryboardSegue クラス

遷移元と遷移先の UIViewController オブジェクトの情報を管理する機能を持ったクラス。遷移時に UIStoryboardSegueTemplate オブジェクトによってインスタンス化され UIViewController の prepareForSegue: sender: メソッド引数として渡ってきます。

ストリーボードファイルはどのように管理されているのか

我々開発者から見るとストーリーボードはビジュアルなファイルですが、実際の中身は単なる XML ファイルです。ストーリーボードファイルはアプリ起動時に XML の内容が読み込まれ画面デザインの情報は UIStoryboard オブジェクトが、ビューコントローラの遷移情報は UIStoryboardSegueTemplate という非公開クラスのオブジェクトが保持します。ストリーボードの内容は画面デザインからビューコントローラの遷移までアプリのデザインの情報をすべて持っています。一つのオブジェクトで管理するには情報が大きいので、UIStoryboard オブジェクトは画面ごとに UINib オブジェクトに分割してその情報を管理しています。

ストーリーボードファイルとオブジェクトの関係

UIStoryboard と UIStoryboardSegueTemplate オブジェクトは UIViewController オブジェクトが生成された時に UIViewController のプロパティとしてセットされます。

以下はアプリを立ち上げたあと、最初の画面が表示された時のオブジェクトの状態を表した図です。

ストーリーボードオブジェクト図

ビューコントローラ遷移時の動き

ボタンを押すと新たに画面を表示するアプリを例にビューコントローラ遷移時の動きを見ていきます。

画面をモーダルで表示させる場合、以下の図のようにストーリーボードを使ってビューコントローラの遷移を定義することが出来ます。

ビューコントローラの遷移を定義する

このように定義しておくと開発者はプログラムを一切書かずにビューコントローラを遷移させることが出来ます。

それではストーリーボードに定義された遷移がプログラム上でどのように動くのか見てみましょう。

以下はユーザがボタンを押してビューコントローラが遷移する時の動きを図にしたものです。

画面遷移シーケンス図

はじめにユーザがボタンを押すとイベントが通知され ViewController オブジェクトに紐づいた UIStoryboardSegueTemplate オブジェクトの perform: メソッドが呼ばれます。UIStoryboardSegueTemplate オブジェクトは UIStoryboard オブジェクトから遷移先 UIViewController オブジェクトインスタンスを取得します。次に UIStoryboardSegue オブジェクトを生成して遷移元と遷移先の UIViewController オブジェクトの情報をセットします。最後に遷移元 UIViewController オブジェクトの prepareForSegue:sender: メソッドを呼び遷移が行われることを通知します。prepareForSegue:sender: メソッドには引数として UIStoryboardSegue オブジェクトが渡ってきます。

以下は遷移時に呼ばれる UIViewController オブジェクトの prepareForSegue: sender: メソッドの例です。

@implementation ViewController

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
  NSLog(@"遷移元ビューコントローラ:%@", [segue sourceViewController]);
  NSLog(@"遷移先ビューコントローラ:%@", [segue destinationViewController]);
}

@end

このメソッドが呼ばれた後、遷移先 UIViewController オブジェクトの loadView や viewDidLoad メソッドが呼ばれ画面の表示が行われます。

UIStoryboardSegue オブジェクトは遷移時に一時的に使用されるオブジェクトです。遷移後は UIViewController オブジェクトの presentedViewController と presentingViewController プロパティで遷移先と遷移元ビューコントローラを参照することができます。

prepareForSegue:sender: メソッドはビューコントローラ同士のデータ受け渡しなどに使います。

以下はビューコントローラ遷移後のオブジェクトの状態を表した図です。

画面遷移後のオブジェクト図

遷移先ビューコントローラは遷移の情報を持っていないので UIStoryboard オブジェクトのみと関連しています。

参考

*1:UIStatusBarWindow という非公開クラスのオブジェクトです

*2:ナビゲーションバーやツールバー、タブバーで使用する UIBarItem クラスとそのサブクラスを除きます

*3:UIApplication オブジェクトはビューの1種ではありませんがウインドウを管理しているオブジェクトなのでビューっぽく表現しています

*4:ビューコントローラーを表示するというよりもビューコントローラーが管理しているビューを表示すると言った方が正しいです

2012-04-05

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

iOSタッチパネル式端末用に最適化された OS で ユーザが端末を操作しやすいように GUI の仕組みが提供されています。iOS アプリ開発では主に GUI を操作するプログラムを実装していきます。iOSGUI プログラムはイベント駆動型と言われるプログラミング方式に則ってプログラムを実装していきます。

iOS に限らず Mac OS 用のアプリ開発でも同じようにイベント駆動型のプログラムを実装しますが、 iOSタッチパネルモバイル端末用の OS という性質上 Mac OS のイベント駆動プログラムと少し違うところがあります。

この記事では iOS のイベント駆動の仕組みを「ライフサイクルイベント」と「ユーザアクションイベント」にわけて説明します。iOS のイベント駆動がどういったものなのか理解してその仕組みの上で自由にプログラムできるようになることが目的です。

イベント駆動って何?

イベント駆動とはイベントと呼ばれるアプリや端末上で起きた出来事に対して処理を行うプログラムの実行形式のことです。

イベント駆動ではイベントが発生すると OS がイベントを検知してプログラマに通知(コールバック)してくれます。イベントが発生すると、ある特定のオブジェクトメソッドが呼び出されます。一つのイベントで一つのメソッドが呼びだされる時もあれば、複数のメソッドが呼びだされる時もあります。イベント駆動型のプログラミングではイベントによって呼び出されたメソッド内にプログラムを実装していきます。

本記事では、発生したイベントによって呼び出されるメソッドを他のメソッド区別するためイベントメソッドと呼ぶことにします。

iOS ではイベント駆動の仕組みを UIKit フレームワークが提供してくれます。

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

iOS ではアプリを起動してから終了するまで UIApplication オブジェクトがイベントやアプリの状態を管理しています。UIApplication オブジェクトは始めにイベントループと呼ばれるイベントを検知するためのループ処理(for または while を使った無限ループ)を実行します。iOS はイベントが発生するとイベントキューと呼ばれるキューにイベントを溜めていきます。イベントループではイベントキューの中身を順番に取り出して実行すべきコードに振り分けていきます。

以下は UIApplication オブジェクトがイベントループを使ってどのようにイベントを処理しているのかを表した図です(アップルの公式ドキュメントより引用)。

イベントループのイメージ

イベントの種類

上の説明でイベントとはアプリや端末上で起きた出来事だと書きましたが、実際イベントにはどういったものがあるのでしょうか。

一口にイベントといっても iOS からは様々なイベントが通知されます。イベントを大きく分類すると以下の2種類に分別されます*1

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

アップルの公式ドキュメントでは特に2のことをイベントと呼んでいます。しかし一般的なイベント駆動プログラミングでは1も含めてイベントと呼ぶことが多いです。

本記事では上記1のイベントをライフサイクルイベント、2をユーザアクションイベントと呼び区別することにします。

ライフサイクルイベント

ライフサイクルイベントは端末やアプリで起こった出来事に対するイベントです。

イベントループでライフサイクルイベントが検知されると UIApplication オブジェクトが決められた順番で UIApplicationDelegate オブジェクトや UIViewController オブジェクトメソッドを呼び出します。*2

イベントメソッドの呼び出し

アプリ全体に関係するイベントメソッドは UIApplicationDelegate プロトコルに、画面に関係するイベントメソッドは UIViewController クラスに定義されています。

ライフサイクルイベントは場面に応じてたくさんのイベントメソッドが呼び出されます。すべてを紹介することは出来ませんがアプリ開発で必ず押さえておきたいイベントメソッドの流れを以下の3つにわけて説明していきます。

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

アプリを起動するとはじめに UIApplicationDelegate オブジェクトに通知されます。そのあと UIViewController オブジェクトに通知されます。画面の表示が終わった後は OS に制御が戻り次のイベントが発生するまで待機します。

またユーザがホームボタンを押してアプリを閉じると UIApplicationDelegate オブジェクトに通知されます。アプリを閉じた時は UIViewController オブジェクトには何も通知されないので注意が必要です。

ユーザがアプリを起動してからホームボタンを押して閉じるまでのライフサイクルイベントを図にすると以下のようになります。

アプリが起動してから画面が表示されるまでのイベントの流れ

アプリ起動から一時停止までのイベントで呼び出されるイベントメソッドの説明です。

画面を遷移したときに発生するライフサイクルイベント

下記の図のように画面が既に表示されている状態から新たに別の画面をモーダル*3で表示した場合、画面を管理するそれぞれの UIViewContoller オブジェクトにイベントが通知されます。

画面遷移のイメージ

画面をモーダルで表示したときのライフサイクルイベントを図にすると以下のようになります。

モーダルビューで画面遷移したときのイベントの流れ

画面遷移イベントで呼び出されるイベントメソッドの説明です。

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

端末のメモリを使いすぎるとメモリワーニングが発生します。メモリワーニングのイベントが発生するとまず UIApplicationDelegate オブジェクトの applicationDidReceiveMemoryWarning: メソッドと画面1を管理する UIViewController オブジェクトの didReceiveMemoryWarning メソッドが呼ばれます。画面構成が1画面の場合はこのまま OS に制御が移って再び待機状態になりますが画面構成が2画面以上ある場合、表示されていない側のビューを管理している UIViewController オブジェクト(画面1を管理する UIViewController オブジェクト)で画面アンロードイベントが発生します。

画面構成が2画面でメモリワーニングが発生したときのライフサイクルイベントを図にすると以下のようになります。

メモリワーニングが発生したときのイベントの流れ

メモリワーニングイベントで呼び出されるメソッドの説明です。

  • UIApplicationDelegate
    • applicationDidReceiveMemoryWarning:
      メモリワーニングが発生したときに呼ばれる。一時的にメモリに保存しているデータをディスクに保存して不要メモリを解放するなど解放出来そうなオブジェクトがあればここで解放する
  • UIViewController
    • didReceiveMemoryWarning
      メモリワーニングが発生したときに呼ばれる。UIApplicationDelegate の applicationDidReceiveMemoryWarning: と役割的に同じ画面単位で管理しているメモリを解放するとよい
    • viewWillUnload
      画面表示していない UIViewController でメモリワーニングが発生したときに呼ばれる。iOS 5 で追加されたメソッド。UIViewController の view プロパティは有効で、この時点では解放されていない。ビューが解放される前に退避したいデータを保存する。例えばテキストフィールドやテーブルに入力されたテキスト情報の保存など
    • viewDidUnload
      画面表示していない UIViewController でメモリワーニングが発生したときに呼ばれる。UIViewController の view プロパティには nil がセットされている。viewDidLoad メソッドインスタンス化したプロパティや IBOutlet プロパティの解放を行うとよい

補足すると viewWillUnload と viewDidUnload メソッドの違いは UIViewController の view プロパティnil になっているかいないかです。

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

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

UIApplicationDelegate

アプリ全体に関わるライフサイクルイベントを実装するには、はじめに UIApplicationDelegate プロトコルを実装したクラスを作成する必要があります。

UIApplicationDelegateプロトコルを実装

上記の図をプログラムで書くと以下のようになります。

@interface AppDelegate <UIApplicationDelegate>

@end

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

以下はイベントメソッドの実装例です。

@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  // アプリが起動した時の処理
  return YES;
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
  // アプリがバックグラウンド状態になる直前の処理
}

- (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];
  // ビューコントローラの遷移直前の処理
}
@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 にどのボタンが押されたか情報が渡ってきますので押されたボタンの種類によって処理を分岐することが出来ます。

まとめ

イベントはライフサイクルイベントとユーザアクションイベントにわけることが出来きます。

またさらに細かくわけると以下の用になります。

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

ライフサイクルイベントもユーザアクションイベントもイベントループで監視されています。イベントが発生することによって呼び出されるメソッドのことをイベントメソッドと呼びます。

ライフサイクルイベントは場面によって呼び出されるイベントメソッドと順番が決まっています。

ユーザアクションイベントはイベントがどこに伝達されるのか理解するのがポイントです。この仕組みがわかっていないとイベントメソッドを実装したのに呼び出されないなんてこともありえます。

参考

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

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

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

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

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

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

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

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