Objective-CでiOSアプリの第三歩 UIViewControllerの必要性を理解する
公開日:
:
最終更新日:2017/02/27
iOSアプリを書く! iOS, Objective-C, UIKit, Xcode
前回はUIViewControllerの存在を無視して”Hello, World!”アプリケーションを作ってしまったわけだが、警告が出てしまっていた。iOS側から、アプリケーションのwindowにrootViewCotrollerを設定しなさいと要求されたわけだ。起動後の画面に”Hello, World!”を表示するレベルでは、UIViewControllerを用意しなくても別に困らないわけだが、アプリケーションがユーザの操作を受け付け、何かしらの反応を返すようにするためには、UIKitの性質上UIViewControllerが必要となってくる。そうしたところを見ていこう。
UIButtonをビューに追加する
UIViewの子孫クラスについては、前回利用したUIWindow、それからUILabelといったようなクラスを通じて勝手が分かってきた。そこで、同じくUIViewの子孫クラスであるUIButtonを使って、”Hello, World!”アプリケーションにインタラクティブな動作をつけてみることにしよう。
前回と同じく、AppDelegate.mのapplicationメソッドにオーバーライドしていく。前回のUILabelと同じように、UIWindowのaddSubViewメソッドを使ってひとまずボタンのGUIだけ設置する。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; // Override point for customization after application launch. self.window.backgroundColor = [UIColor whiteColor]; UILabel *helloLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 100, 100)]; helloLabel.text = @"Hello, World!"; [self.window addSubview:helloLabel]; /*今回の追加部分*/ UIButton *helloButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; [helloButton setTitle:@"Show!" forState:UIControlStateNormal]; helloButton.frame = CGRectMake(100, 100, 100, 100); helloButton.center = CGPointMake(self.window.center.x, 100); [self.window addSubview:helloButton]; /*追加部分終わり*/ [self.window makeKeyAndVisible]; return YES; } |
今回新しく出てきたUIButtonクラスのインスタンス化・初期化は、”alloc”と”initWith…”というメソッドではなく、”buttonWithType”と言うメソッドにボタンのタイプを与える形で行う。通常の角丸のボタンであればUIButtonTypeRoundedRectになる。次に、ボタン内の文字についてだが、UIButtonの仕様を読んでいくと、titleLabelというプロパティがUILabel型となっており、したがって”Hello, World!”の文字を表示した時のように、UILabelのtextプロパティにアクセスすれば文字を設定・変更できるように感じる。けれども、このやり方だと反映がされない(titleLabelプロパティ自体がreadonlyだからであろうか)。代わりにsetTitleメソッドに、引数としてボタンの状態を与えて設定する。
//間違い helloButton.titleLabel.text = @"Show!"; //正解 [helloButton setTitle:@"Show!" forState:UIControlStateNormal]; |
forStateに別の状態を与えテキストを設定することで、ボタンを押し下げた状態だけテキストを変更するといったような挙動が可能になる。
一方、UILabelであればinitWithFrameで設定していたボタンの位置。これがどうなるかというと、frameプロパティに設定することになる。まあ、UILabelの場合でも実は単なるinitメソッドで生成して、後からframeプロパティでいじっても良かった。frameプロパティは親クラスのUIView由来のプロパティである。
UIView由来のプロパティで、centerというものもある。CGRectMakeに似た、CGPointMakeというグラフィックライブラリのインライン関数を使い、中心点のx,y座標を与える。x座標にはUIView由来のcenterプロパティのx座標を取得して入れている。
UIButtonにアクションを追加したい所だが…
さて、ボタンのガワだけができたので、今度は機能を追加したい。JavaScriptで考えてみるならば、onClickのようなプロパティがあり、そこに関数を登録するないし無名関数を使って挙動を書き込むといった手順が想像できるだろう。だが、Objective-Cのボタンの場合には、addTargetというメソッドを使い、オブジェクトとそのメソッドを機能の実行者として指定しなければならない。
// UIButtonの親クラスUIControlのaddTargetメソッドの書式 - (void)addTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents; |
第二引数の型として、SEL型というものが出てきた。これは@selector(メソッド名)と書くことにより、同名のメソッドを指し示す。
第三引数では、UIControlEventsクラスを受けるということがわかる。ボタンが押されたなどのイベントを表す定数。ボタンのクリック(押し下げて指を離す操作)であればUIControlEventTouchUpInsideだ。
したがって、①AppDelegate.mのapplicationメソッドのオーバーライドの中でボタンクリック時の動作を関数として宣言し、addTargetで結びつけるということは出来ない。また、②UIButtonを継承したクラスを新規ファイルとして作成し、カスタム動作ボタンとしてクラスを作成するというのも、ボタンの数が膨大になってくると冗長で管理が大変になる。
あとは、③addTargetのaction:に与えるメソッドに引数が与えられないのならば、変更対象となるUILabelのインスタンスをメソッドの実装内でどのように知るのかという問題がある。
これらの問題を一気に解決する方法として、こういったGUIとGUIのイベントに結びつけるメソッドを集中して宣言・実装するクラスを作る。それがUIViewControllerだ。UIViewControllerは、MVCのC、Controllerの部分に当たるのだが、同時にデフォルトでViewを持つことができる(プロパティviewを呼び出すと勝手に生成されている)。GUI部品をUIViewControllerのメンバ変数として宣言して、このデフォルトのViewの中にaddSubViewで置いていくという形にすると、①、addTargetには自身をオブジェクトとして与え、自身のメソッドを呼び出す形にすれば良い。②、UIButtonに機能を拡張していく必要がないため、サブクラスないしファイルが増えない。③、変更対象のUILabelは自身のメンバ変数として宣言しているので、メソッド内での参照が可能。というように解決できる。
それでは、UIViewController利用前提で作り直そう
というわけで、Xcodeの新規ファイルでUIViewControllerを継承したカスタムクラスを作成する。HelloControllerという名前にしよう。HelloControllerには、UILabelを表示したり隠したりするメソッドtoggleLabelを宣言しておく。HelloController.hは以下のようになる。
#import <UIKit/UIKit.h> @interface HelloController : UIViewController - (void)toggleLabel; @property UILabel *helloLabel; @property UIButton *helloButton; @end |
一方のHelloController.m。AppDelegate.mのapplicationメソッドで行っていた処理を、viewDidLoadというメソッドのオーバーライド部分に移す形となる。AppDelegate.mではself.windowでaddSubViewしていたところを、self.view(これは先述の通り、メンバ変数として宣言していなくてもアクセス時に自動的にviewが作成される)をメソッドの呼び出し元に変える。
#import "HelloController.h" @implementation HelloController @synthesize helloLabel = _helloLabel; @synthesize helloButton = _helloButton; - (void)viewDidLoad { [super viewDidLoad]; self.helloLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 100, 100)]; self.helloLabel.text = @"Hello, World!"; [self.view addSubview:self.helloLabel]; self.helloButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; [self.helloButton setTitle:@"Show!" forState:UIControlStateNormal]; self.helloButton.frame = CGRectMake(100, 100, 100, 100); self.helloButton.center = CGPointMake(self.view.center.x, 100); [self.helloButton addTarget:self action:@selector(toggleLabel) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:self.helloButton]; } - (void)toggleLabel{ if(self.helloLabel.hidden == YES ){ self.helloLabel.hidden = NO; } else { self.helloLabel.hidden = YES; } } @end |
このようにすると、メソッドtoggleLabel側では、表示非表示を切り替えるラベルをメンバ変数self.helloLabelとして知ることが出来る。
最後に、AppDelegate.mのapplicationメソッドの中で忘れずにhelloControllerをインスタンス化しrootViewControllerに指定しておかなければならない。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; // Override point for customization after application launch. self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible]; HelloController *helloController = [[HelloController alloc] init]; self.window.rootViewController = helloController; return YES; } |
まとめ UIViewControllerは何をもたらすか
UIViewControllerを用意しメンバ変数を用意することによって、viewの上に配置したGUI同士お互いを知ることが出来る。また、メソッドからも、GUIを知ることが出来る。したがって、お互いに関係しそうなGUIのグループをまとめて管理するというのが、UIViewControllerの役割となるだろう。対照的に、起動後の画面上に”Hello, World”を100個表示するというような、GUI同士が絡まないプログラムでは、UIViewControllerの必要性はあまりないだろう。
一方、現在画面に表示されているGUIのまとまり以外のまとまりを呼び出したいときは、また別に定義されたUIViewControllerを呼び出してくることになる。これが画面の遷移ということになるのだが、それは次回あるいは次々回にでも。
ついでに、UIViewControllerを導入したことで、画面の回転にも対応するようになった。これも導入によってもたらされるものということになるかもしれない。
関連記事
-
Objective-Cの第三歩 クラスとクラスメソッドを定義する
Swiftの事をひとまず置いておいてObjective-Cの入門編を書いているわけだが、前々回の"H
-
Objective-Cの第六歩 プロトコルの宣言と実装
Objective-Cの第五歩では、親クラスの継承とメソッドのオーバーライドについて説明した。そこま
-
iOSプログラミング入門書では、まず最初のHello, World!すら難しい
Hello, World!が第一歩 新しくプログラミング言語を習得するときに、「この言語でのH
-
Objective-CでiOSアプリの第一歩 新規プロジェクトで生成されるコードの解説
しけたコンソールアプリばかり作っていたけれど、ようやっとiOSプログラミングに入るとしよう。急がない
-
Objective-Cでは何故.hファイルと.mファイルを作成させられるのか
前回説明したように、Objective-Cでプログラムを書く際には、別にC言語そのもののつもりで書い
-
Xcode 6の正式リリースまでに、Objective-Cでアプリを一つ仕上げる!
Swiftに早速触れてみたい!でもXcode 6がベータ版の間は、有料(7800円/年)のDevel
-
Objective-CでiOSアプリの第二歩 とにかく立ち上がればというレベルのHello, World!アプリを作成
前回はiOSアプリ作成の第一歩として、Empty Applicationを選択した際に書いてあるコー
-
Objective-Cの第五歩 クラスの継承とメソッドのオーバーライド
前回Objective-Cの第四歩では、等差数列の和を計算するプログラムGaussTesterの作成
-
Objective-Cの第四歩 インスタンスプロパティ・メソッドを定義する
Objective-Cの第三歩では、ピュアC言語からのObjective-Cのクラス入門ということで
-
Objective-Cの第一歩(仕切り直し)
前回Objective-Cの第一歩として挙げたコードであるが、綺麗さっぱり忘れてほしい。というのは、