24/7 twenty-four seven

iOS/OS X application programing topics.

座標の指定が整数値でない場合 UIKit の描画(ビュー、画像、ボタン、その他いろいろ)がぼやける

iPhone および iPad で開発をしているとき、たまに画像や文字がぼやけてしまう現象にあったことはないでしょうか。
同じ画像を表示していても特定の場合だけぼやけるとかそういう場合は、だいたい座標の指定が小数になってしまっていることが原因です。


この現象は画像や文字に限らず、UIKit を使って描画するありとあらゆるものに当てはまります。
また、直接座標の数値を指定していなくても、間接的に位置に影響するプロパティを変更したりする場合でも起こりますので、知らないうちになっている場合も多く、厄介な問題です。


まず、ラベル (UILabel) の例を下記に示します。
1番上だけが正常で下2つのラベルは文字がぼやけているのが分かります。


1番上のラベルについてのコードです。CGRect の座標の値に注意して下さい。

label = [[UILabel alloc] initWithFrame:CGRectMake(10.0f, 10.0f, 300.0f, 60.0f)];

label.textAlignment = UITextAlignmentCenter;
label.text = @"Open and Share";
label.font = [UIFont fontWithName:@"TimesNewRomanPSMT" size:36.0f];
[self.view addSubview:label];
[label release];


2番目のラベルについてのコードです。frame.origin.x の値を小数にしています。
他に違いはないので原因は座標の値にあることが分かります。

label = [[UILabel alloc] initWithFrame:CGRectMake(10.5f, 80.0f, 300.0f, 60.0f)]; // orgin.x が小数!

label.textAlignment = UITextAlignmentCenter;
label.text = @"Open and Share";
label.font = [UIFont fontWithName:@"TimesNewRomanPSMT" size:36.0f];
[self.view addSubview:label];
[label release];


3番目のラベルについてのコードです。
最初に与えた frame の座標は全て整数値ですが、center の値を変更したことで(width が奇数のため)origin.x の値が小数になってしまいます。
位置に影響する別のプロパティを変更したことで、間接的に frame の座標に小数が設定されてしまう例です。

label = [[UILabel alloc] initWithFrame:CGRectMake(10.0f, 80.0f, 301.0f, 60.0f)];
label.center = CGPointMake(160.0f, 180.0f); // center を変えた場合、幅や高さが奇数なら x, y が小数になる可能性がある

label.textAlignment = UITextAlignmentCenter;
label.text = @"Open and Share";
label.font = [UIFont fontWithName:@"TimesNewRomanPSMT" size:36.0f];
[self.view addSubview:label];
[label release];

この例ではわざと frame.size.width を奇数にしていますが、画像などの場合サイズが奇数ということはよくありますので、画像をセンタリングして表示する場合など注意する必要があります。
特に、画像を外部からダウンロードする場合など、サイズがあらかじめ決まっていない場合には特に気をつけないといけません。


では次に画像を表示する例です。
左側の画像は正常に表示されていますが、右側の画像はぼやけてしまっていますね。


左の画像を表示するコードです。

UIImageView *imageView = nil;
imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Icon.png"]];
imageView.frame = CGRectMake(10.0f, 10.0f, 57.0f, 57.0f);
[self.view addSubview:imageView];


右の画像を表示するコードです。
先のラベルの例と同じく、幅や高さが奇数のビューに対して、center を変更したことで、x, y 座標が少数になってしまいました。

imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Icon.png"]];
imageView.center = CGPointMake(160.0f, 38.0f);
[self.view addSubview:imageView];


次はテキストフィールド (UITextField) です。
下のテキストフィールドに入力された文字(東京ディズニーランド)はぼやけてしまっています。


それぞれコードは下記になります。

[inputField setContentVerticalAlignment:UIControlContentVerticalAlignmentBottom];
[inputField setContentHorizontalAlignment:UIControlContentHorizontalAlignmentLeft];

[inputField setText:[NSString stringWithUTF8String:"セルリアンタワー"]];
[inputField setContentVerticalAlignment:UIControlContentVerticalAlignmentCenter];
[inputField setContentHorizontalAlignment:UIControlContentHorizontalAlignmentLeft];
		
[inputField setText:[NSString stringWithUTF8String:"東京ディズニーランド"]];

今回の原因は、contentVerticalAlignment プロパティの指定により、テキストの描画位置の座標が奇数になってしまうことによります。
自動で調整して欲しいから、フレームワークに任せているのにそれは無いんじゃないかと思いますが、そういう仕様なので仕方ないです。


このように、座標の値がまったく出てこないにも拘らず、知らず知らずのうちに現象が発生する可能性があるのがこの問題の厄介なところです。


例としてビューとテキストフィールドについて挙げましたが、ビューの配置だけでなく、ボタン (UIButton) そのほかあらゆるオブジェクトの描画で同様です。
drawRect: で直接描く場合も同様です。drawRect: や drawAtPoint: など、描画系のメソッドの引数の座標が小数ならぼやけて描画されます。


特定の場合の対策としては、座標を計算で求めて指定するような場合は、いったん int にキャストしてしまうのが簡単です。
例えば、センタリングするために次のような計算で座標を求めることがあります。
(全体の幅からビューの幅を引いて半分にする)

view.frame = CGRectMake((screenWidth - view.frame.size.width) / 2, (screenHeight - view.frame.size.height) / 2, view.frame.size.width, view.frame.size.height);

この計は幅か高さが奇数になったら x か y が小数になるので、以下のように int にキャストして整数にしてしまいます。

view.frame = CGRectMake((int)((screenWidth - view.frame.size.width) / 2), (int)((screenHeight - view.frame.size.height) / 2), view.frame.size.width, view.frame.size.height);


※追記
http://d.hatena.ne.jp/KishikawaKatsumi/20100527/1274910461#c:titl=コメントで教えてもらいました (Thanks! > digdog)。
CGRectIntegral (CGRect rect) で、CGRect の値を整数値に丸めてくれるようです。


画像のコードを使って調べてみました。

imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Icon.png"]];
imageView.center = CGPointMake(160.0f, 38.0f);
NSLog(@"%@", NSStringFromCGRect(imageView.frame));
NSLog(@"%@", NSStringFromCGRect(CGRectIntegral(imageView.frame)));

NSLog による出力は以下です。
確かに整数値に丸まっていますね。この例の場合、幅と高さが 57 > 58 になってるのでちょっと注意する必要がありますが、便利ですね。

[88160:207] {{131.5, 9.5}, {57, 57}}
[88160:207] {{131, 9}, {58, 58}}


また位置に影響するプロパティを操作するときは注意することです。
frame だけでなく、 center, contentVerticalAlignment, contentHorizontalAlignment など 間接的に frame に影響するプロパティを知っておきましょう。


とはいえ根本的な対策としては特に無いので気をつけるしかないです。
実機でもシミュレータでも同様に発生するので、注意して確認することが必要です。