May 18, 2014

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
リファレンスだけあって、ある程度の知識が無いと理解できない情報が出てくる場合が多いが、何を検索しても何か見つかるし、何よりもサンプルコードが充実しているので、すごく助かる。

See more ...

Posted at 22:40 in PC一般 | WriteBacks (0)
WriteBacks

May 04, 2014

今更iPhoneでOpenGLES1.1を動かしてみる

iPhoneでもOpenGLを動かしてみたいと思って、知人から入手したiPhone3Gであるが、3Gというのが曲者だった。

iPhone3Gは、OpenGLアプリを作成するのにいくつかの制約がある。
・OpenGLES2.0が使えない
 OpenGLES1.1しか使えない。
・GLKitが使えない
 GLKitはiOS5以降に含まれているが、iPhone3GはiOS4.2.1までしかバージョンアップできない為。
・ステンシルバッファが使えない

それに対し、iPhone3Gの1つ次の3GSは、OpenGLES2.0が動く。これは、3GS以降のGPUは全てPowerVRのSGXシリーズであるのに対し、3GはPowerVRの"MBX Lite 3D"であることが関係しているようだ。
また、3GSにはiOS5がインストールできるので、そうすればGLKitが使える。
そして、3GSはステンシルバッファが使える。

iPhone3GはARMv6アーキテクチャーである為、Xcode 4.4.1を使用しているが、これに含まれる、iOS Applicationの"OpenGL Game"というテンプレートは、付属のiPhoneシミュレーターでは動いたので、簡単にiPhone3GでOpenGLを始められそうと思ったが、このテンプレートはGLKitを使用しているため、iPhone3Gでは動かなかった。
しかも、このテンプレートはOpenGLES2.0にも対応しているため、冗長である。

その為、GLKitに依存していないOpenGLES1.1用のテンプレートをインターネット上で探した。その結果、以下を発見した。
(1) Xcode 3.xのOpenGL ES Application テンプレート
 OpenGLES2.0も使うようになっているので、シミュレーターでは表示されるがiPhone3Gの実機では表示されないオブジェクト(物体)がある。
 但し、depth bufferは用意されていないらしい。
 また、Xcode 4では開けない。
(2) OpenGL ES 道場(1) - こじ研(携帯メディア) のGLBaseプロジェクト
 Xcode 3.xのテンプレートをベースに、depth bufferが追加されており、多少OpenGLES1.1向けに整理されている。但し、多少GLES2.0用の残骸が残っている。
(3) jlamarche/iOS-OpenGLES-Stuff · GitHub紹介記事)のSimple OpenGL ES 1.1 example
 OpenGLES1.1のサンプルプロジェクト。下記(4)のテンプレートを使用したプロジェクトと類似している。但し、Xcode 4.4.1で確認する限り、そのままではシミュレーターでも動かない。
(4) jlamarche/iOS-OpenGLES-Stuff · GitHub紹介記事)のOpenGL ES 1.1 Project Template
 OpenGLES1.1に特化したテンプレート。Quaternionを扱うためのマクロや、gluLookAt()や、アセンブラで書かれた行列積のマクロが用意されており、専門的な香りがする。
 但し、Xcode 3用のテンプレートであり、Xcode 4以降では開けない。

これらの内、(1)は使用するメリットがほとんど無いので、(2)-(4)について、実際にiPhone3Gで動作するまでに行ったことを記録する。

■(2)-(4)共通の、iPhone3Gのための手順
・"TARGETS"の"Build Settings"の"Architectures"を、"Standard (armv7)"から"armv6"に書き換える
・(推奨)同じく"Build Settings"の"LLVM GCC 4.2 - Code Generation"の所の"Optimization Level"を、"None [-O0]"以外にする
("None [-O0]"にしていると、整数型の割り算や剰余が、libgccの___divsi3や___modsi3を使うコードになることがあり、実機で"Symbol not found: ___divsi3"等のエラーになってハングアップする)

■(2) GLBaseプロジェクトの使用手順
特に何もいじらなくても、そのまま動く。
GLBaseViewController.mのdrawFrameを書き換えれば、OpenGLのコードで好きな描画をさせることができる。

なお、ディレクトリーツリーのルートの"GLBase"をrenameすれば、ディレクトリー内の"GLBase"を含むファイル名を一斉にrenameできる。

■(3) Simple OpenGL ES 1.1 exampleの使用手順
・"git clone https://github.com/jlamarche/iOS-OpenGLES-Stuff.git"等として取得
・(推奨)Build Settingsの画面で"Validate Settings"を実行し、修正を許可
・TARGETSのBuild SettingsのBuild OptionsのCompiler for C/C++/Objective-Cを"LLVM GCC 4.2"に変更(Default Compilerだと実機用のコンパイルがエラーになる)
・そのままではPart6ProjectAppDelegate.applicationDidFinishLaunchingが呼ばれないため、以下のパッチを適用、または同様に変更

--- Original/iOS-OpenGLES-Stuff/Simple OpenGL ES 1.1 example/Classes/Part6ProjectAppDelegate.m
+++ tmp/Simple OpenGL ES 1.1 example/Classes/Part6ProjectAppDelegate.m
@@ -23,6 +23,9 @@
GLViewController *theController = [[GLViewController alloc] init];
self.controller = theController;
[theController release];
+
+ self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
+ self.window.rootViewController = self.controller;

GLView *glView = [[GLView alloc] initWithFrame:rect];
[window addSubview:glView];
--- Original/iOS-OpenGLES-Stuff/Simple OpenGL ES 1.1 example/main.m
+++ tmp/Simple OpenGL ES 1.1 example/main.m
@@ -8,11 +8,12 @@


#import
+#import "Part6ProjectAppDelegate.h"

int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [NSAutoreleasePool new];
- UIApplicationMain(argc, argv, nil, nil);
+ UIApplicationMain(argc, argv, nil, NSStringFromClass([Part6ProjectAppDelegate class]));
[pool release];
return 0;
}

これで、カラフルな12面体が回転する。
GLViewController.mののdrawFrameを書き換えれば、OpenGLのコードで好きな描画をさせることができる。

■(4) OpenGL ES 1.1 Project Templateの使用手順
これは残念ながらXcode3用のテンプレートであり、Xcode4では使えない。Xcode3とXcode4のテンプレートは互換性が無い上、Xcode4用に書き換えるのも困難なのである。
参考URL:XCode4のプロジェクトテンプレートが作れない! - とっくりばー
Xcode3でこのテンプレートを使ってプロジェクトを作成するのが1つの方法だが、ファイル名や一部文字列を置換するだけなので、そういうスクリプトを作っても良いし、上記URLに載せられているRubyスクリプトでもプロジェクトファイルのコピー&置換ができる。

1. 上記URLのReplacer.rbを使う等により、テンプレートからプロジェクトを作成
2. なぜかGLView.mの場所が間違っているので、Classes/に移動する
3. (推奨)Build Settingsの画面で"Validate Settings"を実行し、修正を許可
4. (推奨)TARGETSのBuild SettingsのBuild OptionsのCompiler for C/C++/Objective-Cを"LLVM GCC 4.2"に変更(Default CompilerだとOpenGLCommon.hのアセンブラを用いたマクロがコンパイルエラーになる)

これで、GLViewController.mののdrawFrameに何かを書けば、OpenGLのコードで好きな描画をさせることができる。
何も書かないと画面が真っ暗になるので、試しに何か表示するなら、drawFrameをiOS-OpenGLES-Stuff/Simple OpenGL ES 1.1 example/Classes/GLViewController.mのものに置き換えてみても良いし、画面が暗い青になるだけで良ければ、1行目の"glColor4f"を"glClearColor"に書き換えても良い。

See more ...

Posted at 22:50 in PC一般 | WriteBacks (0)
WriteBacks