cocos2d and the new retina iPad - take 2
I described in a previous post about cocos2d and the new retina iPad a quick solution to the problem of supporting higher-resolution images. The main objective of that work was to make easier re-building your app for submission on the App Store before you could possibly provide higher-resolution images for the new iPad. I mentioned anyway that that approach was just a quick hack and not a full answer to the need to provide 4 (four!) different versions for any of your artworks.
Now, I would like to quickly describe a different approach aimed at reducing that number to 2 (if you are willing to discontinue support for older iPhones and iPods, up to the iPhone 3GS/iPod Touch 3G). This is possible by:
- providing a “@2x” (aka “-hd”) version;
- providing a “@2x-ipad” (aka “-hd-ipad”) version;
- making the non-retina iPads (iPad 1 and 2) use the “@2x” version by default.
This is made possible through a new category I wrote that you can add to your project to transparently get the behavior described above.
The code
As you can see, the changes are pretty trivial. The only thing you have to do is inspect the two constants defined at the beginning of the file to check that file suffixes are ok with your conventions.
[sourcecode]
//
// CCFileUtils+SDSDeviceSuffix.m
// MantraPhoneTest
//
// Created by sergio on 3/19/12.
// Copyright 2012 Sergio De Simone, Freescapes Labs. All rights reserved.
//
#import "cocos2d.h"
#define CC_IPAD_DISPLAY_FILENAME_SUFFIX @"~ipad"
#define CC_RETINA_IPAD_DISPLAY_FILENAME_SUFFIX @"-hd~ipad"
#ifdef CC_RETINA_IPAD_DISPLAY_FILENAME_SUFFIX
//////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
@implementation CCFileUtils (SDSDeviceSuffix)
//////////////////////////////////////////////////////////////////////////////////////////////
+ (NSFileManager*)localFileManager {
static NSFileManager *__localFileManager = nil;
if (!__localFileManager)
__localFileManager = [[NSFileManager alloc] init];
return __localFileManager;
}
//////////////////////////////////////////////////////////////////////////////////////////////
+ (NSString*)getPathForSuffix:(NSString*)path suffix:(NSString*)suffix {
NSString *pathWithoutExtension = [path stringByDeletingPathExtension];
NSString *name = [pathWithoutExtension lastPathComponent];
//– check if path already has the suffix.
if( [name rangeOfString:suffix].location != NSNotFound ) {
CCLOG(@"cocos2d: WARNING Filename(%@) already has the suffix %@. Using it.", name, suffix);
return path;
}
NSString *extension = [path pathExtension];
if( [extension isEqualToString:@"ccz"] || [extension isEqualToString:@"gz"] )
{
// All ccz / gz files should be in the format filename.xxx.ccz
// so we need to pull off the .xxx part of the extension as well
extension = [NSString stringWithFormat:@"%@.%@", [pathWithoutExtension pathExtension], extension];
pathWithoutExtension = [pathWithoutExtension stringByDeletingPathExtension];
}
NSString *retinaName = [pathWithoutExtension stringByAppendingString:suffix];
retinaName = [retinaName stringByAppendingPathExtension:extension];
if( [[self localFileManager] fileExistsAtPath:retinaName] )
return retinaName;
CCLOG(@"cocos2d: CCFileUtils: Warning HD file not found (%@): %@", suffix, [retinaName lastPathComponent] );
return nil;
}
//////////////////////////////////////////////////////////////////////////////////////////////
+ (NSString*)getDoubleResolutionImage:(NSString*)path {
#if CC_IS_RETINA_DISPLAY_SUPPORTED
NSString * retinaPath;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
if (CC_CONTENT_SCALE_FACTOR() == 2) {
if ((retinaPath = [self getPathForSuffix:path suffix:CC_RETINA_IPAD_DISPLAY_FILENAME_SUFFIX])) {
return retinaPath;
} else if ((retinaPath = [self getPathForSuffix:path suffix:CC_IPAD_DISPLAY_FILENAME_SUFFIX])) {
return retinaPath;
} else if ((retinaPath = [self getPathForSuffix:path suffix:CC_RETINA_DISPLAY_FILENAME_SUFFIX])) {
return retinaPath;
}
} else {
if ((retinaPath = [self getPathForSuffix:path suffix:CC_IPAD_DISPLAY_FILENAME_SUFFIX])) {
return retinaPath;
} else if ((retinaPath = [self getPathForSuffix:path suffix:CC_RETINA_DISPLAY_FILENAME_SUFFIX])) {
return retinaPath;
}
}
} else {
if (CC_CONTENT_SCALE_FACTOR() == 2) {
if ((retinaPath = [self getPathForSuffix:path suffix:CC_RETINA_DISPLAY_FILENAME_SUFFIX])) {
return retinaPath;
}
}
}
#endif // CC_IS_RETINA_DISPLAY_SUPPORTED
return path;
}
@end
#endif
[/sourcecode]
It is available on github and free for your use.
iOS6: dynamic autorotation
One of the most intrusive changes brought by iOS6 is the way autorotation is handled in UIViewControllers. If you have an autorotating app for iOS5, you will need to change it to correctly support autorotation under iOS6. If you develop an app which is supposed to run both on iOS5 and iOS6, then you will have to handle autorotation in the old as well as the new way.
Hitchhiker's guide to MKStoreKit
In-App Purchase is one of those great feature of the iOS ecosystem that I wish were easier to understand and implement in my apps. In the end it is just a machinery that will not really add value to my apps, and it would be great if the Apple motto “it just works” could be applied to StoreKit as well.
What's new in iOS 7 User Interface – Part 2: Deference
In a previous post, I began describing what changes the introduction of iOS 7 brought to iOS UI/UX dimension. In that post, I listed 4 main principles shaping the idea of iOS flat-UI: