setNeedsDisplayInRect: での矩形の指定方法


安定動作のためのsetNeedsDisplayInRect:

ピンクのUIView Yの中のdrawRect:で線を描いたりしているのですが、ズームインしたときにはこのYを大きく拡大したりしています。
そのためsetNeedsDisplayでこのY全体を再描画していると、その瞬間にだけものすごい量のメモリを使ってアプリが落ちてしまったりしていました。
そこで、少なくとも再描画が必要な範囲(例の青の部分)というのを特定して、その範囲だけを再描画することにしました。

setNeedsDisplayInRect:の再描画範囲がおかしい?

しかし、ちゃんと再描画が必要な範囲を計算して、setNeedsDisplayInRect:に渡しているはずなのに、再描画される範囲がどう見てもおかしく正常動作しませんでした。
NSLogで渡している矩形を書き出したりして何度もチェックしたのですが、渡している矩形の範囲は数字上は正しいはずなのに、画面では再描画範囲が指定した範囲よりも右下にずれているような。。。?

値をいろいろと変えたり、あちこちにデバッグライトを入れてチェックしたりすること数時間、原因が分かりました。

原因は

setNeedsDisplayInRect:には、それを呼び出しているレシーバーを基準にした座標系のCGRectを渡す必要があったのでした。

上の例だと、私はUIView Xの座標系で計算したCGRectを渡しちゃっていたので、レシーバーであるUIView Yのoriginの分だけ右下にずれた範囲が再描画対象になっていたのでした。

UIView Yの中の青の部分だけを再描画させるためには、


CGRect r1 = CGRectMake(120,150,180,190); /* これじゃダメ */
CGRect r2 = CGRectMake(60,70,180,190); /* こちらが正解 */


r1だとダメで、r2をsetNeedsDisplayInRect:に渡してやる必要があります。
図形の変化に合わせてピンクのUIView Yのframeも変化していたので原因特定にすごく時間がかかりました。

実は、もちろんマニュアルにも書いてある。(なので誰も悩まないのでgoogleで検索しても解決できなかった・・・)

invalidRect
The rectangular region of the receiver to mark as invalid; it should be specified in the coordinate system of the receiver.

読んだはずなんだけどなぁ。
あんまりピンと来なくてスルーしてしまったのか?

教訓:マニュアルはちゃんと読もう!

storyboardをipad用に複製する方法


iPhone用に作ったstoryboardをiPad用に使いたいと思い、方法を調べました。

Stack Overflowより

見つかった情報はこちら。
Convert Storyboard from iPhone to iPad – Stack Overflow

1. iPhone用の storyboard を複製して、MainStoryboard_iPad.storyboard に名前を変える
2. テキストエディタで、このstoryboardを開く
3. targetRuntime=”iOS.CocoaTouch” という箇所を検索し、targetRuntime=”iOS.CocoaTouch.iPad” に置き換える
4. 保存してから、xcodeでこのstoryboardを開く。ただし、コンポーネントはバラバラ。
※ 原文は英語。適当に訳しました

追加で

  • テキストエディタで開いたときに、width=”320″ の箇所を width=”768″ に置換。height=”480″ の箇所を height=”1024″ に置換するといいかも。
  • テキストエディタは使わずに、コピーした新しいsotryboardをxcodeに追加した後、そのstoryboardを右クリック-> “open as” -> “Source Code” で書き換えればよい。

というようなコメントが書かれていました。

やってみた

最初Finderでstoryboardファイルを見つけるのに苦労しました。
よくよく探してみると、[プロジェクト名]->[en.lproj]配下にありました。ローカライズの作業なんかまったくやってないんですけど、これはstoryboardのファイルは自動でlpojに入れられる?
これをfinder上でコピーして名前をMainStoryboard_iPad.storyboardに変更して、xcodeにドラッグアンドドロップ。
次にxcode上でMainStoryboard_iPad.storyboardを右クリック-> “open as” -> “Source Code” で中身を書き換えます。

targetRuntime=”iOS.CocoaTouch.iPad” に書き換える、->”open as” -> “Interface Builder – iOS Storyboard”で開くと。。。
すべてのUIViewControllerが768×1024に変わるだけで、そこに載せているコンポーネントはそのままです。
320×480のサイズ指定した背景画像などは、widthをwidth=”768” に変更、heightをheight=”480″に変更すれば、ちょっとはマシになります。

キラキラしたアニメーションエフェクトを探して


アプリを使っているとアイテムを使用したときとか、目標を達成したときとかに、画面がキラキラ輝くエフェクトがあったりしますが、これってどうやって作っているのでしょう?
今作っているアプリでアイテムを使用するって場面があるんだけど、いまいちアイテムを使った感が得られなくて悩んでいます。
とりあえず「キラキラ」系の効果音をならしつつ、アイテム画像を2倍に拡大しながらうっすら消していく感じにしたんだけど、うーむイマイチ。

でもキラキラした画像を何枚も用意してアニメーションさせるなんて、考えただけでもウンザリ。
検索してもあんまりコレってページが見つからないんですよねぇ。

その中でかろうじて私が見つけることができたのがこちら。

有料ソフト Particle Designer

Particle Designer – iOS Game Particle Effects – Explosions Smoke Fire Rain Stars – iPhone iPad OSX Cocos2D

最初に見つけたのはコレ。
Particle(分子、粒子)ってことだから、粒子を好きなようにいじってキラキラできるんだろう。
値段も $7.99 ってことなので、高いものではないっていうかむしろ安いんだけど、極力有料ソフトは使いたくないんで買うことはないだろうなぁ。

Windows用のフリーソフト

エフェクトエディターの詳細情報 : Vector ソフトを探す!
次にこんなフリーソフトを発見。
どのようなソフトなのかまだチェックしていませんが、パラメータ指定で様々なエフェクトを作成して連番のpng形式のファイルを出力してくれるらしい。
こちらはフリーソフトなのでいずれさわってみようと思います。
どのようなものが作れるのか楽しみですが、最近windowsさわってなくて起動するのがちょっと面倒だったり。
mac版も作ってくれないかなぁ、なんて。

ちょっと元気があるときにでも一度自分でキラキラアニメ作ってみるかなぁ。
一度作れば使い回せそうだし。

UIColorを使いやすく


UIColor使いにくいなぁと思いながら我慢して使っていましたが、こんな工夫をされている方を見つけました。

UIColor を扱いやすくする « 水色のドア

Prefix.pch ファイルに

#define RGB(r, g, b) [UIColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:1]
#define RGBA(r, g, b, a) [UIColor colorWithRed:r/255.0 green:g/255.0 blue:b/255.0 alpha:a]

を追加するという、ただそれだけで

UIColor * lineColor = RGB(153,153,255);

とか、

UIColor * foreColor = RGB(0x1e,0x90,0xff);

のように記述できるという。
便利そうなのでしばらく使ってみよう。

# Prefix.pchファイルの存在を今まで知らなかったというのは内緒。

==========追記(2013/01/03)==========
こんな記事を見つけました。
Objective-Cでよく使う便利マクロを10個集めてみた #iOS #iPhone #Objective-C #AdventCalendar – Qiita

この記事の中で同じようなUIColor用のマクロがありました。

#define RGB(r, g, b) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:1]
#define RGBA(r, g, b, a)    [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:(a)]

私が最初に見つけたサイトにあったマクロとの違いは、引数にカッコがついていること。
こちらの記事の方も最初はカッコをつけていなかったようですが、コメントで指摘があったようです。

カッコをつけないと

RGB(200 + 55, 200 + 55, 200 + 55) 


のような使い方をするときに、意図した通りにならず

[UIColor colorWithRed:200+55/255.0 green:200+55/255.0 blue:200+55/255.0]


になってしまうんですね。

勉強になります。

Warning: Attempt to present 〜 on 〜 while a presentation is in progress!


警告が出るもよくわからず。。。

modalを

[self presentViewController:modalViewController animated:YES completion:nil];

で呼び出して、なんらかの処理をさせて、その後 delegate で戻ってきたところで

[self dismissViewControllerAnimated:YES completion:nil];

でmodalを閉じたりするんですが、そのときにタイトルに書いた


Warning: Attempt to present モーダル on 親のview while a presentation is in progress!

のような警告がでてきて悩んでました。

警告の意味がわからずgoogle先生にも聞いてみたところ、いつものstackoverflowさんが引っかかったのですが、それによるとmodalviewを開こうとしたら既にそのmodalviewは開かれている場合に出る警告ってことかな?
なぜこんな警告が出るんだろう。
そんなことをしているつもりはなくて、、、ちょっとよく意味が分からずしばらく放置していました。

原因が判明

この警告が出ていても一応正常に動いているように見えるし、他の箇所でmodalを表示しているところではこの警告は出てなかったりして、ほんと意味が分からず。
でも、別のバグ取りのためにステップ実行していたら理由が分かりました。

その理由というのが、viewWillAppear の中でmodalを表示させていたこと。

1. viewWillAppear が呼び出される
2. その中で presentViewController で modal を表示
3. delegateで戻ってきて dismissViewController で modal を閉じる
4. 再び viewWillAppear が呼び出される

modalの終了処理が終わる前にもう一度 modal を開こうとして警告が出ていたんだろうか。
もしかしたらそのままでもiOSが適切に対応してくれているのかもしれませんが、タイミングによっては大変なことになりはしないかとかなり心配だったので速攻で対処しました。

modalを閉じたときにも呼び出し元の viewWillAppear って呼ばれるんですね。
今まで知らなかった。
この警告で検索してもあまりヒットしないということは、常識なんだろうなぁ。
まだまだ力不足だなぁ。

iOSのバージョンで場合分け


アプリを作っているときって、Web上にあるコードの断片を参考にしてコーディングすることが結構あるのですが、iOSのバージョンアップによって「Deprecated in iOS 6.0」なんてxcodeに警告を出されることがたまにあります。

で、この場合、iOS6.0以上と6.0未満で場合分けをしないといけないわけなんですが、実は面倒でこれまで放置していました。。。
まぁ動けばいいや、なんて思ったりしてて。
ですが、そろそろ一度調べておこうなどと殊勝にも考えまして、ようやく調べてみました。

次のように場合分けすればいいらしい。
簡単ですね。


float version = [[[UIDevice currentDevice] systemVersion] floatValue];
  if (version >= 6.0) {
    // iOS6.0以上の処理
  }else{
    // iOS6.0未満
  }

私の場合、最近アプリ開発を始めたということもあって、対象は5.1以降でいいやって思ってるため多分この方法でいいのですが、もっと細かくバージョンで場合分けしたい場合には、この方法だとダメらしいです。
上記の方法ではfloatにバージョン番号を入れちゃってるので、例えば4.3.5とか5.1.1とか小数点(ドット)が2つ以上入っているバージョンが判別できません。

で、どうすればよいのかと言うと↓のサイトで解説されています。
NSStringで比較せよとのことです。
OSのバージョン番号を比較する方法 – 強火で進め
ちょっと面倒ですが、アプリ起動時に一度調べちゃえばいいだけなので、大した手間でもないか。

iPad 5.1 simulator で UIImagePickerController が正常に動かない


UIImagePickerControllerを使って写真を選択する処理を実装しようとしています。
先人の知恵を頼りにしながら以下のようなコードになりました。
しかしこのコード、iphoneシミュレータやiPad実機などでは正常に動いたのですが、iPad 5.1 simulatorでだけは動作が不安定なんです。


- (IBAction)pushButtonSelectPicture:(id)sender {
	if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]){
		UIImagePickerController *ipc = [[UIImagePickerController alloc] init];
		ipc.delegate = self;
		
		ipc.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
		if(imagePopController != nil){
			[imagePopController dismissPopoverAnimated:NO];
			imagePopController = nil;
		}
		if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad){
			//iPad
			imagePopController = [[UIPopoverController alloc] initWithContentViewController:ipc];
			[imagePopController presentPopoverFromBarButtonItem:sender
									   permittedArrowDirections:UIPopoverArrowDirectionAny
													   animated:YES];
		}else {
			//iPad以外
			[self presentViewController:ipc animated:YES
							 completion:^{}];
		}
		ipc = nil;
	}
}

-(void)imagePickerController:(UIImagePickerController*)picker
	   didFinishPickingImage:(UIImage*)image editingInfo:(NSDictionary*)editingInfo{
	if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad){
		// ipad
		[imagePopController dismissPopoverAnimated:YES];
	}else{
		// iphone
		[self dismissViewControllerAnimated:YES completion:nil];
	}
	[self setImage:image];
	image = nil;
}

popoverまでは正常に動いて写真を選択しても imagePickerContoller:didFinishPickingImage:edittigInfo が呼ばれなかったり、そもそもpopoverが出なかったり。。。
breakpointを設定してステップ実行していると、以下のエラーが出たこともありました。

Named service 'com.apple.PersistentURLTranslator.Gatekeeper' not found. assetsd is down or misconfigured. Things will not work the way you expect them to.

このエラーについて調べてみると、こんなのが↓
ios – Log message referring to Gatekeeper appears occasionally when running simulator – Stack Overflow
objective c – iPad simulator does not work with UIImagePickerController in an iPhone app – Stack Overflow

ひとりは
iPad simulator v5.0
iPad simulator v5.1
でだけこの問題が発生したと言ってます。
私もiPad 5.1 simulatorでは問題があるけど、実機やiPad 6.0 simulatorでは正常に動いています。
simulatorのバグなのかなぁ。

上記リンク先で一応の回避策のようなものが書かれています。
違うデバイスでテストするときは、一旦シミュレータを終了してからリスタートするといいみたい。
確かに私の環境でもこれで一応は動きました。

viewのframeサイズはできるだけ小さく


Mac Book Airを手に入れてから約2ヶ月半、なんとかアプリ第一弾をリリースすることができました。
足し算と引き算の算数アプリなんで特に難しいことはしていないように見えますが、全てが初めての経験なので本当にあちこちにつまずきました。

interface builderを横画面にする方法が分からなかったり、UINavigationViewの使い方なんてまったく意味が分かりませんでしたし、コピペしたコードもなぜか動かなかったり。ノウハウが全然ないからコピペして動かないと何がまずいのか全然わからない。
音の出し方もアニメーションのやり方も苦労しました。
そして最後の難関がアプリの申請作業。
申請作業ってちょっと大変過ぎでしょコレ。

そんなこんなでは苦労しましたが、その苦労のおかげでなんとかアプリの作り方のイメージができるようになり、やりたいことを実現するにはどうすればいいのか、なんとなく分かるようになってきました。

そして現在アプリ第二弾を作成中です。

作成中なのですが、今日までかなりハマっていた問題がありました。
メモリ不足でアプリが落ちてしまう問題です。

続きを読む

viewForZoomingInScrollViewの中で zoomScale を参照すると危険


UIScrollViewをピンチイン、ピンチアウトでズームイン、ズームアウトできるようにしました。

ここまでは apple Developer Library(日本語) iOS Scroll View プログラミングガイドの23ページあたりに書かれている通りにやるとすんなり動きました。

その後、ズーム前後でズームの倍率がどれくらい変わったかを知る必要があったので、
viewForZoomingInScrollViewの中で現在のズーム率を取得して、scrollViewDidEndZoomingの引数のscaleと比較してズーム倍率の変化を求めようと考えました。

しかし、viewForZoomingInScrollViewの中で、

beforeScale = [scrollView zoomScale];

などと現在の倍率を調べようとすると無限ループに陥ります。。。

NSLog(@"scale: %f",[scrollView zoomScale]);

なんてのもダメ。
これやっちゃうと固まってしまってしばらく処理を受け付けなくなってしまいました。

うーん、どうやらzoomScaleの値を取得する際にviewForZoomingInScrollViewが呼び出されているようなのです。

こちらにも同じ症状でお悩みの方が。
iphone – UIScrollView bug? float foo = scrollview.zoomScale crashes the app – Stack Overflow

結局scrollViewWillBeginZoomingの中でズーム前のズーム倍率を取得することにしました。
こちらのメソッドは内部でzoomScaleを参照しても大丈夫でした。

英語のヘルプ読んでもviewForZoomingInScrollViewとscrollViewWillBeginZoomingの違いはよく分かりませんでしたが、
使ってみた感じだと前者は、実際にズームされなくても2本指でタッチしたら呼ばれる、
後者は2本指でタッチした後で指を動かして実際にviewの大きさが変わり始めたら呼ばれる
ような感じかな。
viewForZoomingInScrollViewが呼ばれてもscrollViewDidEndZoomingが呼ばれないことは多々あって少し困っていたけど、scrollViewWillBeginZoomingが呼ばれた後は今のところ必ずscrollViewDidEndZoomingが呼ばれてて少し幸せになれました。

iPad実機で画像が表示されない


第一弾のアプリもだいぶできてきたので、とうとうApple Developer登録を済ませました。
例によってgoogleでやり方を調べながら、途中で時間がかかりすぎてセッションが無効になったりしながら、なんとか完了。

それでいよいよiPadでの実機テストを行ったのですが、一部の画像が表示されない!
シミュレータでは表示されるのになぜだー。

こちらの方法や
BridgeMetaware | 実機で画像が表示されない
こちらの方法
実機テストで画像が表示されない
を試してみたけどダメ。

結構長い間うんうん悩んで、、、まぁ悩んでいてもしょうがないのでダメ元でファイル名でも変更してみようと思いまして。
ファイル名を変更して、ソースファイルの画像ファイル読み込み部分も変更しようとしたら大文字の部分が小文字になってる?
これが原因?

で正しく大文字に書き直してやると無事実機でも動くようになりました。

「シミューレータでは画像ファイル名の大文字/小文字の違いは無視するけど、実機では大文字/小文字まで正しく書かないとダメ」
ってことでしょうかね。

初歩的な間違いだけどシミュレータでは平然と動いてくれるので発見が遅れてしまいました。
どうせならコンパイルの時点でエラーにするか、シミュレータでも表示しないようにしてくれると嬉しいんだけどなぁ。