Archive for the 技術情報 Category

Road to OpenGL ES (4)

単にiOS上でゲームを作るならUnityは一番早い方法だと思います。

日本語の書籍もある程度出ていますし、ネット上でもブログ等で紹介されています。

何よりも画面上で様々な設定が出来て、簡単なゲームならチョコッと(?)プログラムを描くだけでもそれらしく見えます。

ゲームのキャラクターや場面の画像も比較的容易で、UnityのHPでも紹介されている様に

などの3Dレンダリングソフトと連携する事が出来ます。

高価なものもありますが、対象をiOSと絞った場合にはCheetar3DBlendeardeもサポートしていますので、その部分では非常に安価におさめる事が出来るのではないでしょうか。

ただ、Unityでは独自の開発環境で開発し、それをxcodeのプロジェクトに書き出すという形式を取っています。

そのため生成されるコードをイジル事は殆ど出来ません。

もちろんプロダクトを作って売る事が目的ですが、根っからの開発者としてはちょっと物足りません。

とりあえずUnityの採用はPendingにしたものの、もう一つ解決しなければいけない課題。3Dレンダリングです。

 

先ほどご紹介したBlenderとCheetah3Dを試しましたので、次回はそこら編をご紹介したいと思います。

 

 

 

 

 

 

 

Thread内ループの停止

ツイッターの @iphone_dev_jp でお問い合わせを受けましたので、久しぶりに技術的なコラム(^^;

iOSでもThreadがサポートされており、マルチスレッドでの処理が可能な事はご存知だと思います。ここではiOS4.0以上でサポートされているGCDでの例を挙げます。

並列して処理をしたい場合、Threadを生成して平行して処理をさせる事は有効だと思います。マルチコアCPUをサポートしたiPad2やiPhone4Sでは特に処理効率が有効になると思います。
それにThreadを使用すると一つの処理をまとめて「ポイッ」と投げてしまう事が出来ますので、開発上も楽チンだと思っています(ただし何でもカンでもThreadにする必要はないですし、余計に面倒くさくなります)。

普通に使われているiOSもanimationで内部的にはThreadが使われているため、「ポイッ」と投げてやれば勝手にanimationを行ってくれます。

しかしThreadの中で持続的にループを指せようとする場合には問題があります。
Threadを生成したObjectをreleaseしてもそのThreadは自動的に停止してくれる訳ではないのです。

当然僕も「何かThreadを停止させるAPIは無いのか?」と思いました。で、探したのですが見つかりません。もしかしたら僕の探し方が悪いのかもしれませんが、少なくとも僕にはそういうAPIが見つかりませんでした。

そこで…


@interface TimerView : UIView {
    BOOL started;
}
-(void)startTimer;
-(void)stopTimer;
@end

 


@implementation TimerView
-(void)startTimer {
    if(started == NO) {
        started = YES;
        dispatch_queue_t queue;
        queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
        dispatch_async(queue, ^{
            while(started) {
                useconds_t useconds = 5000;
                usleep(useconds);
                スレッドの中でやらせたい事
                if(CONDITION) {
                dispatch_async(dispatch_get_main_queue(), ^ {
                    描画等の処理をする
                });
            }
        }); 
    }
}

-(void)stopTimer {
    started = NO;
}

@end

 

上のソースでは単純にThread内でループを作り、StopTimerメソッドでstartedNOにしてやるだけです。
5000 ミリ秒ごとに起き上がってくる処理の中でチェックし、終了してしまいます。
Threadの中ではしてはイケナイ事が幾つかあります。例えば描画処理等もそれです。それらは


                dispatch_async(dispatch_get_main_queue(), ^ {
                    描画等の処理をする
                });

の中で行います。

My name is Taro Yamada.

iPhone 4Sの中に入っている Siri。

日本語には対応していないため、十分に使いこなす事が難しいですね。改めて自分の英会話力の程度を思い知らされます。

自分の名前さえも認識してくれませんから。

 

ところでSiriを使ってメールを送る場合には、相手先は「連絡先」の中の「読み」を参照しているようです。ですのでそこをアルファベット表記してやれば認識してもらえる可能性が増えます。

でもここでも問題が…

自分の名前をローマ字表記しても「ネイティブは何て読んでいるんだろう?」

ローマ字のままで読んでも認識してくれる確率はかなり低いです。

で、見つけたのが「Repeat After Me」。Xcode 4.2の中に入っています。

 

これを使うとローマ字入力した名前の読み方を、実際に発音して教えてくれます。その真似をするとかなり認識率が高まります。

 

また「連絡先」で「妻」に登録しておいてやると

“I want to send email to my wife.”

なども認識してくれます。

 

 

iOS 5.0

とうとう本日、待望のiOS 5.0が出ましたね。

それに合わせて昨日はiTunesのアップデートがありましたし、今日はLionのアップデート。

一度アップデートしてしまえばWi-FiリンクでもうPCやMacに直接繋げる必要も無くなりました。

それよりも今後はOTAでiOSさえもネット経由で配信されるため、iOS 5.0にアップグレードしてもらえば開発者側としても楽になります。もちろんAppleも…

文字通りPC Freeになってしまいました。

 

GM Seedから使いだした感想としては、ますます便利になったと思います。

気に入っている機能は「通知センター」、「iMessage」、「リマインダー」、「Wi-Fiシンク」などです。

 

中にはBeta Seedを入れていた方々で正規版のiOS 5.0へのアップグレードに手間取っている方もいるかもしれません。私もそうでした。

既にGM Seedが入っているとiTunesの「アップデートを確認」ボタンを押しても「このバージョンのiPodソフトウェア(5.0)は最新バージョンです。」と表示されてしまいます。

ではどうすれば?

iTunesで「アップデートを確認ボタン」の下の「復元」ボタンを押す事でアップデート出来るようです。

 

明日はiPhone 4Sの発売です。

個人的にはソフトバンクを快く思っていなかった私としてはauから出るiPhoneを楽しみにしています。

もちろん予約済みです。

 

備忘録

GarageBandで生成したファイルをiOSで使用可能なファイル形式に変換するには…

afconvert -f caff -d LEI16 SoundFile.m4a

 

 

最近忘れやすいので、備忘録として書いておきます。

iPhone アプリの広告

前回の書き込みでiAdの実装を紹介しましたが、今日2度目のiAdの広告と巡り会いました。毎日見ている訳ではありませんが、暇があるとたまに見たりしています。

ぽけっと定規(無料版)です。
やっぱりなかなか出ませんね。
前回見たのもトヨタですから、まだまだ広告主が少なくインベントリーも少ないのかもしれません。

ところで、ぽけっと定規をリリースして改めて感じたのですが、広告で収入を得るためには幾つかの条件があると思います。

  • アプリ自体が多くの人にダウンロードしてもらえる事。
  • そのアプリが日常的に使いたくなったり、使わなければならない事。
  • 表示される広告が魅力的で、クリックしてみたくなる事。

などです。

ぽけっと定規を使われている方なら分かると思いますが、3つの代理店(?)の広告を表示するようにしています。もちろんその一つはiAdです。後の2つはAdMobとInMobiです。この2つを選んだのは、ぽけっと定規のダウンロード・ユーザの殆どが日本だからです。Internationarizationしてますので英語圏と英語がわかる国の方々からもダウンロードされてます。

しかしかなりの比率が日本です。ですので日本での広告は威信をしている代理店を選びました。

しかしぽけっと定規から見ている限りはどこも広告のインベントリーは少ない気がします。同じ広告が何度も出て来たりしますから。
広告収入をアテにしたアプリも出してる身としては、是非ともユーザーが興味を持ちそうな広告を出してもらいたい物です。

iAd

気付いている人も居ると思いますが、8月末から日本でもiAdのサービスが始まりました。FuturesVisionのぽけっと定規にも 1.2 から広告を入れさせて頂いています。
実は 1.1 から iAd 単独の広告を入れていたのですが、その時点では日本でサービスが始まっておらず、殆ど意味がありませんでした。(もちろんUS等でダウンロードして頂いた方々からのアクセスはありました)

今は#ifdefで実装されていませんが、ソースの中に残っているiAdの実装部分をご紹介します。一応、AppleのReviewも通過したソースです。
これをRootViewControllerの中で実装し、- (void)viewDidLoad の中で

[self addBarInit];

を呼び出してやるだけです。



-(void)adBarInit {
CGRect r;
CGFloat tabBarHeight;
UIDevice *device;
NSString *ver;

bannerIsVisible = NO;
device = [UIDevice currentDevice];
ver = [device systemVersion];

if([ver compare: @”4.0.0″] == NSOrderedAscending) {
return;
}

adView = [[ADBannerView alloc] initWithFrame: CGRectZero];
adView.alpha = 0.5f;
adView.delegate = self;
if([ver compare: @”4.1.9″] == NSOrderedDescending) {
adView.requiredContentSizeIdentifiers = [NSSet setWithObject:ADBannerContentSizeIdentifierLandscape];
adView.currentContentSizeIdentifier = ADBannerContentSizeIdentifierLandscape;
}
else {
adView.requiredContentSizeIdentifiers = [NSSet setWithObject:ADBannerContentSizeIdentifier480x32];
adView.currentContentSizeIdentifier = ADBannerContentSizeIdentifier480x32;
}

tabBarHeight = 49;

r = adView.frame;
r.origin.y = self.view.frame.size.width- tabBarHeight – adView.frame.size.height;

if([ver compare: @”4.1.9″] == NSOrderedAscending) {
r.origin.y -= 8;
}
adView.frame = r;

device = nil;
ver = nil;

[self.rulerView addSubview: adView];
[self.rulerView bringSubviewToFront:adView];
}

– (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error {
if(bannerIsVisible) {
[UIView beginAnimations:@”animateAdBannerOff” context:nil];

[UIView commitAnimations];
bannerIsVisible = NO;
}
}

– (void)bannerViewDidLoadAd:(ADBannerView *)banner {
if(!bannerIsVisible) {
if(activeView == nil) {
activeView = self.rulerView;
[activeView addSubview: adView];
[activeView bringSubviewToFront:adView];
}
[UIView beginAnimations:@”animateAdBannerOn” context:nil];
[UIView commitAnimations];
bannerIsVisible = YES;
banner.hidden = NO;
[banner setNeedsDisplay];
}
}


忘却とは恐ろしい物で、コレで全部か?と言われると…な状況ですがご参考にはなると思います。

サービスが始まったとは言え、まだまだiAdがヒットする確率は低いようです。私もまだ1度しか見た事がありません。
ですがiAdに限らず広告を入れると、ダウンロードされた自分のアプリがどの程度実際に利用されているのかが分かるので、参考になります。
ダウンロードされても実際に使って頂かなければ寂しいですもんね。

最後に、ぽけっと定規は無料のアプリですので、出来ればたまに広告をクリックして頂けると助かります。
また、有料ではありますが広告無しのぽけっと定規プラスもありますので、よろしければそちらもご利用ください。

次回はぽけっと定規 1.2で実装したAdWhirlについて書いてみたいと思います。

UITableViewCellをカスタマイズする(の続き)

デバイスをローテーとした場合には、UIViewControllerのサブクラスに shouldAutorotateToInterfaceOrientation のメッセージが送られます。


– (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}

ここでNOを返してしまえば問題ありませんが、YESを返すと画面がローテートします。中には UIInterfaceOrientationPortrait しか対応していないアプリも多いように見えます。iPhoneやiPod Touchを使用していると UIInterfaceOrientationPortrait だけでも十分ですが、iPad(2を含む)ではどちらかというとLandscapeが一般的な気がします。
特にiPad2ではSmartCoverの関係で UIInterfaceOrientationLandscapeRight が一般的かなぁというのが私の感想です。(全く個人的な感想)

そこで困るのが、そのViewに表示している各表示項目の配置です。Portraitで一番下に表示していたもの等はLandscapeでは配置をかえてやる必要があります。
しかし各Viewにはローテートが発生したかどうかは伝わってきません。
そこで私が取っているのがUIViewの -(void)layoutSubviews の上書きです。
下記のようにLandscapeかどうかを判断して配置のしなおしをしています。


-(void)layoutSubviews {
NSInteger isLandscape;
UIApplication* app;
UIInterfaceOrientation orientaion;

CGRect f;
CGPoint p;

if(caloryLabel.text == nil || [caloryLabel.text isEqualToString:@””]) {
updateButton.enabled = NO;
}

app = [UIApplication sharedApplication];
orientaion = app.statusBarOrientation;
isLandscape = UIDeviceOrientationIsLandscape(orientaion);

switch (isLandscape) {
case YES:
f = label.frame;
f.size.width = 480 – 30;
foodNameLabel.frame = f;
f = label.frame;

「これで一安心」
と思っていたのですが、いくつかの部分で動かなくなってしまった機能がありました。その一つがUITableViewCellをサブクラス化した部分です。
いつの間にかswipeによるセルの削除などが出来なくなったのです。
最初は

– (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

あたりを疑ったのですが、らちがあきません。
結局たどり着いたのは先ほどの -(void)layoutSubviews です。
オリジナルのUITableViewCellでも使っていたのですね。
そこでこのメソッドの最後で

[super layoutSubviews];

と追加してやる事で問題は解決しました。

TableViewCell をカスタマイズする

UITableViewとその元のUITableViewCellの機能は豊富かつ強力で、殆どの場合にはそのままで使用出来ると思います。しかし表示の仕方をカスタマイズしようとすると限界にが出てきます。
UITableViewは2次元(sessionを含めると)のテーブルを管理しますが、それぞれのセルの表示を担当しているのがUITableViewCellクラスです。
これをサブクラス化すれば表示できる項目もカスタマイズ出来ます。
全てをObjective-Cのソースで書く事も出来ますが、Interface Builderを使わない手はありません。

まずはUITabeleViewCellのサブクラスを作成します。ここでは単純にTableViewCellクラスとします。
次にUIViewベースのxibを作成します。
xcode上でviewを選択肢、クラス名を先ほど作成したTableViewCellに変更します。

次にUITableViewControllerまたはUITableViewControllerのサブクラスと関係付けてやります。

このように、File’s OwnerのクラスをUITableViewControllerまたはUITableViewのサブクラスに指定してやります。
その中には、新しいセルのクラスを IBOulet で定義してやります。


#import
#import "TableViewCell.h"

@interface TableViewController : UITableViewController

@property (nonatomic, assign) IBOutlet TableViewCell* tableViewCell;

@end

このようにInterface Builderを使うとセルとテーブルを関連づけてしまうため、再利用が面倒ですが、コードを書く手間は格段に減ります。

ではUITableViewの中でどのようにして新しいセルを作ってやるかというと、テンプレートとして作成された以下のメソッドに実装を付け加えてやります。


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";

TableViewCell *cell = (TableViewCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:@"TableViewCell" owner:self options:nil];
cell = tableViewCell;
}
tableViewCell = nil;
cell.label.text = @"Label";

return cell;
}

面白いのはloadNibNameでロードして来ていますが、返り値がありません。
これが先ほどIBOutletで指定した場所(TableViewCell* tableViewCell)に自動的に入ってくるわけです。

さて、上のコードでは
 tableViewCell = nil;
としているだけですが、メモリーリークは?と思いますが、これはretainではなく、assignで定義されていますので明示的な releaseは要りません。

基本はコレだけです。
Interface Builderで作成したUITableViewCellのサブクラスは、当然UIViewクラスのサブクラスですので、そこに貼付けられるものは大概貼付けられます。

今日はここまで。
次回は、この方法でセルを作った場合に気付いた事…
を書いてみようと思います。

TableViewCell をカスタマイズする

UITableViewとその元のUITableViewCellの機能は豊富かつ強力で、殆どの場合にはそのままで使用出来ると思います。しかし表示の仕方をカスタマイズしようとすると限界にが出てきます。
UITableViewは2次元(sessionを含めると)のテーブルを管理しますが、それぞれのセルの表示を担当しているのがUITableViewCellクラスです。
これをサブクラス化すれば表示できる項目もカスタマイズ出来ます。
全てをObjective-Cのソースで書く事も出来ますが、Interface Builderを使わない手はありません。

まずはUITabeleViewCellのサブクラスを作成します。ここでは単純にTableViewCellクラスとします。
次にUIViewベースのxibを作成します。
xcode上でviewを選択肢、クラス名を先ほど作成したTableViewCellに変更します。

次にUITableViewControllerまたはUITableViewControllerのサブクラスと関係付けてやります。

このように、File’s OwnerのクラスをUITableViewControllerまたはUITableViewのサブクラスに指定してやります。
その中には、新しいセルのクラスを IBOulet で定義してやります。


#import
#import "TableViewCell.h"

@interface TableViewController : UITableViewController

@property (nonatomic, assign) IBOutlet TableViewCell* tableViewCell;

@end

このようにInterface Builderを使うとセルとテーブルを関連づけてしまうため、再利用が面倒ですが、コードを書く手間は格段に減ります。

ではUITableViewの中でどのようにして新しいセルを作ってやるかというと、テンプレートとして作成された以下のメソッドに実装を付け加えてやります。


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";

TableViewCell *cell = (TableViewCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:@"TableViewCell" owner:self options:nil];
cell = tableViewCell;
}
tableViewCell = nil;
cell.label.text = @"Label";

return cell;
}

面白いのはloadNibNameでロードして来ていますが、返り値がありません。
これが先ほどIBOutletで指定した場所(TableViewCell* tableViewCell)に自動的に入ってくるわけです。

さて、上のコードでは
 tableViewCell = nil;
としているだけですが、メモリーリークは?と思いますが、これはretainではなく、assignで定義されていますので明示的な releaseは要りません。

基本はコレだけです。
Interface Builderで作成したUITableViewCellのサブクラスは、当然UIViewクラスのサブクラスですので、そこに貼付けられるものは大概貼付けられます。

今日はここまで。
次回は、この方法でセルを作った場合に気付いた事…
を書いてみようと思います。