頭と尻尾はくれてやる!

パソコンおやじのiPhoneアプリ・サイト作成・運営日記


drawRect:内でUILabelオブジェクトを作ったらダメなの?

【問題点】
UILabelで表示した文字がbackgroundからforegroundに戻ってくると、どういうわけか汚くなってしまう!
画像の下が本来のもので、上のが不具合発生時のもの。

img2


【結論】
アプリがforegroundに戻ってくる時になぜか、drawRect:メソッドが呼び出される場合があり(下のログ参照)、この場合に文字が汚くなっていました。

img1

↑アプリを終了させて、別アプリを起動ししばらくいじった後に、当アプリを再起動、の繰り返し。
アプリ終了時に"applicationDidEnterBackground"を表示、
アプリ起動時に"applicationWillEnterForeground"を表示させています。

MyView:UIViewクラスのdrawRect:メソッドでUILabelのオブジェクトを作っていたのですが、UIViewのdrawRect:に関しリファレンスには
Subclasses need not override this method if the subclass is a container for other views.

なんてあるから、UILabelのオブジェクトを作るのはdrawRect:内はよろしくないのかも。
(うん、結論なんて言ってるけどよくわかってないんだ)


結局、MyViewクラスに別なラベル作成のためのメソッドを作って、MyViewクラスのオブジェクト作成時にそのメソッドを呼び出すようにしたら不具合はなくなりました。
よくわからんが、これで行くことにしようっと。
ちなみに、そのメソッド名は"drawLabel"としてて、その場合の表示が一番最初の画像の下の文字列になります。


【ファイルの構成】
MyViewController:UIViewController
MyView:UIView
関係あるのはおなじみのこの二つ、、、
いや、ラベルで文字を書くだけなら
[myViewController.view addSubview:label]
ってな感じにすればMyViewクラスはいらないけど、まーいろいろとごにょごにょやってるので、MyViewクラスってのがあるんです。

MyViewControllerクラスのloadViewで
MyView *myView = [[MyView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.view = myView;


で、MyViewのdrawRect:内で
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 100, 320, 30)];
label.text = @"ありがとう浜村淳です 0123456789abc (drawRect:)";
[self addSubview:label];

大雑把で細かいとこまでは書いてませんが上のような構成です。
なお毎度のことながら、IBは使っていません。


【メモリ不足?】
この意図しないdrawRect:が呼ばれる時にメモリ不足の警告が出る場合も、出ない場合もあり、、、
不具合は実機でのみ確認されました(iPhoneシミュレーターで再現するかは不明)。
うーん、やっぱりなんともよくわからん。


タブ間でデータを共有する

なんで今までこの方法に気が付かなかったんだろうね、という間抜けなお話。
タブに限らず、クラス間と言い換えてもいいのかも。

今作っているのはタブを使うアプリなのですが、各タブごとにviewControllerのクラスがあります。
仮に
Tab1ViewControllerクラス
Tab2ViewControllerクラス
Tab3ViewControllerクラス
とでもします。

これらに共通のデータを持たせたい、なんて場合にどうやるのがいいんでしょうかね?

頭と尻尾はくれてやる! NSMutableStringでタブ間データのやりとり

↑あああ、前にも似たようなこと書いてますな。
この時にはBOOL値を共有させたかったけど、BOOL値はポインタじゃないのでやり方がよくわからず文字列型としNSMutableStringを使った、って話でした。

ところが作っているうちに、共有させたい変数が増えてきまして、なにかとややこしくなってきてしまいました、、、

どうしたものかと考えたあげく、共有するデータを扱うクラスを新たに作り、その中でデータを管理するようにしました。

仮にDataManagerクラスとして、HogeAppDelegate.mでそのオブジェクトを作り、、、例えば
DataManager *dataManager= [[DataManager alloc] init];
ってな感じで作り、各タブのviewControllerに参照を渡します。

そうすると、各タブに渡すのはdataManagerだけでいいし、
dataManager.isFinish = YES;
ってな感じでBOOL値も文字列型にしなくてもすんなり扱えるし。

どういうデータを共有するのかってのをこのクラスにコメント書いておけばわかりやすいし、コードもすごくすっきりしました。






どちらのクロップが速いか調べてみた

iPhone SDK開発のレシピ

iPhone SDK開発のレシピ

価格:2,520円(税込、送料別)


↑この前この本を買ったのですが、ここに画像の切り取り(クロップ)の方法が載っていました(レシピ57)。

頭と尻尾はくれてやる! png画像を扱う
ここの記事で示した方法と違ったので、どっちがいいのかなあ?
と気になったのでどちらが速いのか簡単に調べてみました。

コードの共通部分はこんな感じ。↓
UIImage *image = [UIImage imageNamed: @"ryomaden.png"];
CGSize size = CGSizeMake(150, 150);//150×150ピクセルの画像を作る
UIImage *resultImage;//ここにアウトプットの画像が入る

//計測対象部分

[resultImage drawAtPoint:CGPointMake(10,10)];//画像を表示

で、それぞれのコードはこんな感じです。↓

(1)最初に見た方法
CGRect croppingRect = CGRectMake(240, 0, size.width, size.height);
CGImageRef imageRef = CGImageCreateWithImageInRect(image.CGImage, croppingRect);
resultImage =[UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
(2)「iPhoneSDK 開発のレシピ」で見た方法
UIGraphicsBeginImageContext(size);
[image drawAtPoint:CGPointMake(-240, 0)];
resultImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
↓ちなみに元画像がこれ。

元の画像

↓出力はこんなのになります。

クロップ後の画像

なぜこの画像?とか深く考えないでください、深い意味はありません。

さて、結果。
実機(3GS、iOS4.1)で各3回ずつですが、比べてみると、、、
(1)が0.09msecくらい。
(2)が53msecくらい。
なんと、全然違うじゃないか~!

(2)はどこで時間食ってるのかな?と思って調べると
[image drawAtPoint:CGPointMake(-240, 0)];
↑ほとんどここでした、、、

どこにdrawしてるのか知らないけど(画面に表示されるわけでもないのに)時間がかかる処理なんだな。

ともかく、クロップは(1)のCGImageRefを使う方法ですることにしようっと。


png画像を扱う

pngの画像を扱う時にいろいろはまったのでメモ。

Resourcesにあらかじめpng画像を入れておいて、、、
MyViewクラス(UIViewのサブクラス)のdrawRect:メソッド内にて。


【png画像をオブジェクトにする方法】
UIImage *image = [UIImage imageNamed: @"image.png"];

こうでもいいし、

NSString *filePath = [[NSBundle mainBundle] pathForResource:@"image" ofType:@"png"];
UIImage *image = [[UIImage alloc] initWithContentsOfFile:filePath];

とすればreleaseできるので気分的にすっきりするかな。
ともかく、これでpng画像はUIImageクラスのオブジェクトにできる。

表示して確認しようと勢い余って(?)
[self addSubview:image];
↑こうすると、ビルド時に警告が出て、実行時に落ちた。

よく見れば、UIImageクラスはNSObjectのサブクラス。
そりゃだめだな、ってことで

[image drawAtPoint:CGPointMake(0,0)];

なんてすると、画面に表示してくれる。

[image drawAsPatternInRect:self.bounds];
こんな感じでdrawAsPatternInRect:メソッドを使うと画面にいっぱい並べてくれる。

png画像を並べる

↑ちなみに一つのpng画像はハート型です。
ハートがたくさん並んでもあまりハッピーな気分にはならないな(どうでもいいけど)。


【UIImageViewクラスなんてのもある】
なお、UIImageViewクラスを使って
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
[self addSubview:imageView];
このようにしても表示してくれる。
imageView.frame.size.widthなどを調べると、png画像と同じサイズのビューを作るみたい。


【マスクしてみる】
マスクを使って画像を切り抜く - 24/7 twenty-four seven

この通りにやってみたけど、マスクしてくれない、、、
仕方ないので自分でpng画像を作るところからやってみた。

いろいろ試した結果、アルファチャンネル付きだとまずいみたい。
そうでなければ、リンク先のコードでマスクはしてくれたけど、なにやらサイズが意図してないものになってる?

どうやら、オリジナルのイメージとマスク用のイメージのサイズが違った場合、マスクして出力される画像のサイズはオリジナルと変わっていない。

自分のやりたいことからすると、こりゃちょっと具合が悪い。


【クロップでいいやん】
要は、png画像から任意の位置、任意のサイズの長方形で画像を取り出したかったので、改めて調べると簡単にクロップできることがわかりました。

The Future is Now: UIImageのクロップの方法(How to crop a UIImage)

CGImageRef imageRef = CGImageCreateWithImageInRect([imageToCrop CGImage], rect);
UIImage *cropped =[UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);

↑リンク先にあるコードだけど、CGImageReleaseなんてのがあるのかー!
UIImageのオブジェクトにしたら、こうやってリリースするのがお作法みたい。


知らなくてはまった!loadViewが呼ばれるタイミング

不具合対策にえらくはまったのでメモ。

さんざんはまった過程は後にして、私が知らなかったのは
「メモリ不足が起きるとOSがviewを解放する。再度そのviewを表示するのにloadViewが呼ばれる。」
ということ。

私が書いていたコードなんですが、問題部分は以下のような簡単な画面構成でした。

tableViewControllerでテーブルを表示(画面1)
そのテーブルのセルをタップするとナビゲーションコントローラーにより遷移して別なview(画面2)を表示する
というもの。
標準アプリの「設定」など、よくある構成ですよね。

この遷移後の画面2でごにょごにょとやってるとさんざんメモリを食ったようで、
Received memory warning. Level=1
なんてデバッガコンソールに警告が表示されます。
そして、最初の画面1に戻った時に落ちるのです。

どうやら、画面2でメモリ不足になった場合、画面1のviewはなくなっちゃってるみたいです。
他にも消すとこあるんじゃないの?
どーせ戻ったら真っ先に表示する画面を消すなよ!
なんて思ったのですが、そんなこと言っても仕方ないわけで、、、
iOSは画面1のviewを解放しといて、必要なとき(画面2から1へ戻る時)に再描画するんですね。
その時に呼ばれるのが、loadViewということ。

でらうま倶楽部 : iPhone デバッグで見逃しがちなメモリ警告。実機でわざとその状況にするには?

↑ここに説明がありました。

私が最初書いたコードではそのことを考慮していなかったので、画面1を再描画しようとした時に、テーブルを描くのに必要な値を正常に得られず落ちていたのでした、、、

そーだったのかー!!!


  TopPage  



Copyright ©頭と尻尾はくれてやる!. Powered by FC2 Blog. Template by eriraha.

FC2Ad