Subclass UIImage imageNamed:? - ios

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.

Related

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.

UIImage screen size condition weird behaviour

So I need to differentiate the screen size (3.4 or 4 inch) devices in order to select the right image to display.
Here is what I did: -- from here
#define IS_PHONEPOD5() ([UIScreen mainScreen].bounds.size.height == 568.0f && [UIScreen mainScreen].scale == 2.f && UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
if(IS_PHONEPOD5()) {
self.tutorialImageView.image = [UIImage imageNamed:#"tutorialOverlay-568h#2x.png"];
} else {
self.tutorialImageView.image = [UIImage imageNamed:#"tutorialOverlay.png"];
}
Regardless of the screen size the image view always contains the larger image and extends off the screen (on the 3.5")
Am I doing something wrong, I have tried a few other things and it has always done the same thing.
EDIT: Even if I don't ever select the larger image it still is the one on screen at runtime:
self.tutorialImageView.image = [UIImage imageNamed:#"tutorialOverlay.png"];
It is still the larger image?
Have you checked UIViewContentMode of UIImageView? Try it by setting UIViewContentModeScaleAspectFit and check if it works.
don't use #2x in your image string. because every retina device will automatically take care of that.
use should save your image text like image#2x.png but i must use only without #2x extention. use properly like [UIImage imageNamed:#"image.png"]; this is for 3.5 inch.
How to use 4 inch screen use extention with -568h. Eg:
image-568h#2x.png. use like [UIImage imageNamed:#"image-568h.png"];
self.tutorialImageView.image = [UIImage imageNamed:#"tutorialOverlay-568h.png"];
more detail
https://developer.apple.com/library/ios/documentation/iphone/conceptual/iphoneosprogrammingguide/App-RelatedResources/App-RelatedResources.html
Edited:
self.navigationcontroller.navigationbar.translucent = NO;

Retina image displayed too big in retina simulator

I display a retina image (with #2x.png extension) using:
myImage = [UIImage imageNamed:#"iPhoneBackground#2x.jpg"];
UIGraphicsBeginImageContext(myImage.size);
[myImage drawAtPoint: CGPointZero];
myImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
imageView = [[UIImageView alloc] initWithImage:myImage];
NSLog(#"Dimension:%f x %f",myImage.size.width,myImage.size.height);
[self.view addSubview:imageView];
However the image is displayed twice its size on the retina simulation. Image and simulator both have 640 x 960 resolution, so I would expect the image filling the screen.
I know there are other ways than CGContext to display an image, but that's the way I would need to other purposes in my code.
Any idea why I have this definition issue ?
Don't use #2x suffix
From apple documentation:
The UIImage class handles all of the work needed to load
high-resolution images into your app. When creating new image objects,
you use the same name to request both the standard and the
high-resolution versions of your image. For example, if you have two
image files, named Button.png and Button#2x.png, you would use the
following code to request your button image:
UIImage *anImage = [UIImage imageNamed:#"Button"];
You do not need to explicitly load a retina image, #2x will be automatically appended to the image name if the device has a retina display.
Change your UIImage code to: myImage = [UIImage imageNamed:#"iPhoneBackground.jpg"];

iOS 6.1 XCode 4.6 - imageNamed never return #2x version

Both files "flipImage.png" and "flipImage#2x.png" are in project. In -[FlipsideViewController viewDidLoad] I have the following code. The sanity check (thanks to other stackoverflowers) correctly reports retina or no. But in either case, the image loaded is the small one, verified by its height. Why no auto-load of appropriate image? OK, I can see workarounds, but I'd like to Use The System when possible.
UIImage* flipimage = [UIImage imageNamed:#"flipImage.png"];
NSLog(#"Image height = %f", flipimage.size.height); // always 416, never 832 :(
// Sanity check.
if ([[UIScreen mainScreen] respondsToSelector:#selector(displayLinkWithTarget:selector:)] &&
([UIScreen mainScreen].scale == 2.0)) {
NSLog(#"Retina");
} else {
NSLog(#"Non-retina");
}
iOS retina displays don't work that way. The height of an #2x image and a standard resolution image will be the same on the device and in your code. When you create an image on the screen that is 416 points x 416 points, it doesn't change height just because it's on a retina display versus a non-retina display.
The difference is that #2x images have higher resolutions so they show more pixels per point which is what the retina displays are using instead of pixels.
So essentially, all you need to do is use the standard resolution filename for any image you use within the app and the OS will automatically take care of replacing it with the higher resolution images if it's on a retina display.
According to the previous comments... and without having to change your code a lot, why don't you just do this:
UIImage* flipimage = [UIImage imageNamed:#"flipImage.png"];
NSLog(#"Image height = %f", flipimage.size.height * [UIScreen mainScreen].scale);
That should returned you the size (Number of points * number of pixels per point).

Handling different iphone screen sizes/resolution for background images

I would like to better understand the iphone resolutions etc.
I have an application that has a basic buttonView and logoView. I have output the height of the logoView which will auto fit in height depending on screen size.
For the iphone5 I have 318 to work with.
For the iphone4(<) I have 230 to work with.
My question is, how should I handle the image used for the background of this view. Would I create one three separate images for the following?
-iphone3 etc (230)
-iphone4 retina (230 size, #2)
-iphone5 retina (328 size, #2)
Or would I create only the 2x 230 images, and can I stretch the image to 318 when an iphone5 is used and more space is available?
It all depends on your image:
If your Image can be stretched, UIImageView will do all the work.
If only a part of you image should be stretched you should use this:
imageView.image = [imageView.image resizableImageWithCapInsets:UIEdgeInsetsMake(top, left, bottom, right)];
If your image can't be stretched you should then do different images for the phones and change them in runtime.
UPDATE
For the last point you could do something like this in your viewDidLoad method:
BOOL isIPhone = [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone;
BOOL isIPhone5 = isIPhone && ([[UIScreen mainScreen] bounds].size.height > 480.0);
if (isIPhone5) {
imageView.image = [UIImage imageNamed:#"iphone4image.png"];
} else {
imageView.image = [UIImage imageNamed:#"iphone5image.png"];
}
iOS 8 has different size classes for different screens. It's explained very well here. Every iOS Developer should go through this link:
If you want to make this a little more succinct you can account for longer screen sizes with a macro.
#define ASSET_BY_SCREEN_HEIGHT(regular, longScreen) (([[UIScreen mainScreen] bounds].size.height <= 480.0) ? regular : longScreen)
Example usage:
- (void)viewDidLoad
{
[super viewDidLoad];
self.splashScreenImageView.image = [UIImage imageNamed:ASSET_BY_SCREEN_HEIGHT(#"Default", #"Default-568h")];
}

Resources