iPhone で今更 HelloWorld(InterfaceBuilder使わずに実装)
Xcode についてくる InterfaceBuilder についての感想の遷移。
①初めて触ったとき → 超意味不。本気で使いにくい。
②すべてプログラムで実装した後 → InterfaceBuilder ってそういうことやってたのね。だがコードで書く。
③今 → とりあえず InterfaceBuilder で見た目だけ作って、動いてからプログラムに置き換える。
順を追って振り返る。
始めに、様々な入門書で InterfaceBuilder を使ったコーディング入門をしている。これは、本に限ったことではなく、Web でもそう。
全く何も知らずに「はいできたー」と、のたまうならそれでも良いかもしれない。しかし本職でそれは致命的だと思う。カスタマイズもできなければ、動的な画面生成もできないからだ。
そして、見事にそれにハマった。InterfaceBuilder で作ったものの実体、どうやって動くのか、それが入門で説明しないからだ。
ごもっとも。そんなの知る必要があるのはこだわりを持つか本職かだけだ。
そこで最初にとった行動は、InterfaceBuilder を使わずにコーディングしてみるということだ。
最初に誰もが通る HelloWorld を僕はこう作った。
- テンプレートを全部試してみる。
- 共通部分を比較してみる。
- 最も基本となるテンプレートからプロジェクトを作成する。
上記の手順で行き着いたのが Window-based Application だった。
main があって、AppDelegate を呼び出して、window と呼ばれる画面が存在するこの構成だ。
構成的には、こんな関係っぽい*1。
main -> AppDelegate <-| |- window ^ |壁| ^ ViewController <-| |- UIView
UIViewController には、サブコントローラほ保持できるし、それに合わせて、UIView も下階層で保持できる。
個人的にはあまり深くはしたくないかな、、、、
Window-based Application では、main,AppDelegate,window のセットしかない。
main -> AppDelegate <-| |- window
なので、ViewController は手前味噌で実装した(今はまだしも 09/06 位は殆ど英語しかなかった)。
ヘッダ。
#import <UIKit/UIKit.h> @interface HelloViewController : UIViewController { UILabel* labelHello; UIButton* buttonPush; } - (IBAction)pushHello; @end
本体(参照カウントがわかりやすいように手動で数えてます)。
#import "HelloViewController.h" @implementation HelloViewController - (id)init { if (self = [super init]) { // MVC の M 層なんかはこの辺で初期化しとく。 // 今回はなし。 } return self; } // nib(XIB) 抜きでインスタンス作るときのイベント。 // MFC の OnCreate とかそのへん。 - (void)loadView { // alloc で参照カウント 1 [self addView:]で参照カウント 2 self.view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]; // 参照カウントを 1 にしておく。 [self.view release]; // alloc 時点で参照カウント 1 labelHello = [[UILabel alloc] init]; [labelHello setFrame:CGRectMake(20, 100, 300, 21)]; [labelHello setText:@"Push button."]; // addSubview で参照カウント 2 [self.view addSubview:labelHello]; // buttonWithType でボタンを作ると、autorelease なので、 // このメソッドを抜けると参照カウントが 1 減る。 // 変数に保持させたいので、retain で参照カウントを増やしておく(参照 2)。 buttonPush = [[UIButton buttonWithType:UIButtonTypeRoundedRect] retain]; [buttonPush setFrame:CGRectMake(100, 140, 80, 40)]; [buttonPush setTitle:@"hello" forState:UIControlStateNormal]; // イベントを設定しておく // ボタンを押されたら、自インスタンスの pushHello を起動。 [buttonPush addTarget:self action:@selector(pushHello) forControlEvents:UIControlEventTouchUpInside]; // addSubview(参照 3) [self.view addSubview:buttonPush]; // メソッド終了(buttonPush の参照が 2 になる) } // ボタン押されたときのイベント。 - (IBAction)pushHello { [labelHello setText:@"Hello world!"]; } - (void)viewDidUnload { } - (void)dealloc { // 両方参照カウント 1 へ [buttonPush release]; [labelHello release]; // self.view は消えて、子に持ってる連中の参照カウントが全部 1 減る。 [super dealloc]; } @end
で、これを AppDelegate に読ませれば起動できる。
@interface HelloWorldAppDelegate : NSObject <UIApplicationDelegate> { UIWindow *window; UIViewController* controller; // 追加 } @property (nonatomic, retain) IBOutlet UIWindow *window; @end
- (void)applicationDidFinishLaunching:(UIApplication *)application { // Override point for customization after application launch controller = [[HelloViewController alloc] init]; [window addSubview:controller.view]; [window makeKeyAndVisible]; } - (void)dealloc { [controller.view removeFromSuperview]; [controller release]; [window release]; [super dealloc]; }
で、InterfaceBuilder 使うコードと比較して何が違うかというと、
- UIViewController 初期化時に、initWithNibName を使用しない(nib/xib ファイルを使わないから)。
- loadView で画面を生成してる(InterfaceBuilder が本来サポートしてる部分)
- dealloc で参照カウント制御してる(autorelease しとけば無視もできる)
要するに loadView の所でコード書かなくて済むだけ。
よほど複雑な画面作るのでなければ、コードで書いた方が実行速度早いしおすすめ〜。
とはいえ、見た目に時間かけるより前に、ロジックを確認したい場合も確かにある。なので、最近はこんな使い方をする。
- InterfaceBuilder で画面を適当にでっちあげる
- 内部処理がうまい事動いてるのだけ確認する
- loadView メソッドを作成して、initWithNibName を init に移植する。
もっといい使い方があって、スパゲティにならない方法があれば是非。
*1:実際にはこれも把握するのに2−3日苦しんだのだが