iOSのInterfaceBuilderを使ってみる

先日作成したiPhone3GのOpenGLアプリに、簡単なUIを付け加えたので、行った手順を記録する。

●Setupボタンを追加してみる

  1. MainWindow.xibを開く(*1)
    → InterfaceBuilderの画面が開く。
  2. Toolbarを貼り付ける

    → Bar Button Itemが1つ勝手に付けられる
  3. "Item"を何かに変える
    "Item"を選択し、Attributeの画面で、IdentifierをCustomから何かに変えると、いくつかの既定のアイコンや文字列が選べる。
    残念ながら「設定」っぽいアイコンが無いので、今回は"Edit"にしてみた。

    Titleを"Setup"に変えることもできたが、省略した。
  4. コールバックメソッドを宣言する
    GLViewController.hに
    - (IBAction)setup:(UIBarButtonItem *)sender;
    という宣言を追加する。(*2)(*3)
    UIBarButtonItemというクラス名は、Identity Inspectorで調べることができる。
  5. UIとメソッドを関連付ける
    MainWindow.xibを開き、"View Controller"(GLViewControllerクラスに対応している)を選択し、Connections inspectorのsetupの右の○印(outletと呼ばれる)をUIエディター上の"Edit"までドラッグ

    または、UIエディター上で"Edit"を選択し、Ctrlを押しながら"View Controller"までドラッグ
    ドラッグしたら、○印の中に黒丸が付くことを確認する。
  6. コールバックメソッドを定義(実装)する
    とりあえず、ボタンが押されたらログ出力するよう、GLViewController.mに
    - (IBAction)setup:(UIBarButtonItem *)sender
    {
        NSLog(@"GLViewController.setup called.");
    }
    を追加する。

以上でシミュレーター上で実行し、ボタンが押されたらログが出ることを確認した。

(*1)今回使用したテンプレートに、ViewControllerに対応するXIBファイル(GLViewController.xib)が無いので、今回はMainWindowにボタンを追加した。

(*2)GLViewControllerクラスに置くのが最善かは不明。 MainWindow.xibのOwnerはUIApplicationクラスなので、XxxAppDelegateクラスに置くことも考えられるが、IBのコールバックメソッドはUIViewControllerクラスに置かれることが多いので、GLViewControllerクラスを選んだ。

(*3)NIB File(XIB File)のAction Methodは全て

- (IBAction)setup:(id)sender;
この形らしい。(詳しくはHelpで"IB action methods"を検索、idはObjective-Cの基底クラス)

●スピード調整バーを追加してみる

上記の手順で作ったEditボタンの右側が淋しいので、Toolbarの余白に

こういうのを入れてみる。

  1. MainWindow.xibを開き、LabelとSliderとText Fieldを追加

    LabelはToolbarのItemにはならず、Labelを無視して左詰めされてしまうので、Flexible Space Bar Button Itemを置いてスペースを確保する。
    Labelの文字列は"Interval"にし、Sliderの値の範囲は1〜100(フレーム間隔が1ms〜100msの意味)に変更する。
  2. GLViewController.hにメンバー変数とコールバックメソッドの宣言追加
    メンバー変数は、プログラムで変化させる部品について作成する。
    @interface GLViewController : UIViewController <GLViewDelegate>
    {
        IBOutlet UITextField* textField;
        IBOutlet UISlider *slider;
    }
    - (IBAction)slided:(UISlider *)sender;
    - (IBAction)setup:(UIBarButtonItem *)sender;
    
  3. UIとメンバー変数、メソッドを関連付ける
    MainWindow.xibを開き、"View Controller"を選択し、textField, sliderのoutletから部品へドラッグして接続
    slidedのoutletをSliderにドラッグし、Value Changedを選択して接続
  4. GLViewController.mにメソッド定義追加
    - (IBAction)slided:(UISlider *)sender
    {
        /* スライダーに連動してテキストボックスの文字列更新する */
        NSString *text = [[NSString alloc]initWithFormat:@"%d ms", (int)sender.value];
        [textField setText:text];
        [text release];
        /* アニメーション速度を変える */
       [(GLView *)self.view setAnimationInterval:sender.value/1000];
    }
    
    /* 起動時にスライダーの位置を初期値に設定する */
    -(void)viewDidLoad
    {
        [super viewDidLoad];
    
        float animationIntervalInMsec = [(GLView *)self.view animationInterval] * 1000;
        [slider setValue:animationIntervalInMsec];
        [self slided:slider]; //テキストボックス初期化のため、slidedメソッドを呼び出し
    }
    

以上で、スライドバーでアニメーションのスピード調整ができることを、シミュレーターで確認した。

●別画面でSetup画面を追加してみる

3Dオブジェクトの複数のバラメーターを操作するための画面を作成してみる。

  1. SetupViewControllerクラスを追加
    XcodeのメニューバーからFile → New → File
    テンプレート選択画面では、iOSの"Objective-C class"を選択
    Option選択画面では、"With XIB for user interface"にチェック
  2. SetupViewController.xibを開いて、UI部品一式を追加
  3. SetupViewController.hにメンバー変数とメソッド宣言追加
    @interface SetupViewController : UIViewController
    {
        IBOutlet UISlider* bodyGranularitySlider;
        IBOutlet UILabel* bodyGranularityLabel;
        IBOutlet UISwitch* textureSwitch;
        IBOutlet UISlider* flipperGranularitySlider;
        IBOutlet UILabel* flipperGranularityLabel;
    }
    - (IBAction)bodyGranularitySlide:(UISlider *)sender;
    - (IBAction)textureSwitched:(UISwitch *)sender;
    - (IBAction)flipperGranularitySlide:(UISlider *)sender;
    
    - (IBAction)backToMain:(UIBarButtonItem *)sender;
    @end
    
  4. UIとメンバー変数、メソッドを関連付ける
  5. 3Dオブジェクトのバラメーター変更の為のメソッド追加
    P2Object.hに以下を追加
    + (void)reinitialize;
    
    /* クラス変数へのアクセサー */
    + (bool) useTexture;
    + (void) useTexture :(bool)value;
    + (int) bodyGranularity;
    + (void) bodyGranularity :(int)value;
    + (int) flipperGranularity;
    + (void) flipperGranularity :(int)value;
    
    P2Object.mに以下を追加
    /* クラス変数 */
    static bool useTexture = true;
    static bool drawingShadow = false;
    static int bodyGranularity = 18;
    static int flipperGranularity = 10;
    
    +(void)reinitialize
    {
        /* パラメーターの変化をオブジェクトに反映させる */
        (コードは省略)
    }
    
    + (bool) useTexture
    {
        return useTexture;
    }
    + (void) useTexture :(bool)value
    {
        useTexture = value;
    }
    + (int) bodyGranularity
    {
        return bodyGranularity;
    }
    + (void) bodyGranularity :(int)value
    {
        bodyGranularity = value;
    }
    + (int) flipperGranularity
    {
        return flipperGranularity;
    }
    + (void) flipperGranularity :(int)value
    {
        flipperGranularity = value;
    }
    
    Objective-Cには、クラスメソッドは存在するが、クラス変数という概念が存在しないので、Cのコードをそのまま使う等の理由でインスタンスを生成せずにクラスメソッドで全てを実装している場合は、このように、状態を保存する変数を.mファイル内で静的変数にして、クラスメソッドとしてアクセサーを用意するしか無さそうである。
  6. SetupViewのコールバックメソッドの定義追加
    SetupViewController.mに以下を追加する。
    #import "P2Object.h"
    
    - (IBAction)bodyGranularitySlide:(UISlider *)sender
    {
        /* スライドバーの数値を右側のラベルに反映させる */
        NSString *text = [[NSString alloc]initWithFormat:@"%d", (int)sender.value];
        [bodyGranularityLabel setText:text];
        [text release];
        /* スライドバーの数値をオブジェクトのパラメーターに反映させる */
        [P2Object bodyGranularity:sender.value];
    }
    - (IBAction)textureSwitched:(UISwitch *)sender;
    {
        /* ユーザー操作結果をオブジェクトのパラメーターに反映させる */
        [P2Object useTexture:sender.on];
    }
    - (IBAction)flipperGranularitySlide:(UISlider *)sender
    {
        /* スライドバーの数値を右側のラベルに反映させる */
        NSString *text = [[NSString alloc]initWithFormat:@"%d", (int)sender.value];
        [flipperGranularityLabel setText:text];
        [text release];
        /* スライドバーの数値をオブジェクトのパラメーターに反映させる */
        [P2Object flipperGranularity:sender.value];
    }
    
    SetupViewController.mのviewDidLoadメソッドに、以下の、スライドバーやスイッチの値をオブジェクトのパラメーターに初期化するコードを追加する。
        [bodyGranularitySlider setValue:[P2Object bodyGranularity]];
        [flipperGranularitySlider setValue:[P2Object flipperGranularity]];
        [textureSwitch setOn:[P2Object useTexture]];
        /* スライドバーの右のラベルを更新する */
        [self bodyGranularitySlide:bodyGranularitySlider];
        [self flipperGranularitySlide:flipperGranularitySlider];
    
  7. 画面遷移処理追加
    MainWindowの"Edit"ボタンが押されたらSetupViewが開くよう、GLViewController.mのsetupメソッドを以下のように変える。
    #import "SetupViewController.h"
    
    - (IBAction)setup:(UIBarButtonItem *)sender
    {
        SetupViewController *setupView = [[SetupViewController alloc] initWithNibName:@"SetupViewController" bundle:nil];
        [(GLView *)self.view stopAnimation];
        [self presentModalViewController:setupView animated:YES];
        [setupView release];
    }
    
    presentViewControllerでなくpresentModalViewControllerを使っているのは、筆者の端末がiPhone3Gである都合で、iOS5以降でないからである。
    SetupViewの"Done"ボタンが押されたらMainWindowに戻るよう、"Done"ボタンのコールバックメソッドを次のようにする。
    - (IBAction)backToMain:(id)sender;
    {
        [self dismissModalViewControllerAnimated:YES];
        [P2Object reinitialize];
    }
    
    "Edit"ボタン押下時にGLViewController.setupでアニメーションを停止しているので、SetupViewが閉じたらアニメーションを再開するコードを追加する。幸い、MainWindowに戻ったらGLViewController.viewWillAppearが呼ばれるので、そこに追加するのが良さそうである。
    -(void)viewWillAppear:(BOOL)animated
    {
        [super viewWillAppear:animated];
        [(GLView *)self.view startAnimation];
    }
    

●結果

・プロジェクトファイル一式
UsingTheTemplateAndIB.tar.gz

・追加したSetup画面の操作でオブジェクトが変化する様子
Use textureをONにすると→ 腹の白の輪郭がきれい

Use textureをOFFにすると→ Granularity(ポリゴンの細かさ)が50でも汚い

●参考文献

iPhone SDK: Interface Builder Basic Training
筆者は、このページの通りにやってみるだけで、InterfaceBuilderの使い方がほとんどわからない状態から、色々なUI部品を使うことができるようになった。とても良いチュートリアルであった。
XcodeのHelp
リファレンスだけあって、ある程度の知識が無いと理解できない情報が出てくる場合が多いが、何を検索しても何か見つかるし、何よりもサンプルコードが充実しているので、すごく助かる。

本記事執筆前の試作中、画面遷移して戻るとMainWindowがステータスバーの分ずれることが時々発生したので、その対策として、viewDidLoadにて

self.view.frame = [UIScreen mainScreen].applicationFrame;
とし(これは定番の対策のようである)、それをするとview.frameの縦サイズが常に460になるので、それに合わせてMainWindow.xibにてViewの縦サイズを460にしていたのだが、この記事の執筆の為に1から作り直すと、MainWindow.xibにてViewの縦サイズを460に変えるだけで、MainWindowがステータスバーの分ずれる現象が再現しなくなったので、このコードは省略した。