I'm developing Tab bar Application and found strange issue on iPhone 6
Notice that image is not wide enough for full screen but repeated.
I have set image through both storyboard and programmatically
I have also have 2x and 3x images placed in Images.xcassets
For any one who comes here later, here is what i do
After Some tries i've used a hack as follow
Do not used images from assets instead place them as files (we do before assets)
Check if we have iPhone 6 and user #3x image, (i was not sure why it was not picked by assets but it do in this manner)
Following is code
#define isIPhone6 [[UIScreen mainScreen] bounds].size.height > 568 ? YES : NO
UITabBar *tabBar = tabBarController.tabBar;
UIImage* tabBarBackground = [UIImage imageNamed:#"tab_bar1.png"];
if (isIPhone6) {
tabBarBackground = [UIImage imageNamed:#"tab_bar2#3x.png"];
}
[tabBar setBackgroundImage:tabBarBackground];
Xcode offers asset buckets for 1x, 2x, 4" 2x, and 3x assets. My fullscreen assets are all wonky with this configuration, because the 2x bucket is used for 3.5" and 4.7" screens. Currently I have a UIImage category that introspects the current screen size, and selects a "*-3.5" asset if the device seems to be 3.5".
This is clunky and not cool. Seeing that Xcode caters to all the different device sizes for their LaunchImage asset, I was hoping there was some way to supply device specific assets for non launch image assets without resorting to code.
I have reported a bug to Apple about this since mid November 2014 and I just noticed they marked it as No Value... which gives me the impression Apple has omitted an additional slot for iPhone 6 on purpose. My guess is they now want the 2x slot to be used for iPhone 6, and maybe they're reducing support to iPhone 4s as it's getting old.
If you really want to keep supporting the iPhone 4s, I'd suggest to use iPhone 6 sized images in the 2x slot, and then use the following method to load your images:
+(UIImage *)loadImageNamed:(NSString *)imageName
{
CGSize screenSize = [UIScreen mainScreen].bounds.size;
CGFloat screenHeight = MAX(screenSize.width, screenSize.height);
CGFloat const IPHONE_4_SCREEN_HEIGHT = 480;
UIImage *image = [UIImage imageNamed:imageName];
if(screenHeight == IPHONE_4_SCREEN_HEIGHT) {
CGFloat const xScale = .85333333;//x conversion ratio from iPhone 6's 375 pts width screen to iPhone 4's 320 pts width screen
CGFloat const yScale = .71964018;//y conversion ratio from iPhone 6's 667 pts height screen to iPhone 4's 480 pts height screen
CGSize newSize = CGSizeMake(xScale * image.size.width, yScale * image.size.height);
UIGraphicsBeginImageContextWithOptions(newSize, NO, 0);
[image drawInRect:(CGRect){0, 0, newSize}];
UIImage *resizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return resizedImage;
}
return image;
}
Resizing a big image (iPhone 6) to a smaller one (iPhone 4) doesn't lose much quality even if the aspect ration is not the same.
Using an existing project when moving to Xcode 6 and iOS 8 creates some initial issues. I solved this by changing the image set from Device specific to Universal. This removes the R4 image option and leaves you with #1x, #2xand #3x. If you use for example a background image that needs to fit the screen then I will recommend that you find an image that does not have to fit 100 % perfect to look good, and set image to be displayed as Aspect Fill.
I have the same problem and got confirmation from apple that I have to wrote code for that. For example you can try like this Fullscreen images on iPhone 6 with Asset Catalogs
But I actually went lazy about it by using one 2x for 4" & 4.7" screen and another 2x for 3.5" screen
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.
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];
}
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.