iPhoneアプリについて

iPhoneSDKで配信されているサンプルコードMoveMeについての解説の概要。元ページはhttp://developer.apple.com/iphone/gettingstarted/docs/creatingiphoneapps.actionです。しっかり翻訳したわけでないので、詰まったときは元のページを見てください。んで間違ってるところ見つけたら教えてください。
=================================================================================





iPhoneアプリをつくるにあたって、まずmain関数で全体の初期化を行う必要がある。そのときに記述するコードは下記のようになる。

int main(int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    int retVal = UIApplicationMain(argc, argv, nil, @"MoveMeAppDelegate");
    [pool release];
    return retVal;
}

まず、autorelease用のpoolを作成する。これはスレッドを使うときも同様である。注意しなければならないのはUIApplicationMainの部分だ。アプリケーションのDelegateを引数とするがこれを間違えると元も子もない。このデリゲーションはUIApplicationと連携をとり、状態を変化させる。主な処理は下記の様になる。

・アプリケーションウィンドウと初期UIを作成
・自作のデータエンジンに必要な初期化タスクを行う
・自作URLスキームに関連した内容を開く
・デバイスの位置の変化に反応する
・低メモリの警告を扱う
・アプリケーション終了の要求を扱う


アプリケーションウィンドウの作成
ウィンドウはUIのための描画面を提供するが、ビューオブジェクトは実際の内容を持つ。そのビューオブジェクトとはUIViewのインスタンスである。MoveMeではMyViewとPlacardViewくらすがUIを扱う。アプリケーションが立ち上がり終わるとUIApplicationがapplicationDidFinishLaunchingメッセージを委譲する。このときに行われるのは
・新しいUIWindowオブジェクトを作成。そのフレームサイズはスクリーンのサイズにあわせる。
・バックグラウンドでの動作と全画面のフレームを埋めるためにMyViewの新しいインスタンスを作成。
・Welcomeボタンとして動作させるためのPlacardViewのインスタンスを作成。配置はバックグラウンドビューの中央。
・ウィンドウの表示

MoveMeでは下記の様に書かれている

- (void)applicationDidFinishLaunching:(UIApplication *)application
{
    window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
 
    // Set up content view to fill screen
    contentView = [[MyView alloc] initWithFrame:[window bounds]];
 
    // Set up and show window
    [window addSubview:contentView];
    [window makeKeyAndVisible];
}

ここではスクリーン全体をUIScreenから得て、新しいUIwindowオブジェクトの初期化のために使う。ここではメンバー変数windowがそれで、デリゲートにもなっている。そのあとウィンドウのためのメインコンテンツを作成、可視化する。ウィンドウの表示はシステムにアプリケーションが準備できたことを知らせる。

PlacardViewクラスの生成はinitWithFrame:で行われる。これはMyViewがアプリケーションのバックグラウンドを提供し、それがPlacardViewをサブビューとして追加するからだ。この二つのビューの関係はWelcomeボタンがアプリケーションバックグラウンドのトップに表示されるといった効果だけではなくMyViewにボタンを対象としたイベント処理を許すという結果も引き出す。
initWithFrameは下記のように書かれている。

- initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame])
    {
 
        self.backgroundColor = [UIColor darkGrayColor];
 
        // Create the placard view -- it calculates its own frame based on its image
        placardView = [[PlacardView alloc] init];
        placardView.center = self.center;
        [self addSubview:placardView];
    }
    return self;
}

*UIImageViewは画像表示に用いられる。

Welcomeボタンの描画

drawRectが呼び出されるまでに、描画環境が設定され、準備完了となる。ここで行うことは自作コンテンツを描画するための描画コマンドを指定することである。PlacardViewクラスではコンテンツはバックグラウンド画像と文字列から成り立っており、そのテキストは動的に変化する。この内容を描画するためにはクラスは下記のステップを踏む。
・バックグランド画像を原点に配置。
・welcom文字列の場所の計算をし、中央に置く。
・ドローカラーを黒にする。
・文字列を黒で書く。
・ドローカラーを白にする。
・白で指定位置に文字列を書く。

実際のコードは下記のようになる。

- (void)drawRect:(CGRect)rect
{
    // Draw the placard at 0, 0
    [placardImage drawAtPoint:(CGPointMake(0.0, 0.0))];
 
    /*
     Draw the current display string.
     This could be done using a UILabel, but this serves to illustrate
     the UIKit extensions to NSString. The text is drawn center of the
     view twice - first slightly offset in black, then in white -- to give
     an embossed appearance. The size of the font and text are calculated
     in setupNextDisplayString.
     */
 
    // Find point at which to draw the string so it will be in the center of the view
    CGFloat x = self.bounds.size.width/2 - textSize.width/2;
    CGFloat y = self.bounds.size.height/2 - textSize.height/2;
    CGPoint point;
 
    // Get the font of the appropriate size
    UIFont *font = [UIFont systemFontOfSize:fontSize];
 
    [[UIColor blackColor] set];
    point = CGPointMake(x, y + 0.5);
    [currentDisplayString drawAtPoint:point
                forWidth:(self.bounds.size.width-STRING_INDENT)
                withFont:font
                fontSize:fontSize
                lineBreakMode:UILineBreakModeMiddleTruncation
                baselineAdjustment:UIBaselineAdjustmentAlignBaselines];
 
    [[UIColor whiteColor] set];
    point = CGPointMake(x, y);
    [currentDisplayString drawAtPoint:point
                forWidth:(self.bounds.size.width-STRING_INDENT)
                withFont:font
                fontSize:fontSize
                lineBreakMode:UILineBreakModeMiddleTruncation
                baselineAdjustment:UIBaselineAdjustmentAlignBaselines];
}

タッチイベントの処理

システムはタッチイベントをUIResponderのインスタンスに送る。MoveMeは二つのビュークラスを実装しているが、MyViewのみがこのイベントメッセージに反応する。このクラスは、Welcomeボタンの外側の境界と内側の境界をURResponderの下記のメソッドをオーバーライドすることによって実装する。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;

イベント処理をシンプルにするために、MoveMeでは最初の指のタッチだけを追っている。これはUIViewクラスによりマルチタッチイベントを不可にすることにより行われている。このようにすると連続的に違う指で操作しても無視する。詳しくはsetMultipleTouchEnabledのリファレンスを。さて、タッチイベント処理のためにMyViewクラスは下記のステップを踏む
・タッチがきたら、どこでそれが起きたかをチェック
・ボタンの内側で指が動いたらボタンの位置をアップデートする。
・もしも指がボタンの内部にあり、デバイスの表面を離れていくようであれば初期設定の場所に戻るアニメーションを表示

下記に示す3つの関数が一連の動作を担う

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    // We only support single touches, so get the touch from allTouches
    UITouch *touch = [[event allTouches] anyObject];
 
    // Only move the placard view if the touch was in the placard view
    if ([touch view] != placardView)
    {
        // In case of a double tap outside the placard view, update the placard's display string
        if ([touch tapCount] == 2)
        {
            [placardView setupNextDisplayString];
        }
        return;
    }
    // Animate the first touch
    CGPoint touchPoint = [self convertPoint:[touch locationInView] fromView:placardView];
    [self animateFirstTouchAtPoint:touchPoint];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [[event allTouches] anyObject];
 
    // If the touch was in the placardView, move the placardView to its location
    if ([touch view] == placardView)
    {
        CGPoint location = [touch locationInView];
        location = [self convertPoint:location fromView:placardView];
        placardView.center = location;
        return;
    }
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [[event allTouches] anyObject];
 
    // If the touch was in the placardView, bounce it back to the center
    if ([touch view] == placardView)
    {
        // Disable user interaction so subsequent touches don't interfere with animation
        self.userInteractionEnabled = NO;
        [self animatePlacardViewToCenter];
        return;
    }
}

ボタンが動いた時のアニメーション

基本的にやるステップは下記のようになる。
・アニメーションさせたいビューのbeginAnimations:context:を呼ぶ
・アニメーションプロパティの設定
・アニメーションさせたいビューのcommitAnimationsを呼ぶ

MoveMeにおけるコードは下記の通り

- (void)animateFirstTouchAtPoint:(CGPoint)touchPoint
{
#define GROW_ANIMATION_DURATION_SECONDS 0.15
 
    NSValue *touchPointValue = [[NSValue valueWithCGPoint:touchPoint] retain];
    [UIView beginAnimations:nil context:touchPointValue];
    [UIView setAnimationDuration:GROW_ANIMATION_DURATION_SECONDS];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector: @selector(growAnimationDidStop:finished:context:)];
    CGAffineTransform transform = CGAffineTransformMakeScale(1.2, 1.2);
    placardView.transform = transform;
    [UIView commitAnimations];
}
 
- (void)growAnimationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
{
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:SHRINK_ANIMATION_DURATION_SECONDS];
    placardView.transform = CGAffineTransformMakeScale(1.1, 1.1);
    [UIView commitAnimations];
}

アプリケーションの終了

アプリケーションを構築する上でInfo.plistを作成する必要がある。このXMLファイルはシステムに対してアプリケーションの情報を伝える。XCodeがデフォルトのInfo.plistを作成するので、これを拡張すればいい。
Info.plistは実行アプリの名前、ホームスクリーンに表示される画像の指定、システムに対してのアプリケーションのユニークな識別子の情報が書かれている。MoveMeはフルスクリーンアプリケーションだが、これは別の言葉を使うとステータスバーが表示されていないということになる。それを実現するためにはUIStatusBarHiddenをtrueにする。MoveMeにおけるInfo.plistは下記のようになる。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>en</string>
    <key>CFBundleDisplayName</key>
    <string>${PRODUCT_NAME}</string>
    <key>CFBundleExecutable</key>
    <string>${EXECUTABLE_NAME}</string>
    <key>CFBundleIconFile</key>
    <string></string>
    <key>CFBundleIdentifier</key>
    <string>com.yourcompany.${PRODUCT_NAME:identifier}</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>${PRODUCT_NAME}</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleSignature</key>
    <string>????</string>
    <key>CFBundleVersion</key>
    <string>1.0</string>
    <key>UIStatusBarHidden</key>
    <true/>
</dict>
</plist>