2012年1月アーカイブ

こんにちは。開発担当のICTFです。

データの表示用にUITableViewを使用していました。
その画面の一機能として不要な項目を削除するという機能を盛り込んだのですが、単純な機能ながらそこで1つ詰まってしまった箇所がありましたので、注意事項として記録しておきます。

今回はUITableViewをUIViewの上に貼付けるのではなく、UITableViewControllerを用いました。
IBで画面設計を行なったのですが、継承するクラスをUITableViewControllerに設定すると最初から必要なプロトコルをすべて乗せた状態のコードが書かれている状態なので凄く助かりますね。

前提として、
・NSMutableArrayにデータソースを読み込む。
cellForRowAtIndexPathでデータソースのインデクス順にセルを追加する。
この状態からセルの削除処理を実装するものとします。

まず画面上に「Edit」ボタンを配置し、ボタンがタップされたタイミングでUITableViewの編集モードをONにします。

[self.tableView setEditing:YES animated:YES];

setEditingにYESを設定すると、UITableViewは編集モードに入ります。
こんな感じの画面ですね。
photo001.png
セル左側の赤い丸をタップすると、セル右側に「削除」ボタンが出現します。
「削除」ボタンをタップする事で、実際の削除処理が走ります。
セルは自動で削除される訳ではなく、削除ボタンタップ時の処理を自分で記述します。

削除ボタンがタップされると、次のメソッドがコールバックされます。

// 編集モード時のコールバック

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath

{

    if (editingStyle == UITableViewCellEditingStyleDelete) {

        // 削除処理

// 該当するデータを削除する

[self deleteDataSource:indexPath.row];

// テーブルから該当セルを削除する

        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];

    }   

    else if (editingStyle == UITableViewCellEditingStyleInsert) {

        // 挿入処理

    }   

}

すごく単純なソースなのですが、この処理で躓いてしまいました。
実装時、UITableViewからセルが削除される事だけをまず確認しようとして、[self deleteDataSource:indexPath.row]の中の処理をからっぽ、つまりデータソースから該当データの削除を行なわずにいました。
この状態で実行してみると、[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]を実行するタイミングでアプリがSIGBARTエラーを出力し、落ちてしまうのです。

deleteRowsAtIndexPathsのソースが公開されている訳ではないのでコードを追う事もできず苦労してしまいましたが、試しにデータソースから該当データを削除してから実行してみるとすんなりと正常動作を確認できました。

どうやら最初に紐づけたデータソースの数とセルを削除する際の数は必ず一致していなければならないようです。

私は基本的に最小機能を実装したら確認し、次の機能を盛り込みまた確認する・・・という手順を取る事が多いのですが、今回はこの手法が足を引っ張る結果となってしまいました。
どこまでを最小機能の単位とするかというのはなかなか難しい問題ですね。
それぞれのクラス(ブラックボックスであるなら尚更)の特性を財産として溜め込んで行くしか無さそうです。

こんにちは。開発担当のICTFです。

本日、防衛司令官急募!のiPhone対応がリリースされました!
iPhone対応とは別にゲームバランスの大幅な修正も行なっています。
以前は良く言えば気軽、悪く言えば慣れてしまうと10面当たりまで簡単すぎ、且つ長時間掛かってしまい、何度もプレイする気にならないという難点がありました。
今回のバージョンアップにより、全体的にスピードアップし、さらに初期のステージからでもスリリングなプレイができるように調整されています。

また有料版のリリースと平行し、5面まで無料プレイが可能なLite版も同時リリースしました!
面白いか分からないのに、料金を払うのは抵抗がある方は是非ともまずLite版をプレイなさってみて下さい。
5面まででも十分な難度があると思いますので、プレイしごたえは十分ですよ。

ご購入はこちらから。
【完全版】

【Lite版】

公式サイトはこちら
こんにちは。開発担当のICTFです。

iAd(広告)をアプリに組み込む場合、通常は複数の画面にわたって実装しますよね。
さらにiAdの表示を行なう為にはcocos2dフレームワーク外の仕組みを使わねばなりません。
cocos2dフレームワーク外という事は、ある画面でiAdを表示している状態で次の画面に遷移した場合、フレームワーク外のiAdはそのまま残ってしまうという事です。
ある意味画面をまたいで表示し続けられるのは利点とも受け取れますが、iAdを表示したくないメインゲーム画面などに残ってしまい、大切なパラメータが隠されてしまうという不具合の原因にもなり得ます。
そこで今回は1つのCCLayerにiAdの表示から消去までの仕組みをすべて乗せてしまいたいと思います。(以下iAdLayerと呼びます)
使い方のイメージは、iAdを表示したい画面でiAdLayerをaddChildするだけです。
iAdの消去はiAdLayerが解放されるタイミングで自動的に行なわれます。

[iAdLayer.h]

// iAd表示用のレイヤ

@interface iAdLayer : CCLayer 

<ADBannerViewDelegate>

{

    ADBannerView* m_AdView;

}


@property (nonatomic, retain) ADBannerView* AdView;


@end


ヘッダファイルにはiAdを表示する為のビュークラスを追加します。

またコールバックを受け取るため、ADBannerViewDelegateプロトコルを実装する必要があります。


[iAdLayer.m]

-(id) init

{

if (self = [super init]) {

// 通知センタのオブザーバ登録

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(NotifyFromNotificationCenter:) 

name:nil object:nil];

m_AdView = nil;

m_prevOrientMode = eOrientationMode_Full;

[self createIAd];

}

return self;

}


-(void) dealloc

{

// 通知センタのオブザーバ登録を削除する

[[NSNotificationCenter defaultCenter] removeObserver:self];

[self releaseIAd];

[super dealloc];

}


init中でiAdの生成処理「createIAd」をコールし、dealloc中でiAdの解放処理「releaseIAd」をコールしています。

後はこの2つのメソッドを実装すれば、iAdLayerを貼付けるだけで広告の実装完了という仕組みを実現できます。


// 広告ビューの生成

-(void) createIAd

{

// 広告ビューが生成済みの場合、一度解放する

[self releaseIAd];

RootViewController* rootVC = [CommonFunctions getRootViewController];

// 広告ビューを初期化

self.AdView = [[ADBannerView alloc] initWithFrame:CGRectZero];

self.AdView.delegate = self;

self.isBannerVisible = YES;

UIInterfaceOrientation orientation = rootVC.interfaceOrientation;

float h = 0;

if (orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown) {

self.AdView.currentContentSizeIdentifier = ADBannerContentSizeIdentifierPortrait;

h = rootVC.view.frame.size.height;

}

else {

self.AdView.currentContentSizeIdentifier = ADBannerContentSizeIdentifierLandscape;

h = rootVC.view.frame.size.width;

}

// 画面の下部に表示

self.AdView.frame = CGRectOffset(m_AdView.frame, 0, h - m_AdView.frame.size.height);

[[[CCDirector sharedDirector] openGLView] addSubview:m_AdView];

}


// 広告ビューの解放

-(void) releaseIAd

{

if (m_AdView != nil) {

[m_AdView removeFromSuperview];

[m_AdView release];

self.AdView = nil;

}

}


createIAd内で紹介していないメソッドなどが登場しますが、これはデバイスの向きを調べ、それに合わせた広告の配置を行なう為のものです。

また、前述のinit内で通知センタへの登録を行なっていますが、これはデバイスの向きが変わった事を知る為の登録です。


// 通知センタからの通知イベント

-(void) NotifyFromNotificationCenter:(NSNotification*)notification

{

if (notification.object == nil) {

return;

}

if (notification.name == ROOTVIEWCONTROLLER_NOTIFY_MSG_ROTATE) {

// 画面回転通知

// 広告ビューの再生成

[self createIAd];

}

}


rootViewController内でデバイスの向きが変わった事を検知し、
ROOTVIEWCONTROLLER_NOTIFY_MSG_ROTATEメッセージを発行しています。

iAdLayerでそのメッセージを受け取った時に広告ビューを再生成する事で動的なデバイス回転に対応できるようになります。


少し1つの機能をCCLayerに盛り込むと汎用性が高くなるという話題から逸れてしまいましたが、CCLayerはとても便利なクラスです。何も目に見えるもの以外は実装に向いていない訳ではないと思います。


例えばDBにアクセスする為、色々な画面で用いられるSQL文(ユーザ認証当たりが該当するのではないでしょうか)の発行から処理までの一連の流れをCCLayerに実装し、使いたい場面でそのレイヤを貼付けるというのはどうでしょうか?

貼付けたタイミングでSQLが発行され、処理が終了したタイミングで自動的にremoveFromParentAndCleanupされる訳です。


1つ注意点として、CCLayerのフレームワークに従うという事は、シーンが変わるとレイヤが解放される事は基本的に避けられないという事です。

重要な処理を乗せたレイヤを動かしている場合、画面遷移をブロックする処理が必要となるでしょう。

これは今回の話に限りませんね。