2015年10月27日

【XCode】UIImageで画像を読み込む際、端末にあわせた画像を読みたい

前回端末毎の解像度の話をしました

ここで気になるのは
NavigationBarに指定する画像や
フルスクリーンぴったりの一枚の背景画像を使用したいとき、
解像度ごとに用意し、画像の読み込みを切り替える必要が有ること

たとえばbg.pngという背景一枚絵の画像が320x480であるとし
iPhone4用にはbg.@2x.pngの640x960となりますね

これだけであれば

[UIImage imageNamed: @"bg.png"];

で読み込むことが出来ますが
iPhone5以降の端末では高さが足りませんし、
iPhone6以降では幅も足りません

AutoLayoutで引き伸ばししたところで解像度が保たれないためよくありません

bg-568h@2x.png 640x1136
bg-667h@2x.png 750x1334
bg@3x.png 1242x2208

と用意し、UIImageで読み込む前にスクリーンサイズをチェックして
必要なファイル名を指定して読み込むという手間が必要になります

float h = [UIScreen mainScreen].bounds.size.height;
float scale = [UIScreen mainScreen].scale;
NSString* imageName = @"bg.png";
if(scale >= 3.0f){
 // iPhone6Plus
 imageName = @"bg.png";
}
else if(h >= 667.0f){
 // iPhone6
 imageName = @"bg-667h.png";
}
else if(h >= 568.0f){
 // iPhone5
 imageName = @"bg-568h.png";
}

UIImage image = [UIImage imageNamed: imageName];

めんどくさい!



そこで便利なクラスを作成されたライブラリを使います



このクラスを使うと

[UIImage imageNamed: @"bg.png"];

と明記するだけで
端末と現在の向き(PortraitやLandscape)にあわせて
必要な画像ファイル名を生成して読み込んでくれるカスタムクラスになります

あらかじめ

bg.png
bg@2x.png
bg-568h@2x.png
bg-667h@2x.png
bg@3x.png
bg-512h.png(iPad 非Retina)
bg-1024h@2x.png(iPad Retina)

と用意しましょう

あとは [UIImage imageNamed: @"bg.png"]; とコードを書くだけで最適な画像を読み込んで表示されます

Landscape状態であれば

-l

をつけるルールになっています

bg-l.png
bg-l@2x.png
bg-320h-l@2x.png
bg-375h-l@2x.png
bg-l@3x.png
bg-384h-l.png
bg-768h-l@2x.png

となります
hの数値が横画面時の高さになっているので注意ですね


しかし
問題点がいくつかありました


1.iOS7で動かすと@2xファイルの読み込みがRetina扱いにならない

これはどういうことかといいますと
bg.pngと指定するとRetina端末であればシステム側が@2xとついたファイルがあれば
それはRetina用として読み込みます

bg@2xが640x960のサイズの画像ですが、
プログラムで読み込んでサイズを調べると320x480と半分のサイズと認識されます
つまりRetina用として読み込むと半分のサイズとして扱われ高解像度の画像が表示されるわけです

これがiOS8〜であれば [UIImage imagerNamed: @"bg@2x.png"]; と読み込んでもRetina扱いになってくれるようですが
iOS7ではbg@2x.pngという画像を読み込み、bg@2x@2x.pngがないからRetina用の画像じゃないという扱いになるようです

対策としては

+ (UIImage *)imageNamed:(NSString *)imageName withTransitionSize:(CGSize)size

このメソッドの最後の
if ([[NSBundle mainBundle] pathForResource:imageNameMutable ofType:@""]) {
    return [UIImage dynamicImageNamed:imageNameMutable];
}

ここのreturnの前に
[imageNameMutable replaceOccurrencesOfString:@"@3x" withString:@"" options:NSBackwardsSearch range:NSMakeRange(0, [imageNameMutable length])];
[imageNameMutable replaceOccurrencesOfString:@"@2x" withString:@"" options:NSBackwardsSearch range:NSMakeRange(0, [imageNameMutable length])];

を追加しましょう

@2x、@3xという文字列を削除して読み込み開始を行います
iPhone6PlusはiOS8〜なので意味はありませんが、統一のために追記しました



2.Storyboardから指定したimageの読み込みが対応できていない

カスタムクラスのloadメソッドで定義されている処理は
プログラムソース上から imageNamed: で実行したときのみの適用になっています
Storyboardやxib上でImageViewやボタン画像などイメージを直接指定した場合、判別処理が実行されず
いままでと同じ問題にぶつかります

なのでこの場合からでも同じように処理するように手続きを行います。
- (id)initWithCoderDynamic:(NSCoder *)aDecoder {
    NSString *resourceName = [aDecoder decodeObjectForKey:@"UIResourceName"];
    return [UIImage imageNamed: resourceName];
}

Storyboardからイメージを直接指定された場合、initWithCoderがコールされます
このメソッドの代替を定義し、imageNamed:を実行するようにしましょう

そしてloadメソッドに以下を追加します
Method m1 = class_getInstanceMethod(NSClassFromString(@"UIImageNibPlaceholder"), @selector(initWithCoder:));
Method m2 = class_getInstanceMethod(self, @selector(initWithCoderDynamic:));
method_exchangeImplementations(m1, m2);

dispatch_once(&onceToken, ^{}の中に追加すればOKですね

これでStoryboardからでも判別処理が行われるようになりました


3.ルールが2種類あって命名が大変問題

ご丁寧に -l ルールがあり、Landscape状態でも最適な画像を同じファイル名で行える便利なクラスでは有りますが
縦画像は縦の命名、横画像は横の命名ということでだいったい別の名前になってると思います
(私はそんな運用方法)

なので -320h-l という指定する数値の種類が多くなると混乱するので
Portraitルールだけに固定しようとおもいます。

+ (NSString *)horizontalExtensionForHeight:(CGFloat)h width:(CGFloat)w

まずこのメソッドをコメントアウトします
横画面時のルールを定義してる箇所ですね

次に以下のメソッドの中でエラーがでてるので対応します

+ (UIImage *)imageNamed:(NSString *)imageName withTransitionSize:(CGSize)size

先ほどのコメントアウトしたところを以下のように書き換えます
extension = [self horizontalExtensionForHeight:size.height width:size.width];
extension = [self verticalExtensionForHeight:size.width width:size.height];

ポイントはsizeのwidthとheightを横用のサイズになっているため、入れ替えることですね

これで画像の命名ルールは

bg.png
bg@2x.png
bg-568h@2x.png
bg-667h@2x.png
bg@3x.png
bg-512h.png(iPad 非Retina)
bg-1024h@2x.png(iPad Retina)

に固定できました


posted by ヒイロ at 10:29| 福岡 | Comment(0) | TrackBack(0) | プログラム | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
この記事へのトラックバックURL
http://blog.seesaa.jp/tb/428568263

この記事へのトラックバック
×

この広告は1年以上新しい記事の投稿がないブログに表示されております。