60

Say I want a bundled image to take up all available screen width in an iPhone app - for example a banner. I'd create my_banner.png with width 320px, my_banner@2x.png with width 640px and my_banner@3x.png for iPhone 6 plus with width 1242px. But the resolution of iPhone 6 is 750×1334 pixels. Still it shares the @2x suffix with iPhone 4 and 5 that have 640px width.

What's the recommended way or a good way to specify an image file that has been optimised for the 750px width of iPhone 6? Seems like it cannot be done in an asset catalog? Should it be done programatically? Is there some other suffix that can be used for iPhone 6?

iPhone 4,5,6 screen sizes (Image extracted from http://www.iphoneresolution.com)

Niklas Berglund
  • 3,563
  • 4
  • 32
  • 32
  • Didn't Apple say that i6+ images are @3x? – Lior Pollak Sep 17 '14 at 13:55
  • 4
    iPhone 5 and iPhone 6 has same screen ratio. You should prepare your 2x images for the iPhone 6 resolution and it will be down sampled for the iPhone 5 without a problem. – Desdenova Sep 17 '14 at 13:55
  • @Desdenova Screen ratio isn't the issue. (The 6+ has the same *ratio* as the 6 and the 5.) The resolution is what matters here. While the best solution given current knowledge is to re-create all current 2x images using the iPhone 6 resolution and let iOS downsample them for the iPhone 5, it doesn't directly answer the question of how (if possible) to specify separate x640 or x750 image sizes for iPhone 5/6. – Craig Otis Sep 17 '14 at 14:00
  • 1
    @CraigOtis You can create multiple images in the assets catalog and use them after you check the screen bounds. But it would be unneccecary overkill in my opinion. – Desdenova Sep 17 '14 at 14:06
  • @Desdenova Ah - I think that may be the answer OP is looking for. – Craig Otis Sep 17 '14 at 14:08
  • @CraigOtis Yes, maybe. But he really shouldn't do that. – Desdenova Sep 17 '14 at 14:10
  • 2
    @Desdenova Agreed. I was hoping (for OP) that there would be some similar bonus prefix like when Apple introduced the iPhone 5. Something like: `background@2x.png` and `background-750w@2x.png` – Craig Otis Sep 17 '14 at 14:13
  • what about img-667h@2x, have you tried it? – Nik Yekimov Sep 22 '14 at 05:01
  • what did you end up using? i have this problem too :S – João Nunes Oct 13 '14 at 10:57
  • @joãonunes how you solved it? – Mário Carvalho Feb 18 '15 at 17:15
  • @MárioCarvalho I just added code for it. 2 assets in the catalog and switch between them. – João Nunes Feb 19 '15 at 09:42
  • Forgetting iPhone 6 Plus will bite. – Jonny Apr 21 '15 at 08:01
  • @Jonny there's the `@3x` suffix for iPhone 6 Plus. The problem with iPhone 6 is that it shares the `@2x` suffix with older models, even though it has higher screen resolution than that. – Niklas Berglund Apr 21 '15 at 08:10
  • I think you are mixing points and pixels up. iPhone 6 has the same resolution as iPhone 5, but a large display area. – Jonny Apr 21 '15 at 08:15
  • @Jonny no, iPhone 6 has the same _pixel density_ as iPhone 5 but not the same _resolution_. If you want an image to take upp the full width for iPhone 5 and 6 and optimize it for both devices then you'd make it 640px wide for iPhone 5, 750px wide for iPhone 6. This might help you see the what the issue is. There's a great answer by Jef below. – Niklas Berglund Apr 21 '15 at 08:42
  • In my world, pixel density is the same as resolution, so I guess we are talking about the same thing. I recommend this page: http://www.paintcodeapp.com/news/ultimate-guide-to-iphone-resolutions You can see that iPhoen 6 and iPhone 5 has the same PPI, pixels per inch, ie pixel density ie resolution. – Jonny Apr 21 '15 at 08:55
  • 1
    @Jonny it's not the same thing though. I'm not confused about the resolutions(nor PPI) of different devices. I think what's throwing you off is _rendered pixels_ vs _physical pixels_ in that table. I'm not a fan of the 1.171875 upsampling, and want to avoid it by using images optimised for the device's _actual screen resolution_. For iPhone 6 that is `750x1334`. If you know that iPhone 6 is incapable of displaying image resources at this resolution then that would be new information and make for a good answer to this question. But I don't think that's the case. – Niklas Berglund Apr 22 '15 at 05:14
  • iPhone 6 Plus does downsampling from `@3x` though. So you're right, one might want to consider the plus version as well and provide device specific images for it. – Niklas Berglund Apr 22 '15 at 05:39

12 Answers12

42

It seems to me that a lot of these answers want to address how to constrain the imageView, where I think you are concerned with loading the correct media file? I would come up with my own future extensible solution, something like this:

"UIImage+DeviceSpecificMedia.h" - (a category on UIImage)

Interface:

#import <UIKit/UIKit.h>

typedef NS_ENUM(NSInteger, thisDeviceClass) {

    thisDeviceClass_iPhone,
    thisDeviceClass_iPhoneRetina,
    thisDeviceClass_iPhone5,
    thisDeviceClass_iPhone6,
    thisDeviceClass_iPhone6plus,

    // we can add new devices when we become aware of them

    thisDeviceClass_iPad,
    thisDeviceClass_iPadRetina,


    thisDeviceClass_unknown
};

thisDeviceClass currentDeviceClass();

@interface UIImage (DeviceSpecificMedia)

+ (instancetype )imageForDeviceWithName:(NSString *)fileName;

@end

Implementation:

#import "UIImage+DeviceSpecificMedia.h"

thisDeviceClass currentDeviceClass() {

    CGFloat greaterPixelDimension = (CGFloat) fmaxf(((float)[[UIScreen mainScreen]bounds].size.height),
                                                    ((float)[[UIScreen mainScreen]bounds].size.width));

    switch ((NSInteger)greaterPixelDimension) {
        case 480:
            return (( [[UIScreen mainScreen]scale] > 1.0) ? thisDeviceClass_iPhoneRetina : thisDeviceClass_iPhone );
            break;
        case 568:
            return thisDeviceClass_iPhone5;
            break;
        case 667:
            return thisDeviceClass_iPhone6;
            break;
        case 736:
            return thisDeviceClass_iPhone6plus;
            break;
        case 1024:
            return (( [[UIScreen mainScreen]scale] > 1.0) ? thisDeviceClass_iPadRetina : thisDeviceClass_iPad );
            break;
        default:
            return thisDeviceClass_unknown;
            break;
    }
}

@implementation UIImage (deviceSpecificMedia)

+ (NSString *)magicSuffixForDevice
{
    switch (currentDeviceClass()) {
        case thisDeviceClass_iPhone:
            return @"";
            break;
        case thisDeviceClass_iPhoneRetina:
            return @"@2x";
            break;
        case thisDeviceClass_iPhone5:
            return @"-568h@2x";
            break;
        case thisDeviceClass_iPhone6:
            return @"-667h@2x"; //or some other arbitrary string..
            break;
        case thisDeviceClass_iPhone6plus:
            return @"-736h@3x";
            break;

        case thisDeviceClass_iPad:
            return @"~ipad";
            break;
        case thisDeviceClass_iPadRetina:
            return @"~ipad@2x";
            break;

        case thisDeviceClass_unknown:
        default:
            return @"";
            break;
    }
}

+ (instancetype )imageForDeviceWithName:(NSString *)fileName
{
    UIImage *result = nil;
    NSString *nameWithSuffix = [fileName stringByAppendingString:[UIImage magicSuffixForDevice]];

    result = [UIImage imageNamed:nameWithSuffix];
    if (!result) {
        result = [UIImage imageNamed:fileName];
    }
    return result;
}

@end
Jef
  • 4,728
  • 2
  • 25
  • 33
  • 1
    Exactly! Seems like there's no better way to do it currently. This is a nice way to do it programatically. – Niklas Berglund Nov 01 '14 at 06:35
  • 1
    You nailed it! Thanks! – emotality Nov 01 '14 at 09:06
  • Thanks :) One thing to note about this example is I've exposed the thisDeviceClass enum and the c function to get it in the .h file, probably on the assumption that there would be another use/need for it. Some might prefer to squirrel these things away inside the .m file, need-to-know, encapsulation, etc. Glad to be of service. May I have the bounty ?? :D – Jef Nov 01 '14 at 10:45
  • I have still issue with setting image with the above code if i have set the background image size for tabbar with the iPhone6 size and used above code to load the image it gets the image but not showing proper in background image of tabbar image name is footer_iPhone4New-667@2x and size is 750*110, but still image is not set proper, need help. – Jiten Parmar Nov 25 '14 at 07:08
  • Sorry I think that is beyond the scope of this question/answer, it sounds like you ARE getting the file you are expecting to. Perhaps UITabBar isn't prepared to handle an image of that size or it may have a bug. I suggest you ask a new question and link back to this page as something you've tried. Good luck :) ps quick suggestion, is your size {750, 110} double the size of the bar in BOTh dimensions (to account for the scale)... ? I suspect you might need to adjust the dimensions of the image you're including for this bar.. ;) – Jef Nov 25 '14 at 23:42
  • You are great :) you saved my life and it's really stupidness from Apple to leave us without support to specific devices if we want :( we face this problem in every app with background images – Boda Jan 20 '15 at 16:33
  • Do you use Image Assets with this? How do you setup all your images files with this technique? – Van Du Tran Mar 09 '15 at 14:52
  • No mate sorry, only the launch image and icon sets in the AssetCatalog have device specific slots like that, and those don't respond to +[imageNamed: ] properly (hence the question). So all this answer attempts to do is specify a 'magic-suffix' for each specific screen footprint for use with that method there, +[imageForDeviceWithName:(NSString *)fileName] So unfortunately it comes down to the file names, no nice GUI with a box for each screen. ....you could easily write that in cocoa with a NSImageWell tho, go for it ;) – Jef Mar 11 '15 at 10:03
  • @Jef i could not get this to run :/ where do i put this code and how do I actually call it? – Sebastian Flückiger Apr 21 '15 at 13:27
  • It is a category on UIImage. So make a new file, objectiveC category paste that stuff in the header and main, add the media with the appropriate suffixes for each device and call the method there – Jef Apr 21 '15 at 14:14
  • can you please let me know to how to use this category.. @Jef – bLacK hoLE Sep 23 '15 at 07:15
  • @bLacKhoLE well let us say we have an image called productImage We prepare files with the 'magicSuffix' for each screen footprint, productImage.png (first iPhones 320x480) productImage-568h@2x.png , productImage-667@2x.png etc... Then we load our image with the only method which is exposed in the header here - UIImage *appropriateProductImage = [UIImage imageForDeviceWithName: @"productImage"] – Jef Oct 03 '15 at 21:24
  • @bLacKhoLE nb- these may not neccesarily be full screen sized images, could be for any layout at all. If this seems like a cumbersome and labour intensive design then that's because it is... It's how things were done when iPad was introduced, then retina screens, then the tallboys... Obviously at some point we have too many screen footprints to support individually. The devices are fast enough now to just put a large image file in the bundle and create and cache the appropriate size on the device at runtime (like how launch images have gone with the xib for that..) – Jef Oct 03 '15 at 21:28
4

I am using the following trick as some stuff actually works:

  • Asset Catalog for specific devices
  • Specify images for 1x, 2x on the base of 320x640
  • Specify images for 4 2x and 3x on the base of 320x568 (iPhone 5)
  • Create a new Images set for the iPhone 6 specifically (as this is the only device that makes trouble with edge to edge bindings)
  • Only provide 2x image for iPhone 6 in full resolution (750x1334)

Declare a constant

#define IS_IPHONE_6 [[UIScreen mainScreen]nativeBounds].size.width == 750.0 ? true : false

and use it like this:

UIImage *image = [UIImage imageNamed:@"Default_Image_Name"];
if(IS_IPHONE_^) {
    image = [UIImage imageNamed:@"Iphone6_Image_Name"];
}

this might be not the most beautiful solution, but it works, at least as long as apple does not provide a better API for edge to edge bindings.

Sebastian Flückiger
  • 5,525
  • 8
  • 33
  • 69
2

Auto Layout is supposed to help with this situation..

Now tell me @Nicklas Berglund what would you do if the device rotates? Lets say you are in landscape mode now.. How would you fill the Horizontal space which is not in the image assets any more?

Just food for thoughts.. Auto Layout supposed to take care of your screen no matter which orientation, or which device you are running your app on..

Maybe Apple should start targeting device orientations in image assets in future?

Lets go back to your question.. The solution is to replace your @2x images with 750px wide images and then have Auto Layout do its job. Oh yea, this is the tricky part..

If you just add constraints to fit it, it will squeeze it horizontally when displayed in 4" screen, but you can use multipliers to scale the image appropriately. Here's how you can do it:

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[imageFooterView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(imageFooterView)]];

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[imageFooterView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(imageFooterView)]];


float aspectRatio = imageFooterView.frame.size.height/imageFooterView.frame.size.width;

[imageFooterView addConstraint:[NSLayoutConstraint constraintWithItem:imageFooterView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:imageFooterView attribute:NSLayoutAttributeWidth multiplier:aspectRatio constant:0.0f]];
Bms270
  • 1,586
  • 15
  • 18
1

I couldn't find a way to do it either, as I had a background Image that was perfectly sized with the Asset Catalog on every device except the iPhone 6. My fix (I did this in SpriteKit)?

if (bgNode.size.width != self.frame.size.width) {
        bgNode.texture = [SKTexture textureWithImageNamed:@"i6bg.png"];
        [bgNode runAction:[SKAction scaleXTo:self.frame.size.width/bgNode.size.width y:self.frame.size.width/bgNode.size.height duration:.1]];
    }

bgNode is the background image that is pulled up by the device. If it's an iPhone 6, it won't fit the screen and so the background image width wont be the same as the screen width. When the device is recognized as an iPhone 6, I change the texture to the R4 texture (the @2x for retina) and scale it to the correct dimensions.

I tried doing the same with the regular @2x image, but the scaled image looked very bad (it was too stretched out and noticable). With the R4 texture scaled, the proportions of width/height are a bit better and so the change isn't even noticeable. I hope this gives you some idea as to what you can do before Apple adds an iPhone 6 Asset.

Andriko13
  • 992
  • 1
  • 7
  • 34
  • 1
    Thanks. There are various workarounds. I'd probably check the width with UIScreen: `[UIScreen mainScreen] bounds].size.width` and wrap it in a function to check whether it's running on iPhone 6. Having a hard time believing Apple haven't already created a beautiful solution for this, such as iPhone 6 assets. – Niklas Berglund Sep 22 '14 at 07:58
  • 1
    Exactly. I was wondering whether my asset images had something wrong, as the iPhone 6 background image that I had wouldn't fit the whole screen. I came about the other post you made while searching google and realized I needed to apply a workaround. I'm going to change the check method to `[UIScreen mainScreen]...`, thanks for the advice! – Andriko13 Sep 23 '14 at 01:11
1

Hope this will solve all your issues related to customised edge to edge image.
Xcode 6 - xcassets for universal image support

Make sure if you are using auto layout then check pin is set to zero for all edges and constraints to margin is un checked.

You can also visit this links for launch screen images:
http://www.paintcodeapp.com/news/iphone-6-screens-demystified
http://www.paintcodeapp.com/news/ultimate-guide-to-iphone-resolutions

enter image description here

Community
  • 1
  • 1
Dhaivat Vyas
  • 2,926
  • 1
  • 16
  • 26
1

I raised the same question to apple technical support and they confirm that for fullscreen image it can't be done in asset catalog: "Currently, there is no way for the Asset Catalog to load device specific images. If your app needs to support device specific images you will need to implement your own code to detect the screen size and choose the appropriate image. You can file an enhancement request by using the following link. Be sure to explain your use case for this feature. "

Qiulang
  • 10,295
  • 11
  • 80
  • 129
0

I checked the naming convention of a launch image generated from an asset catalog via Xcode 6 and the landscape version for iPhone 6+, for example, had: LaunchImage-Landscape-736h@3x.png

Based on that, I'd presume it would be as follows, for retina devices, assuming a base file desert.png:

  • desert@2x : iPhone 4s (320 x 420)
  • desert-568h@2x : iPhones 5, 5C and 5S (320 x 568)
  • desert-667h@2x : iPhone 6 (375 x 667)
  • desert-736h@3x : iPhone 6+ (414 x 736)
  • desert@2x~ipad : iPad (1024 x 768)
Benzi
  • 2,439
  • 18
  • 25
0

There is no native Assets support for this case, so I think it would be better to do it manually as working with undocumented file names may break easily in the future.

Rivera
  • 10,792
  • 3
  • 58
  • 102
0

Just measure the device dimensions and call the image that you want. ie Do it programatically

So in your appdelegate have globals

deviceHeight = self.window.frame.size.height;
deviceWidth = self.window.frame.size.width;

that you can call repeatedly. Then check them and call the appropriate image

if (deviceWidth == 640){
image = IPHONE4IMAGE;
deviceString = @"iPhone4";
}
else...
AMAN77
  • 6,218
  • 9
  • 45
  • 60
0

In my case, I was interested in making my base view controller subclass have the same background image as my launch image.

NOTE: This approach will not work unless this is your specific requirement.

Also, even when I tried creating a background image that was the correct size for the iPhone 6 (750x1334), loading that image as a pattern image into a background color for a view ended up scaling the image up in an undesirable way.

This answer gave me the code that I needed to figure out a good solution for me.

Here's the code I got working to have my launch image match my UIViewController's background image (or vice versa):

- (void)viewDidLoad {
    [super viewDidLoad];
    UIImage *background         = [UIImage imageNamed:[self splashImageName]];
    UIColor *backgroundColor    = [UIColor colorWithPatternImage:background];
    self.view.backgroundColor   = backgroundColor;
}
- (NSString *)splashImageName {
    UIInterfaceOrientation orientation  = [[UIApplication sharedApplication] statusBarOrientation];
    CGSize viewSize                     = self.view.bounds.size;
    NSString *viewOrientation           = @"Portrait";
    if (UIDeviceOrientationIsLandscape(orientation)) {
        viewSize                        = CGSizeMake(viewSize.height, viewSize.width);
        viewOrientation                 = @"Landscape";
    }
    NSArray *imagesDict                 = [[[NSBundle mainBundle] infoDictionary] valueForKey:@"UILaunchImages"];
    for (NSDictionary *dict in imagesDict) {
        CGSize imageSize                = CGSizeFromString(dict[@"UILaunchImageSize"]);
        if (CGSizeEqualToSize(imageSize, viewSize) && [viewOrientation isEqualToString:dict[@"UILaunchImageOrientation"]])
            return dict[@"UILaunchImageName"];
    }
    return nil;
}
Community
  • 1
  • 1
mbm29414
  • 11,558
  • 6
  • 56
  • 87
0

Please try this class to change the image name programmatically.

import UIKit

class AGTools: NSObject {
    class func fullWidthImage(imageName: String!) -> String!{
        let screenWidth = UIScreen.mainScreen().bounds.size.width
        switch (screenWidth){
        case 320:
            // scale 2x or 1x
            return (UIScreen.mainScreen().scale > 1.0) ? "\(imageName)@2x" : imageName
        case 375:
            return "\(imageName)-375w@2x"
        case 414:
            return "\(imageName)-414w@3x"
        default:
            return imageName
        }
    }
}

use this method like this.

_imgTest.image = UIImage(named: AGTools.fullWidthImage("imageName"))
Aspgod
  • 11
  • 2
-2

FIRST of all, you need to configure your imageView to cover all the screen, Autolayout will help a lot for this job, take a look on the link below and find how to Pin the constraints (Leading Space, Trailing Space, Top Space and Bottom Space) using Storyboards:

http://www.thinkandbuild.it/learn-to-love-auto-layout/

enter image description here

SECOND step is create device specific image sets on your image assets (image below), to display different images according to device.

enter image description here

Check out this infographic: http://www.paintcodeapp.com/news/ultimate-guide-to-iphone-resolutions

It explains the differences between old iPhones, iPhone 6 and iPhone 6 Plus. You can see comparison of screen sizes in points, rendered pixels and physical pixels

That's all

enter image description here

enter image description here

Please, give a feedback if you have any trouble.

carantes
  • 197
  • 1
  • 9
  • Thanks, but the question isn't related to layout constrains. It's about the pixel ratio between different devices. Apple haven't provided a convenient way(similar to @2x, @3x) to specify images optimized for iPhone 6. – Niklas Berglund Nov 17 '14 at 10:34