2010年5月20日木曜日

三角形の描画

Open GLで三角形を描画するには、glVertexPointer関数、glColorPointer関数、glEnableClientState関数、glDrawArrays関数を使用します。


void glVertexPointer( (GLint)頂点1つあたりの配列の要素数, (GLenum)頂点配列のタイプ, 配列の開始位置, 配列へのポインタ);
最初に頂点の座標を格納した配列をOpen GLに登録します。


void glEnableClientState( GLenum );
頂点を登録したことをOpen GLに通知します。
引数には、登録した配列のタイプを指定します。


void glColorPointer( (GLint)頂点1つあたりの配列の要素数, (GLenum)頂点配列のタイプ, 配列の開始位置, 配列へのポインタ);
頂点の色を登録します。


void glDrawArrays( (GLenum)描画モード, (GLint)描画を開始する配列のインデックス, (GLint) 描画する頂点の数);
描画します。


    const GLfloat triangleVerteces[] = {
        -0.5f, -0.5f,
         0.5f, -0.5f,
         0.0f,  0.5f,
    };
    const GLubyte triangleColors[] = {
        255, 0, 0, 255,
        255, 0, 0, 255,
        255, 0, 0, 255,
    };
   
    // 頂点座標を登録
    glVertexPointer(2, GL_FLOAT, 0, triangleVerteces);
    glEnableClientState(GL_VERTEX_ARRAY);
   
    // 頂点色を登録
    glColorPointer(4, GL_UNSIGNED_BYTE, 0, triangleColors);
    glEnableClientState(GL_COLOR_ARRAY);
   
    // 描画
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 3);

上の例では、赤い三角形が1つ描画されます。
頂点座標の配列(triangleVertices)にX, Y座標のみを与え、Z座標は省きました。このため、glVertexPointer関数の第一引数(頂点1つあたりの配列の要素数)は2(X座標とY座標の2つ)としています。
また、triangleColors配列にはR, G, B, Aの色(Aはアルファ値)を保持しています。
後は、配列の型(GL_FLOAT, GL_UNSIGNED_BYTE等)と配列の開始インデックス(0)、配列アドレスを与えてglVertexPointer,glColorPointerを呼び出します。
glEnableClientState関数には、それぞれ頂点座標を与えるときにはGL_VERTEX_ARRAYを、色配列を与える時はGL_COLOR_ARRAYを与えます。
最後にglDrawArrayで配列に指定した座標と色で描画します。
第1引数の描画モードとは、頂点座標をつなげて描画を行うか(GL_TRIANGLE_STRIP)、ばらばらの三角形として描画を行うか(GL_TRIANGLES)、線分として描画を行う(GL_LINES)かの描画モードを指定します。
例えば、座標が4つ指定してある場合、GL_TRIANGLE_STRIPが指定してあると0番目〜2番目までの頂点を使って三角形を描画し、1番目〜3番目までの頂点を使って三角形を描画するというように、次の頂点と前の三角形とを順次つなげながら描画を行います。
GL_TRIANGLESが指定してあると、0番目〜2番目までの頂点を使って三角形を描画し、3番目〜5番目までの頂点を使って三角形を描画します。
GL_LINESが指定してある場合は、0〜1番目の頂点をつなぐ線分、2〜3番目をつなぐ線分というように、ばらばらの線が描画されます。

2010年5月19日水曜日

Open GLの座標

Open GLでは座標の原点が通常のアプリケーションのように左上隅ではなく、画面の中央にあります。
画面の中央を(0, 0) として、画面左側がX座標マイナス。右側がプラス。中央より上がY座標プラス。下側がマイナスという座標体系のようです。

しかもこの座標体系は、画面の縦横ピクセルが異なる(320 x 480)のに1:1の比率になっているようです。
このため例えば正方形を描画すると、縦の画素数が多いのに横の画素数と同じと見なされて縦につぶれたような長方形が描かれるようになります。

ただし、この座標体系は関数を呼び出すことで変更することが可能です。

glOrthof( Xの左端指定, Xの右端指定, Yの下端指定, Yの上端指定, Zの手前指定, Zの奥指定);


ES1Renderer/ES2RendererクラスのrenderメソッドがOpen GLを初期化するメソッドですが、この中にある座標体系を初期化する部分を以下のようにします。


glMatrixMode(GL_PROJECTION);
glLoadIdentity();

// ここで座標体系を指定する
glOrthof( -1.0f, 1.0f, -1.0f, 1.0f, 0.5f, -0.5f );




これで、画面に円を描画しても縦につぶれて描画されなくなります。

また、通常のアプリケーションのように、左上隅を原点(0, 0)、右下隅を(320, 480)としたい場合は、以下のようにします。

glOrthof( 0.0f, 320.0f, 480.0f, 0.0f, 50.0f, -50.0f );

Z軸は奥行きなのでここではあまり気にしないでください。(汗)

Open GLのファイル構成

xcodeでOpen GLテンプレートを使ってプロジェクトを作成すると、プロジェクト名Delegate.mの他に、EAGLViewのソースとヘッダ、その他、ESRenderer/ES1Renderer  /ES2Rendererのソースとヘッダがそれぞれ作成されます。
EAGLViewがOpen GLを表示する部分で、ESRendererがES1RendererとES2Rendererをスーパークラスとしてラップするクラス、ES1Rendererが 初代のiPhone 3G/iPodTouch1,2世代機で使用可能なOpen GLの描画クラス、ES2RendererがiPhone 3GS/iPodTouch3世代名以降で使用可能な描画クラスだそうです。

ES2Rendererクラスを使うと機種が限定されるみたいなので注意が必要です。

OpenGLに挑戦

iPhone OSはOpen GLをサポートしています。
Open GLを使えばハードウェアのグラフィック機能をフルに生かせます。
今回からはちょっとOpen GLにも挑戦してみようと思います。
Open GL自体がほとんど初めてのようなものなので間違いがあるかもしれませんが、温かく見守っていただければと思います。

ローカライズ

アプリケーションをローカライズするには、xcode上でResourceの所にstringsファイルを追加し、ファイル名を「Localizable.strings」にします。
ファイル名は「Localizable.strings」でないと機能しないので注意が必要です。
そして、xcode上でこのファイルの「情報を見る」を選択し、「ファイルをローカライズ可能にする」ボタンを押します。
ローカリゼーションの所には最初は英語のみ表示されているので、「ローカリゼーションを追加」 ボタンでローカライズしたい言語を追加します。
xcodeの左側のLocalizable.strings配下にEnglishと、追加した言語のファイルが追加されていますので、最後にファイルタイプを「UTF-16」にして、ファイルに以下のように記述します。


"MESSAGE" = "日本語のメッセージ";
"CANCEL" = "キャンセル";

=の左側は後からローカライズされたメッセージを読み出す時のキーになるものです。
右側がローカライズされたメッセージです。上の例では日本語(Japanese)のローカライズメッセージを設定しています。Englishの方のファイルにも同様に英語のメッセージを設定します。

"MESSAGE" = "English Message";
"CANCEL" = "Cancel";


上で設定したローカライズされたメッセージをアプリケーションから取得するには、以下のようにします。


    // リソースファイルからローカライズ用テキストを取得
    NSString* strMsg = NSLocalizedString( @"MESSAGE", nil );
    NSString* strCancel = NSLocalizedString( @"CANCEL", nil );


上記で取得されたメッセージをラベルやボタン等に設定すれば、iPhoneのシステム言語設定に従ったメッセージを表示することが可能です。



さらに、iPhoneのホーム画面に表示されるアプリケーション名もローカライズすることが可能です。
「Localizable.strings」ファイルと同じやり方で「InfoPlist.strings」ファイルを作成し、ローカライズ可能にし、ファイルタイプ(UTF16)の設定を行います。
そして、以下のように記述します。

CFBundleDisplayName = "日本語のアプリ名";

こちらも日本語(Japanese)と英語(English)両方のアプリケーション名を設定します。
これだけで、iPhoneのホーム画面に表示されるアプリケーション名がiPhoneのシステム言語設定によって変化するようになります。

2010年5月18日火曜日

設定をファイルに保存

アプリケーションの設定をファイルに保存するには、NSUserDefaultsクラスを使用します。


    // NSUserDefaultsのインスタンス取得
    NSUserDefaults* defaults = [ NSUserDefaults standardUserDefaults ];
   
    // 設定された文字列を登録
    [ defaults setObject: @"設定値" forKey: @"SET1" ];
  
    // 設定された数値を登録
    [ defaults setInteger: 100 forKey: @"SET2" ];


standardUserDefaultsメソッドでNSUserDefaultsクラスのインスタンスを生成し、setObjectやsetIntegerメソッドでそれぞれの設定値を登録します。1番目の引数が保存する値で、2番目の引数が後で読み出す時にキーとなる値です。

保存ファイルは、アプリケーションをインストールしたフォルダのLibrary/Preferencesフォルダに「com.yourcompany.アプリケーション名.plist」というファイル名で保存されます。中身はXMLファイルです。


設定ファイルを読み出す時は、アプリケーション起動後のawakeFromNibメソッド等で以下のようにします。

    // デフォルト値を作成
    NSDictionary* defDic = [ NSDictionary dictionaryWithObjectsAndKeys:
                            @"設定値デフォルト", @"SET1"
                            , [ NSNumber numberWithInt: 100 ], @"SET2"
                            , nil ];
   
    // NSUserDefaultsのインスタンス取得と読み込み
    NSUserDefaults* defaults = [ NSUserDefaults standardUserDefaults ];
   
    // デフォルト値をNSUserDefaultsに設定
    [ defaults registerDefaults:defDic ];
   
    // 読み込んだ設定値のテキストを取得
    NSString* strSet1 = [ defaults stringForKey: @"SET1" ];
   
    // 読み込んだ設定値の数値を取得
    NSInteger nSet2 = [ defaults integerForKey: @"SET2" ];


NSDictionaryというのは、アプリケーションの初回起動時には設定ファイルが存在しないので、その場合のデフォルト値を設定する為に使用しています。上記の例では、SET1というキーに設定値デフォルト、SET2には数値の100をデフォルト値として指定しています。
その後、NSUserDefaultsクラスのregisterDefaultsメソッドでNSDictionaryを設定しています。

後は、SET1のキーに該当する値は文字列なので、文字列を読み出すstringForKeyメソッド、SET2のキーに該当する値は数値なので、数値を読み出すintegerForKeyメソッドを呼出します。これで、デフォルト値か、前回保存した設定値を読み出すことができます。

2010年5月17日月曜日

ちょっとした情報を表示するUIAlertView

ちょっとした情報を表示するにはUIAlertViewを使用します。
ほとんどUIActionSheetと同じですが、もう少し詳細な情報を表示できるのが異なる点です。


- (id) initWithTitle: (NSString*) title
    message: (NSString*) message
    delegate: (id) delegate
    cancelButtonTitle: (NSString*) cancelButtonTitle
    otherButtonTitle: (NSString*) otherButtonTitle

titleにはメッセージのタイトルを指定します。無い場合はnilを指定します。
messageにはメッセージの内容を指定します。長いメッセージを指定した場合はスクロールバー等も自動的に表示してくれます。無い場合はnilを指定します。
delegateにはボタンが押された時等に呼出されるdelegateを実装しているクラスを指定します。指定したクラスにはプロトコルを宣言する必要があります。無い場合はnilを指定します。
cancelButtonTitleには、キャンセルボタンに表示するタイトルを指定します。キャンセルボタンが無い場合はnilを指定することも可能です。
otherButtonTitleには、その他のボタンに表示するタイトルを指定します。無い場合はnilを指定します。


ボタンが押された場合は、以下のdelegateが呼出されます。

- (void) alertView: (UIAlertView*) alertView clickedButtonAtIndex: (NSInteger) buttonIndex
buttonIndexには、0がキャンセルボタン。1以降はotherButtonTitleで指定したボタンのインデックスが入っています。

UIActionSheetクラス

UIActionSheetクラスは、重要なデータを消去したりする場合に、「本当に消去してよいですか?」等の様に確認の為に使用されるコントロールです。
WindowsでいうところのMessageBoxの様なものでしょうか?

UIActionSheetクラスのインスタンスの初期化には「initWithTitle」メソッドを使います。

- (id) initWithTitle: (NSString*) title
        delegate: (id < UIActionSheetDelegate>) delegate
        cancelButtonTitle: (NSString*) cancelButtonTitle
        destructiveButtonTitle: (NSString*) destructiveButtonTitle
        otherButtonTitle: (NSString*) otherButtonTitle1, otherButtonTitle2, ・・・

titleにはユーザーに問い合わせるメッセージを指定します。メッセージを表示しない時はnilを指定します。
delegateには、UIActionSheetクラスがdelegateで呼出すクラスを指定します。指定されたクラスは、UIActionSheetDelegateプロトコルを宣言する必要があります。
cancelButtonTitleには、キャンセルボタンに表示するメッセージを指定します。メッセージを表示しない時はnilを指定します。
destructiveButtonTitleには、消去をOKしたりするような時のボタンに表示するメッセージを指定します。メッセージを表示しない時はnilを指定します。
otherButtonTitleには、その他のボタンを表示する必要がある場合に指定します。カンマで区切って複数指定した場合は複数のボタンが表示されるようになります。このボタンが不要の場合はnilを指定します。


UIActionSheetコントロールのメッセージを表示する時は、UIActionSheetクラスのshowInViewメソッドを使います。

- (void) showInView: (UIView*)view
UIActionメッセージを表示する対象のUIViewクラスを指定します。


UIActionSheet上のボタンが押されたら、初期化メソッドで指定したクラスの以下のデリゲートが呼出されます。

- (void) actionSheet: (UIActionSheet*) actionSheet clickedButtonAtIndex: (NSInteger) buttonIndex
buttonIndexには、初期化メソッドで指定したボタンのインデックスが入ります。
インデックス順序は0がdestructiveButtonTitleで指定したボタン。1がotherButtonTitle1,2,3・・(指定した場合)と続き、最後のインデックスがcancelButtonTitleで指定したボタンになります。


以下がUIActionSheetの例です。

    // アクションシートを生成
    UIActionSheet* actSheet = [ [ UIActionSheet alloc ]
                                initWithTitle:@"Are you sure?"
                                delegate:self
                                cancelButtonTitle:@"CANCEL"
                                destructiveButtonTitle:@"OK"
                                otherButtonTitles:nil ];
   
    // アクションシートにタグを設定
    actSheet.tag = 1;
   
    // アクションシートを表示
    [ actSheet showInView: imageView ];
   
    // アクションシートは表示直後に解放してかまわない
    [ actSheet release ];


アクションシートは表示したらiPhone OS内部にメモリが確保されるそうなので、すぐにreleaseして構いません。

UIActionSheetを複数表示した場合、どのUIViewから呼出されたUIActionSheetか分からなくなります。この場合、どのアクションシートかを判定する為にtagプロパティに識別子を格納しておきます。

- (void) actionSheet: (UIActionSheet*) actionSheet clickedButtonAtIndex: (NSInteger) buttonIndex
{
    if ( actionSheet.tag == 1 )
    { // UIView1で生成したアクションシートから呼出されたdelegate
        if ( buttonIndex == 0 )

        { // OKボタン押下

            処理
        }
        if ( buttonIndex == 1 )
        { // キャンセルボタン押下
            処理
        }
    }
    else if ( actionSheet.tag == 2 )
    { // UIView2で生成したアクションシートから呼出されたdelegate
        if ( buttonIndex == 0 )
        { // OKボタン押下
            処理
        }
        if ( buttonIndex == 1 )
        { // キャンセルボタン押下
            処理
        }
    }
}

メニューから選択させるPickerView

メニューに複数項目を表示し、その中からユーザに選択させる為のコントロールとして、UIPickerViewがあります。
UIPickerViewクラスには複数の「行」と「列」を表示可能です。
この「行」と「列」のことを、UIPickerViewクラスではそれぞれ「Row」と「Component」と呼んでいます。

また、UIPickerViewクラスが要素の行数と列数がいくつあるのかをアプリケーションに対して問い合わせる時や、特定の行列が選択された時にアプリケーションに対して通知してくる等のイベントをデリゲートを経由して行います。


- (NSInteger) numberOfComponentsInPickerView: (UIPickerView*) view
component(列)がいくつあるのかの問い合わせです。
アプリケーションは要素の列数(component)の数を返却します。


- (CGFloat) pickerView: (UIPickerView*) view widthForComponent: (NSUInteger) comp
特定のcomponent(列)の幅が何ピクセルあるかの問い合わせです。
アプリケーションはcomponent(列)の幅をCGFloat型で返却します。


- (NSUInteger) pickerView: (UIPickerView*) view numberOfRowsInComponent:(NSUInteger) comp
特定のcomponent(列)の中に何個のRow(行)が存在するかの問い合わせです。
アプリケーションは要素の行数(row)を返却します。


- (NSString*) pickerView: (UIPickerView*) view titleForRow: (NSInteger) row forComponent: (NSInteger) component
特定の行と列に表示する文字列の問い合わせです。
アプリケーションはパラメータで指定された行と列に該当する文字列を返却します。


- (void) pickerView: (UIPickerView*) view didSelectRow: (NSInteger) row inComponent: (NSInteger) component
特定の行列が選択されたことの通知です。
アプリケーションは選択された行列を処理します。



途中で表示する行と列のデータを変更する場合は、UIPickerViewクラスのreloadComponentメソッドかreloadAllComponentsメソッドを呼びます。


- (void) reloadComponent: (NSInteger) comp
特定component(列)が変更されたことをアプリケーションからUIPickerViewクラスに通知します。
compに列番号を指定します。
このメソッドが呼ばれると、UIPickerViewクラスのインスタンスは上記のメソッドの上から4つ(numberOfComponentsInPickerView〜pickerView: titleForRow: rowForComponent)が順番に呼出されて表示が更新されます。


- (void) reloadAllComponents
全てのcomponentが変更されたことをアプリケーションからUIPickerViewクラスに通知します。
このメソッドが呼ばれると、UIPickerViewクラスのインスタンスは上記のメソッドの上から4つ (numberOfComponentsInPickerView〜pickerView: titleForRow: rowForComponent)が順番に呼出されて表示が更新されます。

テキストボックス

テキストを入力する為のコントロールにはUITextFieldがあります。これは1行しか入力できないコントロールなのでURL等を指定する時に使います。
複数の行を入力させるにはUITextViewを使います。
メモ帳のようなアプリケーションではUITextViewを使います。

UITextFieldは1行の入力なので、仮想キーボード上でEnterキーを押せば編集が完了したことが分かりますが、UITextViewのように複数行を編集させるコントロールの場合、Enterキーは改行を意味することになります。
そのため、編集の開始、終了等を明確にする為にdelegateを使ってアプリケーションに通知する仕組みが備わっています。


- (BOOL) textViewShouldBeginEditing: (UITextView*) textView
(戻り値) BOOL YES or NO
編集を開始しても良いかの問い合わせ
アプリケーション側で都合が悪ければ戻り値にNOを返却します。


- (void) textViewDidBeginEditing: (UITextView*) textView
編集が開始されたことを通知
このデリゲート上で編集終了用のボタンを表示したりします。


- (void) textViewDidChangeSelection: (UITextView*) textView
編集中のテキストが選択された時及びカーソル位置が変更された時に呼ばれます。
これが呼出された時にtextViewのselectedRangeプロパティを取得すると、NSRange型の選択範囲の情報が取得できます。NSRange型はカーソルの位置(location)と長さ(length)をNSUInteger型で格納する構造体です。
カーソル位置が変更されただけの場合はlengthには0が入ります。


- (BOOL) textView: (UITextView*) textView shouldChangeTextInRange: (NSRange) range replacementText: (NSString*) text
(戻り値)BOOL YES or NO
テキストを変更しても良いかの事前通知です。
rangeに変更対象の位置(location)と長さ(length)が入り、textに変更後の文字列が入っています。
このデリゲートは選択範囲の置き換えだけでなく、1文字入力や、文字削除の場合も呼出されます。
1文字入力の場合はrangeの長さ(length)が0になっています。また、文字削除の場合はrangeの位置(location)と長さ(length)に削除範囲が設定され、textに空の文字列が設定されることになります。
テキスト入力を許可する場合はYESを返却し、アプリケーション側で許可したくない場合はNOを返却します。


- (void) textViewDidChange: (UITextView*) textView
テキストが変更されたことの通知です。


- (BOOL) textViewShouldEndEditing: (UITextView*) textView
(戻り値)BOOL YES or NO
編集を終了しても良いかの問い合わせです。
アプリケーション側で編集を終わらせたくない場合にはNOを返却しますが、NOを返却するとフォーカスが他のコントロールに移動できないことになるので注意が必要です。


- (void) textViewDidEndEditing: (UITextView*) textView
編集が終了したことを通知します。



最後の2つのデリゲート(textViewShouldEndEditing/textViewDidEndEditing)は少し注意が必要です。

UITextViewが複数ある場合は別のUITextViewコントロールをタップすれば上記2つのデリゲートを呼出してくれますが、UITextViewコントロールが1つしか無い場合、ユーザーが編集を終わらせるきっかけがなくなってしまうことになります。(編集を終わらせるボタンが無い為)
このため、編集を終わらせる為にUIBarButtonItem等が押された時にUITextViewに編集終了を通知する必要があります。
この為のメソッドがUIResponderクラスにある「resignFirstResponder」メソッドです。
UITextViewクラスはUIResponderクラスを継承しているのでこのメソッドを呼出すことが可能です。

ボタンが押された時のイベントとしてresignFirstResponderを呼出してあげれば、UITextViewが編集の終了を検知してtextViewShouldEndEditing/textViewDidEndEditingのデリゲートを呼出してくれることになります。


- (void) endEditButtonClick
{
    //  UITextViewクラスに編集の終了を通知
    [ textView resignFirstResponder ];
}

2010年5月16日日曜日

UIViewクラスで変形した画像を元に戻す

UIViewクラスで変形した画像を元に戻すには、UIViewクラスのtransformプロパティにCGAffineTransformIdentityを設定します。
また、画像がオリジナルのものから変形されているか確認するには、CGAffineTransformIsIdentity関数を使います。


    // 画像がオリジナルか判定
    if( CGAffineTransformIsIdentity( trans ) == NO ) {
        imageView.transform = CGAffineTransformIdentity;
    }

2010年5月15日土曜日

カテゴリ

Objective-Cには「カテゴリ」という機能が存在します。
「カテゴリ」は同一クラスを複数のファイルで定義できる機能です。
この機能を利用すると例えばUIViewというクラスに後から独自にメソッドを追加することができます。
クラスの継承ではあるクラスに別のクラスをラップしてラップしたクラスにメソッドを追加することはできましたが、「カテゴリ」では、クラスそのものにメソッドを追加できるのが異なる点です。
C#には「パーシャルクラス」というものがありますが、あれとほぼ同じです。ただし、Objective-Cの方は後からインスタンスを追加できないという制限があります。

以下は、UIViewクラスに後からtestMethodを追加する例です。
ヘッダの宣言例

@interface UIView ( UIViewExtentions )
- (void) testMethod;
@end


ソースの例

@implementation UIView ( UIViewExtentions )
- (void) testMethod
{
・・・
}
@end

クラス名に続けて「( カテゴリ名 )」を付加する以外はほとんど同じですね。

UIViewクラスで裏返したりめくったり

UIViewクラスの機能を使うと、紙をめくったように見せるアニメーションや、紙を裏返したようなアニメーションが簡単に行えます。このようなアニメーションをトランジションアニメーションと言います。


+ (void) setAnimationTransition: (UIViewAnimationTransition) trans
                                   forView: (UIView*) view
                                     cache: (BOOL) cache

1番目の引数がアニメーションの種類です。

UIViewAnimationTransitionNoneアニメーションなし
UIViewAnimationTransitionFlipFromLeft左側から裏返すアニメーション
UIViewAnimationTransitionFlipFromRight右側から裏返すアニメーション
UIViewAnimationTransitionCurlUp下から上へ紙をめくるアニメーション
UIViewAnimationTransitionCurlDown上から下へ紙をめくるアニメーション

2番目の引数はアニメーションをてい要するUIViewクラスを継承するクラスのインスタンスを指定します。
3番目はアニメーション中の変更を現在のアニメーション処理に反映するかどうかのフラグです。アニメーション処理中に別のアニメーションを行いたい場合はNOにしておきますが、通常はYESにしておくと処理が軽くなるのでYESが良いと思います。


    // アニメーション定義開始
    [ UIView beginAnimations: @"TransitionAnimation" context:nil ];

    // トランジションアニメーションを設定
    [ UIView setAnimationTransition: UIViewAnimationTransitionFlipFromRight
                                           forView: imageView
                                              cache:YES ];

    // imageViewの画像を差し替える
    imageView.image = [UIImage imageNamed:@"xxxx.jpg" ];

    // アニメーションを開始
    [ UIView commitAnimations ];

UIViewクラスで拡大縮小

UIViewクラスで拡大縮小を行うには、CGAffinTransformScale関数を使用します。


    //トランスフォームを取得
    CGAffineTransform tf = imageView.transform;

     //縮小する
    tf = CGAffineTransformScale( tf, 0.5, 0.5);

     // 新しいトランスフォームを設定
    imageView.transform = tf;

imageViewをUIImageViewクラスのインスタンスとします。
UIImageViewクラスにはtransformプロパティがあるので、トランスフォームを取得後、CGAffineTransformScale関数で拡大縮小します。
1番目の引数が元となるトランスフォーム。2番目の引数は 横の拡大縮小率。3番目の引数が縦の拡大縮小率です。
最後にimageViewにトランスフォームを設定します。

UIViewクラスで回転させる

UIViewクラスを使って回転操作を行うには、CGAffineTransformMakeRotation関数を使用します。


    //  180度回転するトランスフォーメーションを生成
    CGAffineTransform tf = CGAffineTransformMakeRotation( 3.14f );

    // UIViewクラスを継承したクラスにトランスフォームを指定
    image.transform = tf;

CGAffineTransformMakeRotationのパラメータにはラジアンを指定します。
180度回転させるので3.14を指定しています。90度なら3.14 / 2とかになります。

imageというのは、UIViewクラスを継承したコントロールです。通常はイメージ等を回転させたいと思うので、ここではUIImageViewをimageインスタンスだと思ってください。

iPhoneのスリープを制御する

iPhoneのスリープを制御するには、UIApplicationクラスのidleTimerDisabledプロパティを設定します。


    // アプリケーションのインスタンスを取得
    UIApplication* app = [ UIApplication sharedApplication ];
   
    // スリープしないように設定
    app.idleTimeDisabled = YES;

スリープしないようにすると電池を消費するので気をつけましょう。

2010年5月14日金曜日

UIViewクラスの簡易アニメーション

UIViewクラスには、簡易アニメーションを行う機能が備わっています。


+ (void) setAnimationEnabled: (BOOL) enable
アニメーションを有効/無効化


+ (void) beginAnimations: (NSString*)name context: (void*)context
アニメーションの開始指示


+ (void) commitAnimations
アニメーションを終了


beginAnimationsとcommitAnimationsで囲んだUIViewクラスをスーパークラスとするコントロールがアニメーションします。beginAnimationsの後にアニメーションさせたいコントロールをアニメーション後の状態に設定し、その後、commitAnimationsをするとアニメーションが開始されます。アニメーションの途中の状態はUIViewクラスが自動的に補完してくれます。

タッチイベントの補足

タッチイベントはUIResponderクラスが行います。
UIResponderはUIViewクラスのスーパークラスであり、UIViewクラスは他の様々なユーザインターフェース(ラベルやボタン、スライダーやその他のコントロール群)のスーパークラスなのでほとんどのコントロールがUIResponderクラスの機能でタッチイベントを処理することが可能です。

タッチイベントを実装するには、UIResponderクラスのタッチイベントをオーバーライドします。

タッチイベントには以下のようなものがあります。


- (void) touchesBegan: (NSSet*) touches withEvent: (UIEvent*) event
タッチの開始


- (void) touchesMoved: (NSSet*) touches withEvent: (UIEvent*) event
タッチのドラッグ操作


- (void) touchesEnded: (NSSet*) touches withEvent: (UIEvent*) event
タッチの終了


- (void) touchesCancelled: (NSSet*) touches withEvent: (UIEvent*) event
通話等でタッチ操作がキャンセルされた



「NSSet」というのは、タッチ操作に関する情報を保持する、UITouchクラスのインスタンスを保持するクラスです。タッチ操作は複数の指で行われますので、NSSetクラスで複数のUITouchインスタンスを保持します。

UITouchクラスは、タッチ回数やタッチ時刻、タッチ対象のビュー、位置等を保持するクラスです。NSSetクラスからUITouch情報を取得するには、「anyObject」メソッドや「allObjects」を使います。

    // UITouchを全て取り出す
    NSArray* array = [ touches allObjects ];
    for ( i = 0 ; i < [ array count ] ; i++ ) {
        UITouch touch = [ array objectAtIndex: i ];
    }

    // UITouchを1つ取り出す
    UITouch touch = [ touches anyObject ];


UIEventクラスは、イベント発生時刻の他、他コントロールにあるタッチ情報も含め、全てのタッチ情報を保持しています。

2010年5月13日木曜日

XMLファイルの読み込み

XMLファイルを読み込むには、NSArrayクラスの「arrayWithContentsOfFile」メソッドを使います。


    // appフォルダをオープン
    NSBundle* bundle = [ NSBundle mainBundle ];
  
    // XMLファイルパスを取得
    NSString* strPath = [ bundle pathForResource: @"XMLFile" ofType:@"plist" ];
  
    // XMLファイルを読み込み
    NSArray* xml = [ NSArray arrayWithContentsOfFile: strPath ];


上記はアプリケーション内に予め"XMLFile.plist"というXMLファイルを作成しておいた場合の例です。
plistというのは、「Property LIST」の略で、XML形式のファイルです。
「アプリケーション.app」フォルダ内には、「アプリケーション名-info.plist」というアプリケーションの属性等を保持するファイルがありますが、このファイルはXMLファイルです。

.plistファイルは普通のテキストファイルでも作成できますが、xcodeを使えば簡単に作成できます。
xcodeの左ペインにあるResourcesを右クリック→追加→新規ファイル→テンプレート選択画面からResourceを選択、Property Listを選択します。
後はXMLの要素をポチポチ追加していけばOKです。

ステータスバーの表示/非表示

ステータスバーは画面上部に電波や電池の状態等を表示する部分ですが、ステータスバーの表示/非表示を行うには、アプリケーションのインスタンスを取得後、表示するプロパティを変更します。


    // アプリケーションのインスタンスを取得
    UIApplication* app = [ UIApplication sharedApplication ];
   
    // ステータスバーの可視状態を反転
    app.statusBarHidden = ! app.statusBarHidden;

ツールバーの表示/非表示

ツールバー(UIToolbar)は画面下部等にボタンやスライダー等を表示するコントロールですが、hiddenプロパティを使うと簡単に表示/非表示が行えます。


    // 表示状態を反転
    toolbar.hidden = !toolbar.hidden;

2010年5月12日水曜日

NSMutableArrayクラス

NSMutableArrayクラスはオブジェクトの配列を扱うクラスです。
NSArrayクラスもありますが、こちらは途中変更ができないのであまり使わないかもしれません。

NSMutableArrayクラスのメソッドには以下のようなものがあります。


arrayWithObjects:(id)オブジェクト1, (id) オブジェクト2・・・
NSMutableObjectインスタンスを指定されたオブジェクトを格納して生成します。
オブジェクト配列の最後は必ずnil(=NULL)指定をする必要があります。


count
配列の要素数を取得します。


objectAtIndex:(NSUInteger) index
指定されたインデックスのオブジェクトを取得します。


addObject: (id) オブジェクト
配列の末尾にオブジェクトを追加します。


removeObjectAtIndex: (NSUInteger) index
指定されたインデックスのオブジェクトを削除します。
後ろのオブジェクトは前につめられます。


removeAllObjects
配列の要素を全て削除します。


insertObjetAtIndex: (id) オブジェクト atIndex:(NSUInteger) index
指定されたインデックス位置にオブジェクトを挿入します。


replaceObjectAtIndex: (NSUInteger) index withObject: (id)オブジェクト
指定インデックス位置にあるオブジェクトを指定オブジェクトに置き換えます。


exchangeObjectAtIndex: (NSUInteger) idx1 withObjectAtIndex: (NSUInteger) idx2
idx1にあるオブジェクトをidx2の位置にあるオブジェクトと交換します。

乱数の生成

乱数を生成するには、C言語と同じようにrand関数を使用します。
C言語に詳しい方はおなじみかもしれませんが、乱数生成時にシード(種)を生成し、そのシードをもとにした乱数を生成させます。
通常、シードは時刻を使用しますが、iPhoneでも同じく時刻を使用するようです。


    // ランダムシードを生成
    srand( [ [ NSDate date ] timeIntervalSinceReferenceDate ] );

    // 10未満のランダム数を取得
    NSUInteger rndNum = rand() % 10;


ちなみにNSDateクラスの「timeIntervalSinceReferenceDate」メソッドは、2001年1月1日0:0:0(GMT)からの経過秒数を取得するメソッドです。

アプリケーションの初期化

アプリケーション起動時にUIApplicationクラスがNibファイルを読み込みます。
Nibファイルとは、インターフェースビルダーで配置したボタンやラベル等の属性が記録されたファイルのことです。(インターフェースビルダーはXibファイルを作成しますが、ビルドすると最終的にアプリケーションフォルダ(・・・.app)にはNibファイルができます。)

その後、Nibファイルの情報をもとにそれぞれの部品(ボタンやラベル等)のメモリ確保、初期化等を行います。この時呼出されるのがallocとinitメソッドです。

さらにその後、Nibファイルの情報をもとにしてそれぞれの部品の属性が設定されます。属性とは、例えばラベルのタイトルとか、フォント等の各種設定のことです。

最後に、それぞれのクラスのawakeFromNibメソッドが呼出されます。

このような感じでアプリケーションの初期化処理が行われます。

Windows上でC++、C#でプログラミングを行う時は、各クラスのコンストラクタとLoadedイベントで初期設定を行いますが、iPhoneではコンストラクタがinitメソッド、LoadedイベントがawakeFromNibメソッドに相当すると考えると覚えやすいかもしれません。

2010年5月11日火曜日

アプリケーション起動完了イベント

アプリケーション起動後に最初に呼ばれるイベントは、Windowベースアプリケーションの場合は「プロジェクト名AppDelegate」クラスの「didFinishLaunchingWithOptions」メソッドです。
このメソッドの定義は「UIApplication.h」に以下のように定義されていました。


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_0);

ところで、この定義は同じファイルの「@protocol UIApplicationDelegate」という部分に宣言されている「プロトコル」に定義されていました。また、他にも起動が完了したら呼ばれそうなメソッドが宣言されていました。

ということは、プロトコルを引き込む宣言をしているところがあるのかと思って「プロジェクト名AppDelegate.h」を見てみたところ、以下のように宣言されていました。


@interface ・・・AppDelegate : NSObject <UIApplicationDelegate>{



「<UIApplicationDelegate>」がアプリケーションで使うプロトコル宣言の部分です。
xcodeが自動生成するコードなのでなんとなく見過ごしてましたが、こういうところで引き込んでいたのかと妙に納得したのでした。

ちなみに、独自クラスを実装した場合は、「awakeFromNib」というメソッドが呼ばれます。
このメソッドはNSObjectで宣言されています。

画像の表示

画像の表示はUIImageViewクラスが行い、画像そのものはUIImageクラスが持ちます。
画像ファイルを表示する場合は、UIImageクラスのimageNamedメソッドを使って画像ファイルを読み込み、をれをUIImageViewクラスにセットします。


    UIImage* img = [UIImage imageNamed:@"background.jpg"];
    imageView.image = img;

サウンドの時はリソースからファイルを読み込むのにアプリケーションフォルダを開いて、URLを指定して、URLからリソースのファイルを読み込んで再生しましたが、サウンドのやり方に比べると格段にシンプルです。

リソース以外の画像ファイルを表示するには、UIImageクラスのimageWithContentsOfFileメソッドを使います。

    UIImage* img = [UIImage imageWithContentsOfFile:@"任意のパス/background.jpg"];
    imageView.image = img;

2010年5月9日日曜日

プロトコル

AVAudioPlayerクラスを使用する時に、少しプロトコルについて説明しました。
プロトコルとは、C++、C#の「仮想関数」に似たものであることを説明しました。
プロトコルの定義をxcodeでAVAudioPlayerDelegateプロトコルの「定義へジャンプ」で定義を見てみると、以下のようになっていました。


@protocol AVAudioPlayerDelegate
@optional
/* audioPlayerDidFinishPlaying:successfully: is called when a sound has finished playing. This method is NOT called if the player is stopped due to an interruption. */
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag;

/* if an error occurs while decoding it will be reported to the delegate. */
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error;

#if TARGET_OS_IPHONE
/* audioPlayerBeginInterruption: is called when the audio session has been interrupted while the player was playing. The player will have been paused. */
- (void)audioPlayerBeginInterruption:(AVAudioPlayer *)player;

/* audioPlayerEndInterruption: is called when the audio session interruption has ended and this player had been interrupted while playing.
The player can be restarted at this point. */
- (void)audioPlayerEndInterruption:(AVAudioPlayer *)player;
#endif
@end



「@protocol」〜「@end」までが「プロトコル」の定義です。
プロトコルには上記のようにメソッド宣言が並べられているだけです。
以前AVAudioPlayerクラスを利用した時に実装した関数はここで宣言されています。
ここで注目してほしい所は、「@optional」という記述です。
「@optional」がある場合、ここで宣言されたメソッドは実装してもしなくても良いという意味になります。
以前AVAudioPlayerを利用した時にメソッドの宣言は無くてもかまわないと書きましたが、プロトコル内に「@optional」が記載されていたから実装が無くても構わなかったわけです。

逆にプロトコル内で必ず実装しなければならないメソッドを宣言する時は、「@required」と記載します。

2010年5月8日土曜日

サウンドの再生位置

サウンドの再生位置はAVAudioPlayerクラスのcurrentTimeプロパティで指定できます。
サウンド全体の長さはdurationプロパティで取得できます。


    // 再生位置(10秒)
    avap.currentTime = 10.0f;

    //サウンド全体の長さ(秒)
    NSTimeInterval duration = avap.duration;

NSTimeInterval型はdouble型にtypedefされています。
ちなみにObjective-Cで「プロパティ」というのは、C#でいうところのプロパティと同じ意味で、実際にはメソッドが呼出されています。Objective-C バージョン2.0から追加された言語仕様だそうです。

サウンドの一時停止、ボリューム、ループ再生、再生の高速化

サウンドファイルの一時停止はAVAudioPlayerのpauseメソッドを使います。
再開する時は再度playメソッドを呼出します。
音量はvolumeプロパティに0.0〜1.0までの値を設定します。
ループ再生させたい場合はAVAudioPlayerのnumberOfLoopsプロパティに再生数-1の値を設定します。無限ループは0より小さい値を設定します。


    // 音量設定
    avap.volume = 0.5f;

    // 無限ループ
    avap.numberOfLoops = -1;

    // 1回のみ再生
    avap.numberOfLoops = 0;

    // 2回再生
    avap.numberOfLoops = 1;


また、再生時はAVAudioPlayerクラスのplayメソッドを呼出しますが、
playメソッドはファイルから読み込んで再生する為、再生が開始されるまで若干時間差があるようです。
このため、あらかじめファイルから読み込んでバッファに保持しておくために、prepareToPlayメソッドを呼出しておきます。prepareToPlayを呼出しておくと次にplayで再生を開始した時に即時に再生が行わるようになります。
ただし、stop時にバッファがクリアされてしまうそうなので、stop直後に再度prepareToPlayメソッドを呼んで準備しておく必要があります。


初期化処理等



    // アプリケーションフォルダをオープン
    NSBundle* bundle = [NSBundle mainBundle];
   
    // ファイルのパス生成
    NSString* path = [bundle pathForResource:soundName ofType:soundType];
   
    // URLを生成
    NSURL* url = [NSURL fileURLWithPath:path];
   
    // AVAudioPlayerインスタンス生成
    avap = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
   
    // AVAudioPlayerのdelegate設定
    avap.delegate = self;
   
    // 再生準備
    [avap prepareToPlay];



    // サウンド再生処理
    [avap play];



    // サウンド停止処理
    [avap stop];
    [avap prepareToPlay];

ちなみに、一時停止(pause)ではバッファ解放されないので、pauseメソッド呼出し後に再度prepareToPlayを呼ぶ必要はありません。

サウンドファイルの再生

サウンドファイルを再生する時は、AVFoundationライブラリのAVAudioPlayerクラスを使います。AVFoundationライブラリはiPhone OS 2.2以降で使用できるようになったようです。

xcodeにAVFoundation.frameworkを追加し、リソースにサウンドファイルを追加します。
ヘッダに@import "AVFoundation/AVAudioPlayer.h"を追加しておきます。
また、AVAudioPlayerクラスを利用するクラスを定義するヘッダに「プロトコル」を追加しておきます。


@interface クラス名 : NSObject




@end

クラス宣言の右端に「, AVAudioPlayer」を追加しました。
「プロトコル」とは、C++、C#で言うところの「仮想関数」のようなものです。
Objective-Cはクラスの多重継承を禁止しています。このため、仮想関数を使って、あるクラスから自クラスと継承クラス以外のクラスのメソッドを呼出す仕組みが備わっています。
これと似た機能がObjective-Cでは「プロトコル」というものです。
上記のようにプロトコルの指定が無い場合はワーニングが出ますが、プロトコルの指定が無くても実装したメソッドを呼出してはくれるようです。
C#等では確か仮想関数は必ず実装しなければならなかった記憶がありますが、Objective-Cの場合は必ずしもプロトコルに宣言されているメソッドを実装しなければならないわけではないようです。


    // 自アプリのバンドル(パッケージ)を開く
    NSBundle* bundle = [NSBundle mainBundle];
   
    // バンドル内の音声ファイルパスを生成
    NSString* path = [bundle pathForResource:@"alarm001" ofType:@"m4a"];
   
    // 音声ファイルパスからURLを生成
    NSURL* url = [NSURL fileURLWithPath:path];
   
    // AVAudioPlayerのインスタンス生成
    AVAudioPlayer* avap = [ [AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
   
    // 再生終了時に自クラス内のaudioPlayerDidFinishPlayingを呼出す
    avap.delegate = self;
   
    // 音声ファイルを再生
    [avap play];


NSBundleクラスは、自分のアプリケーションを開くクラスです。
以前アプリケーションの実態はただのフォルダだと書いたことがありますが、リソースファイルにサウンドファイルを追加してビルドすると、サウンドファイルが自分のアプリケーションのフォルダ(アプリケーション名.app)にコピーされます。
このサウンドファイルを自アプリケーションから開くために、フォルダをNSBundleクラスを使ってオープンします。

バンドル(フォルダ)をオープンしたら、サウンドファイルのパスをNSBundleクラスのpathForResourceメソッドを使って生成します。引数にはサウンドファイル(ファイル名)とファイルタイプ(拡張子)を指定します。

パス文字列が生成できたら、NSURLクラスのfileURLWithPathメソッドでNSURLクラスのインスタンスを生成します。

AVAudioPlayerクラスのインスタンスを生成します。初期化の引数でサウンドファイルのURLを渡します。

「avap.delegate = self;」となっている所は、再生終了時やエラー時にAVAudioPlayerクラスがイベントを発生させますが、イベントを呼出す先のオブジェクトを指定します。
上記では「self」となっているので自クラスを指定しています。ちなみにObjective-Cでは自オブジェクトのことを「self」と表記します。C++、C#では「this」だったので少し違いますね。

最後に「[avap play];」としてサウンドファイルを再生させます。

上記だけではサウンドの再生終了時やエラー時のイベントを補足できないので、以下のように各イベント時にcallbackさせるイベントも作成しておきます。


// 再生終了
- (void) audioPlayerDidFinishPlaying: (AVAudioPlayer*) avap
                        successfully: (BOOL)flag
{
    [avap release];
}

// 割り込み発生
- (void) audioPlayerBeginInterruption: (AVAudioPlayer*) avap
{
}

// 割り込み終了
- (void) audioPlayerEndInterruption: (AVAudioPlayer*) avap
{
    [avap play];
}

// エラー発生
- (void) audioPlayerDecodeErrorDidOccur: (AVAudioPlayer*) avap
                                    error:(NSError*)error
{
    [avap release];
}

「audioPlayerDidFinishPlaying」は、サウンドファイルの再生が終了した時にAVAudioPlayerオブジェクトが呼出してくれるメソッドです。メソッドは先ほど「avap.delegate = self;」としましたので、自クラス内に上記メソッドを作っておく必要があります。中身は、再生が終了してavapインスタンスが必要なくなったので「[avap release];」として解放してやります。
もちろん、再度インスタンスを使う場合はreleaseする必要はありませんが、メモリリークになるといけないので最終的には必ずreleaseしましょう。

「audioPlayerBeginInterruption」はサウンドファイルの再生中に電話等の割り込みが発生した場合に呼出されるメソッドです。再生の中断等は自動的に行われるようなのでここでは何も処理をさせていません。処理させていないので、メソッドの実装定義が無くても正常に動作します。

「audioPlayerEndInterruption」は上記割り込みが終了した時に呼出されます。
ここでは、中断されたサウンドファイルの再生を再開させています。

「audioPlayerDecodeErrorDidOccur」はサウンドファイルの再生中にデコードエラー等が発生した時に呼出されます。ここでもインスタンスが不要になったのでreleaseさせます。

2010年5月7日金曜日

和暦の出力

日時を出力する時にロケール指定で「日本(ja_JP)」を指定すると、「2010年5月7日金曜日」等と、一応日本語で出力されるようになりますが、これはあくまでも西暦での出力であり、日本独自の表記ではありません。
これを「平成xx年xx月xx日x曜日」等と日本独自の表記で出力するようにするには、カレンダークラスに日本のカレンダーを指定します。


    // 現在日時(世界標準時)を取得
    NSDate* dt = [NSDate date];
   
    // 時刻書式指定子を設定
    NSDateFormatter* form = [[NSDateFormatter alloc] init];
    [form setDateStyle:NSDateFormatterFullStyle];
    [form setTimeStyle:NSDateFormatterNoStyle];
   
    // ロケールを設定
    NSLocale* loc = [[NSLocale alloc] initWithLocaleIdentifier:@"ja_JP"];
    [form setLocale:loc];
   
    // カレンダーを指定
    NSCalendar* cal = [[NSCalendar alloc] initWithCalendarIdentifier: NSJapaneseCalendar];
    [form setCalendar: cal];
   
    // 和暦を出力するように書式指定
    [form setDateFormat:@"GGyy年MM月dd日EEEE"];
   
    NSLog([form stringFromDate:dt]);

    [form release];
    [cal release];

書式付き日時のロケール指定

書式付き日時のロケールを指定しない場合、デフォルトでは英語表記で出力されるようです。
これを日本語の日時で出力するように指定するには、NSDateFormatterクラスのsetLocaleメソッドを使います。


    // 現在日時(世界標準時)を取得
    NSDate* dt = [NSDate date];
   
    // 時刻書式指定子を設定
    NSDateFormatter* form = [[NSDateFormatter alloc] init];
    [form setDateStyle:NSDateFormatterFullStyle];
    [form setTimeStyle:NSDateFormatterNoStyle];
   
    // ロケールを設定
    NSLocale* loc = [[NSLocale alloc] initWithLocaleIdentifier:@"ja_JP"];
    [form setLocale:loc];

    NSLog([form stringFromDate:dt]);

ロケール指定が無い場合、日時に「Friday, May 7, 2010」等と出力されますが、
日本のロケールを指定したので、「2010年5月7日金曜日」等と出力されるようになりました。

ロケール指定子には、以下のものが使用できるようです。

ja_JP日本語
en_US英語(アメリカ)
en_GB英語(イギリス)
fr_FRフランス語
de_DEドイツ語
it_ITイタリア語
es_ESスペイン語
zh_Hans_CN中国語
ko_KR韓国語

書式指定日時のタイムゾーン設定

書式指定で日時を出力する場合、デフォルトでシステムに設定されたタイムゾーンで日時が出力されますが、タイムゾーンを指定することで世界中の日時を出力することができるようです。


    // 現在日時(世界標準時)を取得
    NSDate* dt = [NSDate date];
   
    // 時刻書式Formatterを生成
   NSDateFormatter* form = [[NSDateFormatter alloc] init];
   
    // 時刻書式指定子を設定
    [form setDateStyle:NSDateFormatterNoStyle];
    [form setTimeStyle:NSDateFormatterMediumStyle];
   
    // タイムゾーンを設定
    NSTimeZone* tz = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
    [form setTimeZone:tz];
   
    NSLog([form stringFromDate:dt]);


NSLogでGMT時刻が出力されます。

2010年5月6日木曜日

タイマー

特定秒数後に特定の処理を行わせたい場合は、NSTimerクラスを使います。
NSTimerのscheduledTimerWithTimeIntervalメソッドのパラメータに、秒数、callbackするオブジェクト、callbackするメソッド、メソッドに渡すオブジェクト、繰り返し指示を指定します。


    NSString* str = @"TEST";
    timerClock = [NSTimer scheduledTimerWithTimeInterval: 1.0f        // 間隔1秒
                                                  target: self        // 呼び先オブジェクト
                                                selector: @selector( callbackMethod: ) // 呼び先メソッド
                                                userInfo: str        // 参照オブジェクト
                                                 repeats: YES ];    // 繰り返しあり



- (void) callbackMethod: (NSTimer*)timerClock
{
    NSLog([timerClock userInfo]);
}

上記例では、自分のクラスのcallbackMethodが1秒間隔で呼出されます。
@selector(・・・)は、関数ポインタを指し示す時に用いるObjective-Cの型です。
繰り返し指示がYESなので、何もしなければ延々と1秒間隔で呼出されることになります。
callbackされたメソッドのパラメータ(timerClock)には呼び元のNSTimerのインスタンスが格納されます。
[timerClock userInfo]は、呼び元で指定したオブジェクト(str)を取得するメソッド(userInfo)を取得する処理です。NSLogでは文字列"TEST"が出力されます。
ちなみにNSLogはコンソールにログを出力する関数です。
タイマーを止めたい時は、 invalidateメソッドを呼出します。

日時の書式付き出力

日時(NSDate)はNSStringクラスの書式指定(stringWithFormat)を使って出力することもできますが、曜日や元号等を出力するときにちょっと工夫が必要です。
また、NSDateは以前書いた通り世界で一意の値であるグレゴリオ暦(世界標準時)を格納するので、NSStringで表示する際はローカル時刻に変換 する必要があります。

そこで、NSDateFormatterクラスを使えば、より簡単に書式指定を行うことができます。


    // 現在日時を取得
    NSDate* dt = [NSDate date];

    // NSDateFormatterのインスタンス生成
    NSDateFormatter* form = [[NSDateFormatter alloc] init];
   
    // NSDateFormatterに書式指定を行う
    [form setDateFormat:@"G yyyy/MM/dd(EEE) K:mm:ss"];
   
    // 書式指定に従って文字出力
    NSString* str = [form stringFromDate:dt];
   
    NSLog(str);
   
    // NSDateFormatterはallocで確保したので明示的に解放する
    [form release];

strには”AD 2010/05/06(Thu) 10:07:50”と格納されます。ちゃんと曜日も取得できてますね。
書式指定できるものには、以下のようなものがあります。

G時代(AD等)
yy年の下2桁
yyyy年(4桁)
MM月(1〜12)
MMM月(Jan)
MMMM月(Janualy)
dd日(2桁)
d日(1〜2桁)
EEE曜日(Sun等)
EEEE曜日(Sunday等)
aaAM/PM
H時(0〜23)
K時(0〜11)
m分(1〜2桁)
mm分(2桁)
s秒(1〜2桁)
ss秒(2桁)
Sミリ秒

NSDateとNSDateComponentsの相互変換

NSDateとNSDateComponentsの相互変換は、以前にも書いたNSCalendarクラスを使って行います。
NSDate→NSDateComponents変換はNSCalendarクラスのcomponentsメソッド。
NSDateComponents→NSDate変換はdateFromComponentsメソッドを使います。

    // NSDateComponentsのインスタンス生成
    NSDateComponents* cmp = [[NSDateComponents alloc] init];
   
    // NSDateComponentsに日付を設定
    [cmp setYear:2010];
    [cmp setMonth:05];
    [cmp setDay:01];
   
    // NSCalendarのインスタンス生成
    NSCalendar* cal = [NSCalendar currentCalendar];
   
    // NSCalendarを使ってNSDateComponentsをNSDateに変換
    NSDate* dt = [cal dateFromComponents:cmp];
    NSLog(@"%@", dt);
   
    // 取得したい情報をカレンダーのフラグに指定
    NSUInteger flg = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit
    | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit | NSWeekdayCalendarUnit;

    // NSDate→NSDateComponents変換
    NSDateComponents* cmp = [cal components:flg fromDate:dt];
    NSString* dbgStr = [NSString stringWithFormat:@"%4d/%2d/%2d(%d)", [cmp year], [cmp month], [cmp day], [cmp weekday]];
    NSLog(@"%s", dbgStr);

    // allocで確保したものはreleaseで解放する
    [cmp release];


2010年5月5日水曜日

日時の計算や比較

現在日時に特定の秒数加算した時刻を取得するには、以下のようにします。


    NSDate* dt = [NSDate dateWithTimeIntervalSinceNow:60.0f];

上記では、現在時刻に60秒加算した日時を取得しています。
パラメータはdouble型なので「60.0f」としています。

dateWithTimeIntervalSinceNowは静的メソッドなので上のような書き方ができますが、NSDateクラスの値は途中変更ができないので、特定日時から計算するには、以下のようにNSDateクラスのインスタンスを複数作成する必要があります。


    NSDate* dt1 = [NSDate dateWithTimeIntervalSinceNow:60.0f];
    NSDate* dt = [dt1 addTimeInterval:120.0f];


上記は現在時刻に60秒加算した時刻をdt1に格納し、dt1に120秒加算した値をdtに格納しているので、結果、dtには現在時刻より3分進んだ時刻が格納されます。


日時を比較するには、NSDateクラスのcompareメソッドを使います。


    NSComparisonResult result = [dt1 compare:dt];
    switch(result)
    {
    case NSOrderedAscending:
        // dt is bigger than dt1
        break;
    case NSOrderedDescending:
        // dt is smaller than dt1
        break;
    case NSOrderedSame:
        // dt is same with dt1
        break;
    }

結果はNSComparisonResultに返ってきます。

また、日時が特定の日時と同じかどうかだけ知りたい場合は、isEqualToDateメソッドを使います。

    BOOL result = [dt isEqualToDate:dt1];
    if(result == YES)
    {
        // same
    }
    else
    {
        // different
    }

isEqualToDateメソッドの結果はBOOL型で返却されます。
BOOL型にはC、C++、C#ではtrue / falseが格納されますが、Objective-Cの場合はYES / NOです。

日付を扱うクラス

日付を扱うクラスについて調べてみました。

iPhoneでは日付を扱う時に「NSDate」というものを使うようです。

「NSDate」はグレゴリオ暦2001/1/1 0:0:0からの経過秒数を扱うクラスだそうです。

世界標準時のみ扱うので、日本国内の日付を扱うには、「NSDateComponents」というクラスを使います。

こちらはグレゴリオ暦ではなく、「年/月/日 時:分:秒 曜日」を扱います。



そして、「NSDate」(世界標準時)から「NSDateComponents」(世界標準時及びローカル日付)へ変換するクラスとして、「NSCalendar」クラスを使います。

「NSCalendar」クラスはタイムゾーンを考慮して日付変換を行ってくれますが、このタイムゾーンを内部でインスタンスとして持っているそうです。

タイムゾーンは「NSTimeZone」というクラス名です。



「NSCalendar」はデフォルトではiPhone OSのシステム設定で設定されているタイムゾーンを使用するようです。


    // 現在日時(世界標準時)を取得
    NSDate* dt = [NSDate date];
   
    // システム情報で設定されているタイムゾーンでカレンダーを取得
    NSCalendar* cal = [NSCalendar currentCalendar];
       
    // 取得したい情報をカレンダーのフラグに指定
    NSUInteger flg = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit
    | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit | NSWeekdayCalendarUnit;
   
    // ローカル日時(システム情報設定のタイムゾーン)を取得
    NSDateComponents* localTime = [cal components:flg fromDate:dt];




OSに設定されたタイムゾーンとは別のタイムゾーンの時刻を設定するには、以下のようにNSCalendarに取得したいタイムゾーンを指定します。


    // 世界標準時のタイムゾーンを取得
    NSTimeZone* worldZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
   
    // カレンダーにタイムゾーンを設定
    [cal setTimeZone:worldZone];
   
    // 世界標準時を取得
    NSDateComponents* worldTime = [cal components:flg fromDate:dt];


2010年5月4日火曜日

iPhoneのアプリケーションの実態

iPhoneのアプリケーションはMac OSと同じく、「xxxxx.app」という名前が付けられます。
Finderで「xxxxx.app」という名前をダブルクリックすると、アプリケーションが起動しますが、この「xxxxx.app」というものの実態は、単なるディレクトリみたいです。
Max OSを長年使われてきた方には常識なのかもしれませんが、Windowsを長年使ってきた私としては結構な驚きでした。Windowsでディレクトリをクリックするとディレクトリがオープンされるだけでしたので。。

で、この「xxxxx.app」のディレクトリ(アプリケーション?)の中を覗いてみると、複数のファイルが格納されていることが分かります。
まず、ビルドした結果の実行形式ファイル、以前ご紹介したplistファイル、xibファイルを変換した後のnibファイル、アプリケーション実行に必要なリソース(絵等)ファイル。そして
pkginfoという謎のファイルが。こちらはエディタで開くとAPPL???という文字が格納されているだけでした。

xibファイル

xibファイルは、iPhoneの開発ツールの1つであるInterface Builderで使用されるようです。(Xcode Interface Builderの略)
例えば、Interface Builderでボタンを追加したとすると、ボタンの属性や位置、表示文字や押された時のイベント関連付け等がこのxibファイルに保存されるみたいです。

試しにテキストエディタで開いてみたところ、普通のXMLファイルでした。
xibファイルはビルドすると最終的にnibファイルに変換されます。
nibファイルとは、Nextstep Interface Builderの略称みたいです。
ここでもNextStepの影響が色濃く残っていますね。

ちなみにnibファイルをテキストエディタで開いたところ、バイナリファイルの様でちゃんと見れませんでした。残念。でもまぁ、中身の意味する所はxibファイルとほぼ同じみたいです。

plistファイル

iPhoneのアプリ開発では、plistファイルでアプリケーションの属性等を指定するようです。
例えば、プロジェクト名がtestだとすると、iPhoneの開発ツールであるxcodeでプロジェクトを新規作成すると、「test-info.plist」というファイルができます。
この「test-info.plist」ファイルはテキストファイルのようで、テキストエディタで開くと普通のXMLファイルを見ることができます。
このファイルでアプリケーションの言語属性や、OSタイプ、実行ファイル名、スタートエントリポイント等を指定できるようです。

OSにアプリケーションの実行指示を行うと、OSが一番最初にこのplistファイルを読み込み、実行ファイル名、エントリポイント等の情報を取得、メモリに展開後、スタートエントリポイントに制御を移行する、というOSの仕様みたいですね。
この仕様は多分MAC OSも同じだと思います。

メソッド呼出し

以下の様にプロトタイプ宣言されたクラスがあるとします。

@interface testClass : NSObject {
}
- (void) testMethod : (int) param1 param2 : (int)p2;
@end

ここでちょっと注意点があります。
ほぼ全てのクラスは「NSObject」クラスを継承させます。
「NSObject」クラスは、全てのクラスのスーパークラスとなるものです。
「NSObject」の「NS」とは、UNIXの「Next Step」の「NS」だそうです。
Objective-CはUNIXのNext Stepから派生した開発言語です。
ちなみに、クラスのメソッド宣言はクラス宣言({}(ブレス))の外側に宣言することも注意点でしょうか。

このクラスをインスタンス生成するには、以下の様に書きます。

testClass* tc = [[ testClass alloc] init];

また、インスタンスのメソッドを呼出すには以下の様に書きます。

[tc testMethod:1 param2:2];

C、C++、C#と比べるとかなり違いますね。

メソッドを呼び出す時は、大括弧([])で囲みます。
C、C++、C#言語では「->」または「.」を使っていたのでかなり違和感がありますが、これもUNIXのNext Stepから来ているものです。

「[testClass alloc]」でインスタンス生成用のメモリ割当を行うメソッドを呼出し、続く「init」で初期化メソッドを呼出します。
「alloc」も「init」もスーパークラスである「NSObject」内で定義されています。
 その結果を「testClass* tc = ・・・」でクラスのインスタンスポインタ「tc」に取得します。
後は、取得したインスタンスポインタ「tc」からtestMethodを呼出すために、「[tc testMethod:1 param2:2]」とします。
「testMethod:1 param2:2」の「:1」の部分は、1番目の引数を指定している部分で、「 param2:2」が2番目の引数を指定している部分です。
1番目の引数はメソッド名の直後に「:(コロン)」を書いて指定し、2番目の引数は「引数の説明:」に続けて指定しています。

メソッド呼出しを「[]大括弧」で囲む所とか、引数の指定方法が大分違うので最初は戸惑いそうですが、慣れると大丈夫そうかな?

2010年5月3日月曜日

メソッドに戻り値とパラメータがある場合

メソッドに戻り値、パラメータがある時は、ヘッダーファイル(*.h)とソースファイル(*.m)に以下の様に書きます。

------- ヘッダー(*.h) ▼ ----------
- (int) test_method : (int) param1;
------- ヘッダー(*.h) ▲ ----------



------- ソース(*.m) ▼ ----------

- (int) test_method : (int) param1
{
    処理
    return(0);
}

------- ソース(*.m) ▲ ----------


戻り値は他の言語と同じような書き方なので分かりやすいと思います。
ちょっと分かりにくいのが引数がある時の書き方で、上の例では引数にint型を持っています。
C、C++、C#言語では「:」はクラスの継承元を示すものでしたが、
Objective-Cでは、引数を表すものとなっています。

次に、引数が2つある場合はどうなるでしょうか?

------- ヘッダー(*.h) ▼ ----------

- (int) test_method : (int) param1 number:(int)nNo;

------- ヘッダー(*.h) ▲ ----------




------- ソース(*.m) ▼ ----------

- (int) test_method : (int) param1 number:(int)nNo
{
    int a = param1;
    int b = nNo;
    return(0);

}
------- ソース(*.m) ▲ ----------


行端に「 number:(int)nNo」というものが追加されました。
「number」は、2番目の引数の説明です。
Objective-Cでは、このように「引数の説明」を書きます。
続けてある「:(int)nNo」が、処理内で実際に使う引数の型と名前です。
「引数の説明」の「number」は何に使うの?と思われそうですが、こちらは「呼び出し側」で使うことになります。

引数が3つ以上ある場合は、2番目の引数と同じ様に「引数の説明:(引数の型)引数名」という風に書いていきます。

 (例)
- (int) test_method : (int) param1 number:(int)nNo param3:(int)p3

メソッドコールは別の機会に。

メソッド宣言と実装

メソッドのプロトタイプ宣言は、ヘッダーファイルに以下の様に書きます。

-(void) test_method;


また、クラスにメソッドを実装するには、ソースファイルに以下の様に書きます。
- (void) test_method
{
    処理
}

C、C++、C#言語では見慣れない記述として、ヘッダー、ソースファイルの1番最初の桁に「-」というものがあります。
「-」とは、端的に言うと動的なメソッドということを示すものです。
staticなメソッドの時はここが「+」になります。

ヘッダとソース

iPhone開発において(Objective-Cと言った方が良いのかな?)、ヘッダファイルは拡張子「*.h」。
ソースファイルは拡張子「*.m」となります。

ヘッダファイルの記述形式はC言語とはかなり異なり、以下の様に各クラスを@interface〜@endで囲むような書き方をします。
@interface test_Class : NSObject<UIApplicationDelegate>
{
   
UIWindow* window;

}
@end

test_Classがクラス名。
NSObjectはスーパークラス。
<UIApplicationDelegate>は「プロトコル」と言って、メソッドのプロトタイプ宣言の集合体のことです。
Objective-Cは多重継承が禁止されているので、実装が必要なメソッドは「プロトコル」でメソッド宣言の集合をまとめて指定します。(プロトコルは複数指定可。)
C#で言うところの「抽象クラス&抽象メソッド」だったかな?確かC#では実装必須で、実装していない場合はエラーになったと思うが、Objective-Cでは実装してなくてもワーニングが出るだけのようだ。まぁ、実装しといた方が良いと思うけど。

ヘッダのインクルードは#includeではなく、「@import」を使います。
#importはC、C++言語の「#include」と違って、多重インクルードを自動的に防いでくれます。

ソースファイルの記述形式は、各クラスを@implementation〜@endまで囲んで記述する形式になります。

@implementation test_Class
 メソッド
@end
クラスが継承してたとしても、クラス名に続いて「:継承元クラス」等と書く必要はありません。
継承元はヘッダファイルの方に書いてあるから。
というか、「:〜」はObjective-Cでは別の意味になるので書いてはいけない。
詳しくは別の機会に書いてみようと思います。

Objective-Cに挑戦

今日からObjective-Cに挑戦します。
今まで書籍等で勉強してはいたのですが、実際に作ってみるのは初めてです。
温かく見守っていただければと思います。