頭と尻尾はくれてやる!

iOSアプリなどの開発日記です


複数のUIImageオブジェクトから1つのUIImageオブジェクトを作る

複数のUIImageオブジェクトがあって、それらから1つのUIImageオブジェクトを作成したかったのですが、どうしたものか?

最初、各UIImageオブジェクトからそれぞれUIImageViewオブジェクトを作成、それらをUIViewオブジェクトに乗せて、そこからrenderInContext:とかでUIImageオブジェクトを得る、などと考えていたのですが、もう少し単純な方法がありました。

次のコードは'image1'と'image2'から'image'を作っています。
UIImage *image1 = [UIImage imageNamed:@"image1.png"];
UIImage *image2 = [UIImage imageNamed:@"image2.png"];

UIGraphicsBeginImageContext(CGSizeMake(width, height));
[image1 drawAtPoint:CGPointMake(0, h1)];
[image2 drawAtPoint:CGPointMake(0, h2)];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

例えば、image1がこれ :

画像1

image2がこれとします :

画像2

合成したのがこれ :

合成した画像

これ、1つのUIImageオブジェクトから作ったUIImageViewオブジェクトを表示しています。
と言っても本当にそうかよくわからない画像ではありますが。


なぜかUITextViewに影ができない

UITextViewのオブジェクトに影を作ろうとしたのですが、なぜか表示されないのです。 コードはこんな感じ :
//UITextView
UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(40 , 10 , 240 , 100)];
textView.backgroundColor = [UIColor orangeColor];
[self.view addSubview:textView];
[textView release];
textView.text = @"UITextView object";
textView.layer.shadowOpacity = 0.9f;

//UIView
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(40, 160, 240, 100)];
view.backgroundColor = [UIColor orangeColor];
[self.view addSubview:view];
[view release];
view.layer.shadowOpacity = 0.9f;
このコード後半部分は比較のため、UIViewで四角を作ってそれに影を付けています。 UITextViewのsuperclassをたどって行くとUIViewがいるのでこれで問題ないかと思ったのですが、結果は以下の画像のようになりました :

UITextViewに影をつける 1

なぜか上のUITextViewの方は影が表示されない。

いろいろ試してみたら次のプロパティを設定するとOK :
textView.layer.masksToBounds = NO;

UITextViewに影をつける 2

通常このプロパティはデフォルトでNOのはずなのですが、UITextViewだとYESになっていました。


なぜかrenderInContext:でコールされるCATiledLayerの描画部分

「赤ちゃんの成長グラフ」アプリのスクリーンショットをアプリ内で作成しようとしたけど、なぜかうまくいかないのでいろいろと調べてみました、というお話。

拡大したグラフ

このアプリで拡大したグラフを表示するのにUIScrollViewとCATiledLayerクラスを使っています(拡大前は単なるUIViewオブジェクトを表示)。
拡大するのをUIScrollViewクラスが担当。
グラフは複数の"タイル"に分けられていて、表示すべきタイルごとにCATiledLayer内のdrawRect:がコールされます。

スクショ撮影の流れはこんな感じ:
CGRect rect = [[UIScreen mainScreen] bounds];  
UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0);    

UIApplication *app = [UIApplication sharedApplication]; 
[app.keyWindow.layer renderInContext:UIGraphicsGetCurrentContext()];

UIImage *image = UIGraphicsGetImageFromCurrentImageContext();  
UIGraphicsEndImageContext();

ここでrenderInContext:というメソッドがありますが、これを実行するとなぜかCATiledLayer内のdrawRect:メソッドが1度だけコールされているのです。
他のUIViewクラスのdrawRect:等はコールされないのに(上記画像を描くのにCATiledLayerだけでなくUIViewクラスのレイヤーなんかもあったりします)です。
どうもCATiledLayerについてはここで再描画されており、その画像がスクリーンショットに使用されていました。

不思議なのは複数のタイルで描かれていても、なぜかコールは1回だけで、しかもdrawRect:の引数となるrect(つまり表示エリア)が表示されているエリアそのまんま、という当方の意図していない値(もともとはタイルごとに描いていたわけだから)なので正常に描くことができませんでした。

これがバグなのか仕様なのかよくわかりませんが、とりあえずコールされるのが1回、引数のrectがどういうものかわかったので、CATiledLayer内のdrawRect:メソッド内をスクショ撮影時には別処理にして描くことで対応しました。





画像に影を付けたいのでいろいろプロパティをいじってみた

影のある画像
↑こういうボタンの画像に影がありますが、こういうのを作りたくなったのでいろいろと試してみました(もちろんこの画像がどのように作成されているのかはわかりません)。

使った画像がこれ→基本となるpng画像
円の外側が透過したpng画像です。

png画像を表示する基本的なコードはこちら :
UIImage *image = [UIImage imageNamed:@"image.png"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
imageView.frame = CGRectMake(xx, yy, image.size.width, image.size.height);
[self.view addSubview:imageView];
[imageView release];

もちろんこれだけでは影はできません。
影を付ける方法はいくつかあるようですが、ここではCALayerクラスのプロパティをいじってみました。

(1) shadowOpacity
この状態から、shadowOpacityプロパティのみ次のように設定してみました :
imageView.layer.shadowOpacity = opacity;
結果は次の通り :

shadowOpacity

なるほど、この値を設定するだけで影ができてる、、、一撃じゃないかっ!
なお、この値のデフォルト値はゼロです。
それで、何もしない場合は影ができないんだな。


(2) shadowOffset
このプロパティはCGSize型なので次のように設定できます :
imageView.layer.shadowOffset = CGSizeMake(w, h);
結果はこんな感じ :

shadowOffset
*shadowOpacity = 0.5

ほー、確かに影の位置が変わってら。
なお、このプロパティのデフォルトは(0.0,-3.0)。


(3) shadowRadius
なんで影に"半径"の設定があるんだよ?と思ったら違うんですね。
ぼかし(blur)の効果に影響する値なんですね。
このプロパティは次のように設定します :
imageView.layer.shadowRadius = radius;
この値を変えるとほれこの通り :

shadowRadius
*shadowOpacity = 1.0
*shadowOffset = (0, 5)

確かにぼけ具合が変わりますな。
デフォルトで3.0です。


(4) shadowColor
このプロパティで影の色も変えることができるんですね。
コードでは次のように設定します。UIColorじゃありません :
imageView.layer.shadowColor = [UIColor redColor].CGColor;

shadowColor
*shadowOpacity = 1.0
*shadowOffset = (0, 5)
*shadowRadius = 3.0

なるほど。でも赤い影は使わないか。
デフォルトは黒色。


(5) masksToBounds
このプロパティをセットしているコードを見かけたので試してみました。
コードはこんな感じ :
imageView.layer.masksToBounds = YES;
結果がこれ :

masksToBounds
*shadowOpacity = 1.0
*shadowOffset = (0, 5)
*shadowRadius = 3.0

なるほど、そいういうことか。 自分の場合、画像の枠外にも影を描いて欲しいからNOでいいわけだけど、デフォルトでもともとNOなのでスルーでOKだな。


renderInContext:メソッドはマスクを無視する

iphone - Taking a screenshot in-app then putting it in a UIImageView does so at normal resolution - Stack Overflow

このあたりのコードを参考にして、コード上でスクリーンショットを作ろうとしたのですが、いざやってみるとこんな風になってしまうのです。

マスクが無視されているスクリーンショット

グラフ外枠の右側にもなぜかプロットされているのですが、ここ元々はマスクされている部分なのです。
なんでだー?といろいろと試行錯誤をしたのですがどう頑張ってもダメ。

いろいろ調べてたどり着いたのがCALayerクラスのrenderInContext:メソッドはマスクは無視しちゃうよってこと。

リファレンスに書いてました。

Important: The Mac OS X v10.5 implementation of this method does not support the entire Core Animation composition model. QCCompositionLayer, CAOpenGLLayer, and QTMovieLayer layers are not rendered. Additionally, layers that use 3D transforms are not rendered, nor are layers that specify backgroundFilters, filters, compositingFilter, or a mask values. Future versions of Mac OS X may add support for rendering these layers and properties.


いつかサポートするかもしれないよって言われてもなあ、、、orz



実はこんな方法が、、、 : masksToBoundsでもマスクできる!





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