*

Objective-CでiOSアプリの第三歩 UIViewControllerの必要性を理解する

公開日: : 最終更新日:2017/02/27 iOSアプリを書く! , , ,

前回は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座標を取得して入れている。

GUIのみ配置した状態

GUIのみ配置した状態

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を導入したことで、画面の回転にも対応するようになった。これも導入によってもたらされるものということになるかもしれない。

画面の回転に対応したプログラム

画面の回転に対応したプログラム

関連記事

無理矢理表示したHello, World!

Objective-CでiOSアプリの第二歩 とにかく立ち上がればというレベルのHello, World!アプリを作成

前回はiOSアプリ作成の第一歩として、Empty Applicationを選択した際に書いてあるコー

記事を読む

アイコンは未指定なので不格好

Objective-CでiOSアプリの第一歩 新規プロジェクトで生成されるコードの解説

しけたコンソールアプリばかり作っていたけれど、ようやっとiOSプログラミングに入るとしよう。急がない

記事を読む

no image

Objective-Cでは何故.hファイルと.mファイルを作成させられるのか

前回説明したように、Objective-Cでプログラムを書く際には、別にC言語そのもののつもりで書い

記事を読む

no image

Objective-Cの第三歩 クラスとクラスメソッドを定義する

Swiftの事をひとまず置いておいてObjective-Cの入門編を書いているわけだが、前々回の"H

記事を読む

no image

Objective-Cの第六歩 プロトコルの宣言と実装

Objective-Cの第五歩では、親クラスの継承とメソッドのオーバーライドについて説明した。そこま

記事を読む

no image

Xcode 6の正式リリースまでに、Objective-Cでアプリを一つ仕上げる!

Swiftに早速触れてみたい!でもXcode 6がベータ版の間は、有料(7800円/年)のDevel

記事を読む

GaussTester実行結果

Objective-Cの第四歩 インスタンスプロパティ・メソッドを定義する

Objective-Cの第三歩では、ピュアC言語からのObjective-Cのクラス入門ということで

記事を読む

no image

iOSプログラミング入門書では、まず最初のHello, World!すら難しい

Hello, World!が第一歩 新しくプログラミング言語を習得するときに、「この言語でのH

記事を読む

no image

Objective-Cの第一歩(仕切り直し)

前回Objective-Cの第一歩として挙げたコードであるが、綺麗さっぱり忘れてほしい。というのは、

記事を読む

クラスを継承して作成

Objective-Cの第五歩 クラスの継承とメソッドのオーバーライド

前回Objective-Cの第四歩では、等差数列の和を計算するプログラムGaussTesterの作成

記事を読む

Message

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

no image
Swiftが今年後半を目処にオープンソース化!そしてSwift 2

一時期iOSとOSXの開発からは離れていたけれど、久しぶりの大きなニュ

RunSwiftスクショ
WEB上で動作するSwiftの疑似実行環境 RunSwift・SwiftStub

Swiftの実行環境はMac+Xcode。ウワサのSwift言語に興味

no image
待望のRetina iMacが登場! iOSアプリの開発が楽になる?

10月16日(日本時間17日)に行われたAppleの新製品発表会。大方

no image
Swiftの参考書 電子書籍以外にもボツボツと登場

書店の棚に並んでいるSwift本を見て、正式リリースを実感! 何しろ

no image
Swift1.0を含むXcode6のGMが公開

大体2週間の間隔を空けてbetaのヴァージョンナンバーが上がり続けてい

→もっと見る

PAGE TOP ↑