手描きのアプリは、指の動きに追従してなめらかな線を書くのがなかなかに難しいものです。いろんなアプリが色々工夫していることかと思います。SpeedTextも工夫しています。んで、色々工夫していると、速くなったんだか遅くなったんだか自分ではよく分からなくなってきます。人間の感覚って難しい。
というわけで、手書き系アプリのための描画速度を測るかんたんなテスト用ビューを作ってみました。アプリの処理を変える前後でテストすると、速くなったかどうか分かりやすいかな、と。
実行すると、画像みたいな感じで「2秒間かけて丸を等速で12個描くような」指の動きをシュミレートします。1つの丸に60個の座標、計720個の座標を用意して、1つの座標の描画が終わるたびに、スタートからの経過時間に応じた座標を描画用のビューに渡します。(描画用のビューとは、あなたのアプリのビューです。なお、iPhoneSDKでは、本物のtouchなんとかメソッドを呼べないため、アプリ側に擬似的なtouchなんとかメソッドを用意する必要があります)
んで、touchなんとかを呼んだ回数をカウントして最後に表示します。呼べた回数が多いほどいいです。これは大雑把なスコアといえます。丸の形も見ましょう。touchに対する処理速度が偏っていると、丸が歪な形になります。あと、丸の上部がどれだけ閉じているかは、指のつけ離しに対する追従性を表します。一つの丸から次の丸に移るときにtouchEnd、touchStartを呼びますので、そこで時間がかかると、円の開始を描くのが遅くなり、上の部分が開いてしまいます。
使い方
1. この記事の最後に記したTestBaseViewクラスをプロジェクトに追加します。
2. テスト対象となるビューに、以下の3つのメソッドを追加します。locationに描画のための座標が入ってくるものとして、touchesBegan:withEvent等と同じ処理を記述してください。
- (void)touchesBegan:(CGPoint)location; - (void)touchesMoved:(CGPoint)location; - (void)touchesEnded:(CGPoint)location;
3. 以下のような感じでTestBaseViewを画面に追加します。(追加先はテスト対象ビューと異なってもよいです)
TestBaseView *testBaseView = [[TestBaseView alloc]
initWithFrame:CGRectMake(0,0,320,100)];
[testBaseView autorelease];
[testBaseView setup];
[testBaseView setDrawView:drawView]; //このdrawViewを、アプリで使ってる描画用のビューにすること。
[self addSubview:testBaseView];
TestBaseViewソース
テスト用なのでソース汚いです。すまんす。
#import <UIKit/UIKit.h> #define TEST_NUMBERS_OF_POINT (10000) #define TEST_NUMBERS_OF_DRAW_X (6) #define TEST_NUMBERS_OF_DRAW_Y (2) #define TEST_POINTS_IN_CIRCUL (60) #define VIEW_WIDTH (320) #define VIEW_HEIGHT (300) #define CIRCUL_RADIUS (48) #define TEST_FLAG_START (0) #define TEST_FLAG_MOVE (1) #define TEST_FLAG_END (2) #define COUNT_SECOND (2.0) @interface TestBaseView : UIView { UITextView *consol; CGPoint points[TEST_NUMBERS_OF_POINT]; int pointFlags[TEST_NUMBERS_OF_POINT]; int drawIndex[TEST_NUMBERS_OF_POINT]; CGPoint pointOfEnd[1000]; int countOfPoints; int countOfDraws; int pointIndex; int lastDrawIndex; BOOL isNextStart; id drawTestView; NSDate *startDate; int drawedPoints; } -(void)setup; @end
#import "TestBaseView.h" @implementation TestBaseView -(void)drawPoint:(int)index { if (pointFlags[index]==TEST_FLAG_START) { [drawTestView touchesBegan:points[index]]; }else if (pointFlags[index]==TEST_FLAG_MOVE) { [drawTestView touchesMoved:points[index]]; }if (pointFlags[index]==TEST_FLAG_END) { [drawTestView touchesEnded:points[index]]; } } -(void)endOfDraw { consol.text = [NSString stringWithFormat:@"points:%d",drawedPoints]; } -(void)drawNext { double t = -[startDate timeIntervalSinceNow]; //double型で秒を返す(マイナス) drawedPoints ++; //COUNT_SECOND秒間でcountOfPointsまで描画するから、 int index = t*countOfPoints/(double)COUNT_SECOND; if (index > countOfPoints-1) { if (isNextStart==NO) { //終了 [drawTestView touchesEnded:pointOfEnd[lastDrawIndex]]; [self endOfDraw]; } return; } int dIndex = drawIndex[index]; if (lastDrawIndex<dIndex) { //次の図になったので、前の図を終了 if (isNextStart==NO) { [drawTestView touchesEnded:pointOfEnd[lastDrawIndex]]; isNextStart=YES; }else{ //次の図の開始 [drawTestView touchesBegan:points[index]]; isNextStart = NO; lastDrawIndex = dIndex; } }else { [drawTestView touchesMoved:points[index]]; } [NSTimer scheduledTimerWithTimeInterval:0.0 target:self selector:@selector(drawNext) userInfo:nil repeats:NO]; } -(void)start:(id)sender { NSLog(@"start"); pointIndex = 0; isNextStart = YES; lastDrawIndex = 0; drawedPoints =0; [startDate release]; startDate = [NSDate date]; [startDate retain]; [drawTestView touchesBegan:points[0]]; [self drawNext]; } -(void)dealloc { [startDate release]; [consol release]; [super dealloc]; } -(void)setup { UIButton *startButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; startButton.frame = CGRectMake(10, 30, 100, 30); [startButton setTitle:@"start" forState:UIControlStateNormal]; [startButton addTarget:self action:@selector(start:) forControlEvents:UIControlEventTouchUpInside]; [self addSubview:startButton]; consol = [[UITextView alloc]initWithFrame:CGRectMake(0,60,320,40)]; consol.opaque = YES; consol.backgroundColor = [UIColor whiteColor]; consol.text = @"hoge"; consol.alpha =0.5; consol.userInteractionEnabled = NO; [self addSubview:consol]; // point のセットアップ 丸を格子状に並べて描画する int index = 0; int dIndex = 0; CGFloat radius = 48; CGFloat addX =(VIEW_WIDTH-(radius*2)-10)/TEST_NUMBERS_OF_DRAW_X; CGFloat addY =(VIEW_HEIGHT-(radius*2)-10)/TEST_NUMBERS_OF_DRAW_Y; for (int drawY=0;drawY<TEST_NUMBERS_OF_DRAW_Y;drawY++) { for (int drawX=0;drawX<TEST_NUMBERS_OF_DRAW_X;drawX++) { CGFloat startX = drawX * addX + radius+10; CGFloat startY = drawY * addY + radius+10; for (int i=0;i<TEST_POINTS_IN_CIRCUL ;i++) { CGFloat x = startX-sin(2*M_PI* i/TEST_POINTS_IN_CIRCUL)*radius; CGFloat y = startY-cos(2*M_PI* i/TEST_POINTS_IN_CIRCUL)*radius; points[index] = CGPointMake(x, y); drawIndex[index] = dIndex; pointFlags[index] = TEST_FLAG_MOVE;//move if (i==0) { pointFlags[index]=TEST_FLAG_START;//start }else if (i==TEST_POINTS_IN_CIRCUL-1) { pointFlags[index]=TEST_FLAG_END;//end pointOfEnd[dIndex] = CGPointMake(x, y); } index ++; } dIndex++; } } countOfPoints = index; countOfDraws =dIndex; } -(void)setDrawView:(id)view { drawTestView = view; } @end
VIEW_WIDTH、VIEW_HEIGHTは対象となるビューのサイズです。大きくすると、大きなビューを想定して丸の間隔が広がります。
ソースのCOUNT_SECONDは、シュミレートする時間(秒)です。ここを減らすと、もっとシビアな条件を測定できます(実際のSpeedTextのテスト時は1秒にしてます。画像は2秒のもの)
このテストプログラムでは、全体的に遅くても安定した速度で処理されると、いい結果になるので、描画自体の厳密な速度は測れないんですけどね。あと、touchが呼ばれる部分をスキップしちゃってるし。まあ、安定した速度で書けるかというテストですね。
hirogram2010/08/18 09:53TestBaseViewの-(void)drawPoint:(int)index と、メンバ変数pointFlagsへの代入処理は、不要でした。あっても動きますが、邪魔なら削ってください。
はてなの日記の方に、Evernoteのイベントについてブログ書きました。
これって、自動でリンクとかできないのかしら。はてなグループの使い方が未だによく分かりません。
2010-02-26 Evernoteの海外イベントに出ました
http://d.hatena.ne.jp/hirogram/20100226
おうちに帰ってブログ書くまでがイベントよね。
まだ帰ってませんけれども。
表題の通り、OS2.2.1のデバイスでも動くけどOS3.0なら専用の機能が動くアプリの作り方です。ADCにサンプルコードがあるのでご存じのかたも多いと思いますが、私は最近知ってありがたかったので、まあ報告します。
OS3.0特有の機能というとメールに画像が添付できたり、コピペが使えたりとかです。他にもあった気がします。OS3.1も出ちゃってるのでいろいろあることでしょう。
やりかたは、ADCのサンプルコード"MailComposer"のReadMe.txtに丁寧に書いてあります。
やるのは割と簡単ですが、注意すべき点を先に挙げておいたほうがいいようです。
このようにソースコードが煩雑になり、ミスも誘発しやすくなりで、開発者にとってはいいことはありません。新しくアプリを作る場合は素直にOS3.1用で作った方がいいです。OS2.2.1でアプリリリースしちゃってて、この後どうしようかという人向けですね。私みたいに。
最後に手順を書きます。MailComposerのReadMe.txtも読みましょう。
これはすべての構成(Distributionも!)に対して行って、3回くらいチェックしてください。
そうすると、XCodeのウィンドウ左上の構成とかを選ぶドロップボックスに「ベースSDKを使用」というのが現れます。これがどっちでも動くよ版です。
-(id) init
{
[super init];
isOs30 = NO;
Class pasteClass = (NSClassFromString(@"UIPasteboard"));
if (pasteClass != nil)
{
isOs30 = YES;
}
return self;
}
-(BOOL)isOs30 {
return isOs30;
}
以上です。MailComposerのReadMe.txtも熟読しましょう。
リジェクトされないか気になるところですが、今申請中なので通ったら報告します。サンプルコードがあるくらいなので大丈夫だと思いますが。。
kimadaなるほど、これが正しいやり方なんですね。このサンプルは見ましたが、ReadMe.txtは、あまり読んでませんでした。。。
私の場合は、とりあえず、NSInvocationで動的に3.0のAPIを呼び出す方法で解決しました。
http://d.hatena.ne.jp/kimada/20090811/1249970669
複雑なことをやる場合には、まったく不向きな方法ですね。ちゃんとReadMe.txtを読むべきでした。。。
バッテリー残量の取得は、2.xで非公開APIを使ったものが出回っていたこともあり、「それを使ってないよね?」というAppleから確認の電話が来ました。そこで事情を説明したところ、その翌日にリリースされました。
なので、2.xでビルドしたアプリから、3.0のAPIを呼び出すこと自体は、問題ないと私は解釈しています。
hirogramさんのアプリも、無事に審査が通ることをお祈りしております!
hirogramコメントありがとうございます。
なるほど、動的にやる方法もあるんですね。勉強になります。
動的にやると、OS2.0でビルドしてもビルドが失敗しない(他の部分のエラーチェックができる)のはメリットですね。
iPhoneの場合、どのやり方が正しいというか、リジェクトされなかったものが正しいみたいな感じがありますね。
いろいろなやり方で試してみましょう。
hirogram報告すると言って忘れてました。
無事審査通りました!
SpeedTextが有料トップアプリケーションのNo.1を獲得しました!
応援してくださった皆さんありがとうございます。
で、今回ちょっといいことがわかりました。
それは、「小さなツールでしかも有料でも、ゲームアプリと勝負できるんだ」ということです。
言うまでもなくゲームは人気を取りやすいし売れるし、上位の物は私も持ってたりするんですが、
手の込んだ重量級アプリには勝てないとなっちゃうと、アプリ作るのもつまらんですよね。
まあ数時間後のWWDC以降、アプリの順位は大きく様変わりし、1位はつかの間のこととなるのは容易に予想がつきます。
それでも、ここまでこれたのは、私にとって結構な収穫でした。
もちろん、このアプリもまだまだがんばりますけどね!
今回は手書きメモアプリです。好評みたいでありがたいです。
今回開発している上で気付いたアプリのサイズの削減方法を2点挙げます。アプリのサイズが大きいと起動時間に影響が出るので、私は結構気にしたりします。もう常識だったらごめんね。
LinaKudos to you! I hadn't thoghut of that!
dkxypjnvgzbywcpdB <a href="http://icadilnkhomr.com/">icadilnkhomr</a>
viwblfm2tCxwH , [url=http://nuwfrnuziydm.com/]nuwfrnuziydm[/url], [link=http://erjgmspazgjx.com/]erjgmspazgjx[/link], http://qhrbgemrzpth.com/
ivfwplbunyMcSTv <a href="http://tpkghnumpcij.com/">tpkghnumpcij</a>
ulxrwefNWOye3 , [url=http://jbovfwgmxgjy.com/]jbovfwgmxgjy[/url], [link=http://bsadnhvmkvsw.com/]bsadnhvmkvsw[/link], http://gpurbjeptytj.com/