Changing background image for multiple platforms iOS - ios

I'm still getting my feet wet when it comes to mobile development.
So I'm working on a .xib that has just a plain view controller in it. My graphic designer gave me 6 different background images as separate files to use. One for iPhone 4s, one for 5&5s, one for 6, one for 6+, one for non-retina iPads, and one for the retina iPads. The .xib and the images are in a CocoaPod that functions as a shared library between a few different apps that we make.
My question is this: How do I correctly determine which image to set as the background since the devices that the app can be run on all require different images?
What I've tried so far:
To get the device
-(NSString*)deviceName {
struct utsname systemInfo;
uname(&systemInfo);
//I set deviceType to the following return
return [NSString stringWithCString:systemInfo.machine
encoding:NSUTF8StringEncoding];
}
And then quite a few if statements to set the background image:
if ([deviceType compare:#"iPhone3, 1"] == 0) {
[self.view setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageNamed:#"iPhone_4"]]];
}
else if ([deviceType compare:#"iPhone5, 4"] == 0) {
[self.view setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageNamed:#"iPhone_5"]]];
}

If you want specify different images for different screen sizes, one of solutions is name image with screen size height in the end.
For example image "DetailsBackground"
For iPhone 4, 4s - "DetailsBackground480"
iPhone 5, 5s - "DetailsBackground568"
iPhone 6 - "DetailsBackground667"
and so on
And then in code just load image according to your screen bounds:
NSString *imageName = [NSString stringWithFormat:#"BackgroundImage%.0f.png", [UIScreen mainScreen].bounds.size.height];
UIImage *backgroundImage = [UIImage imageNamed:imageName];
self.view.backgroundColor = [UIColor colorWithPatternImage:backgroundImage];
Another solution is using Image Assets.
But it more suitable to portrait/landscape and iPhone/iPad There you can use "Device Specific" + compact\regular\any widht+height in the image properties.
Please check attached screenshot:

Related

What is the code when I want to show a #2x image in app

Today when I search some information about #1x #2x #3x
something confuses me
are these only used as app icon? Because most messages always give me the example of the icon.
and If it can be used in app view, how is the code?
like this?
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed: #"background.png"]];
Then, iOS will change #1x and #2x itself? OR I need to code like this:
#define deviceIsIPhone5 ([UIScreen mainScreen].applicationFrame.size.height == 568 ? YES : NO)
if (deviceIsIPhone5)
{
self.backgroundImage.image = [UIImage imageNamed:#"background#2x.png"];
}
else
{
self.backgroundImage.image = [UIImage imageNamed:#"background.png"];
}
You don't have to think about #2x #3x.iOS automatically handles it.
All you need to care about is to put right size image into the right box(.xcassets) if you want to show image properly in all iOS devices.
no need to care about 2x,3x and png.
just enter image name only. ios will automatically check image to use according to screen dimensions.
self.backgroundImage.image = [UIImage imageNamed:#"background"];

How to specify size for iPhone 6/7 customised edge-to-edge image?

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?
(Image extracted from http://www.iphoneresolution.com)
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
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.
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]];
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.
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
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. "
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)
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.
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...
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;
}
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"))
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/
SECOND step is create device specific image sets on your image assets (image below), to display different images according to device.
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
Please, give a feedback if you have any trouble.

Should a universal app have 4 copies of each image?

In a universal app should I have a image.png, image#2x.png, image~ipad.png and image#2x~ipad.png for every image in my project?
Or should I just use my wider iPad images and have iOS scale them down to iPhone for me?
There's a lot of images so I'm a bit concerned about the file size...
Thanks!
Not necessarily but you can see code below. If you are targeting low res iPads and Low res iPhones you would have one set of icons for each. So if you are targeting iPad retina and iPhone retina that is on set of icons too so if you have an image called car.png and car#2x.png you would have 2 icons that cover all 4 models mentioned.
You can of course have images specific for iPad as the screen is larger then in your logic you would show either or depending on the device idiom..like below
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
//Device is iPhone
//This would give you low res icon for iPhone and if device is retina system will show it
UIImage *car = [UIImage imageNamed: #"Car.png"];
UIImageView *carImage = [[UIImageView alloc]initWithImage:car];
}
else {
//Device is iPad
//This would give you low res icon for iPad and if device is retina system will show it
UIImage *carLarge = [UIImage imageNamed: #"CarLarge.png"];
UIImageView *carImage = [[UIImageView alloc]initWithImage:carLarge];
}

Xcode 4.5 background image iPhone 4, 4s, 5

I have in my viewController.m written the background code:
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"image.png"]];
And I have the correct names of the different pictures:
image.png for non-retina display (320x480)
image#2x.png for retina display (640x960)
image-568h#2x.png for iPhone 5 (640x1136)
But when I run it in the simulator it does not take the image-568h#2x.png for iPhone 5 screen it only takes the image#2x for 4s screen and scale it to fit the screen... I dont know if there is any coding to use the image-568h#2x for iPhone 5 screen?
Im using Xcode 4.5
iPhone 5 is retina, just like iPhone 4 and 4S, and the #2x-image will be used automatically for all these devices. It's only the startup-image that is called "-568h#2x" for iPhone 5. You need to write some code to use a different image, something like this would work:
NSString *filename = #"image.png";
CGRect screenRect = [[UIScreen mainScreen] bounds];
if (screenRect.size.height == 568.0f)
filename = [filename stringByReplacingOccurrencesOfString:#".png" withString:#"-568h.png"];
imageView.image = [UIImage imageNamed:filename];
if you are trying to use [UIImage imageNamed:#"image.png"] and expect image-568h#2x.png to be picked automatically from the bundle for iPhone 5, it will not work.
Automatic picking works only for iPhone 4 and 4S.
Only the Default image named as Default-568h#2x.png will be picked automatically in iPhone 5.
for normal images, if you have separate image for iPhone 5, try using this code
CGRect screenBounds = [[UIScreen mainScreen] bounds];
if (screenBounds.size.height == 568) {
// code for 4-inch screen
} else {
// code for 3.5-inch screen
}
I believe it is incorrect to assume that you can apply the -568h#2x trick to all image files. I think it only works for Default-568h#2x.png. This is the file that iOS looks for at app launch on a 4" display device, as well as the "flag" to enable 4" display support in the SDK. For example, once you include this specific file, your table views will fill the screen.
I have not read anything to suggest that you can simply provide any image with the -568h#2x file name component and have it be used automagically. You'll have to do that yourself based on the screen size, e.g. [UIScreen mainScreen].bounds.size.height.

Subclass UIImage imageNamed:?

I am on my voyage to supporting the iPhone 5. I am well aware that there is iOS 6's auto layout feature in Interface Builder. This is great however, sometimes I want to load my own images if its an iPhone 5.
So some of the images in my game for iPhones are exactly the screen's size either (320x480) or (640 x 960). So if I am on an iPhone 5 those images will not cover the entire screen. So I am going to make (640 x 1136) images for those full screen images.
Now I know that if I simply name the image: MyImage-568h#2x.png it won't load. So, how would I subclass UIImage so that it will load the -568h#2x image thats not the default image?
Thanks!
The lazy way:
UIImage * img = [UIScreen mainScreen].bounds.size.height > 500 ? [UIImage imageNamed:#"Default-568h.png"] : [UIImage imageNamed:#"Default.png"];
I've done this in an app where the main screen on devices without a camera is Default.png; it doesn't seem too hacky because we would need to change Default.png to support new devices anyway.
The less lazy way: Condition on the space you need to fill, not the screen size.
The solution you asked for isn't really generic enough to warrant a class, but here it is anyway:
#implementation MyImage
+(UIImage)imageNamed:(NSString*)name {
if ([UIScreen mainScreen].bounds.size.height > 500) {
UIImage * img = [UIImage imageNamed:[name stringByAppendingString:#"-iPhone5"];
if (img) { return img; }
}
return [UIImage imageNamed:name];
}
#end
There's really no need to subclass UIImage. If you're feeling really hacky, you can patch/swizzle/otherwise +[UIImage imageNamed:] to point to a custom method, but I really don't recommend that except for debugging.

Resources