iPhone Helpful Coding Tips 24-Mar-2011 12:44 PM
Edit FancyEdit New New Blog Upload All Recent Home Logout

Table Of Contents

TOP

1. Conditional build for simulator

Sometimes, you have some code you only want to run in the Simulator. the TARGETIPHONESIMULATOR define will help you with this:

#if TARGET_IPHONE_SIMULATOR
NSLog( @"Running in the simulator!" );
#else
NSLog( @"Running on the device!" );
#endif

NSLog calls from the simulator will result in text being visible via the Console.app Be sure to set a search on your app name to minimize the "noise" in the view window.

NSLog calls from the device will be visible via the Organizer, accessible via XCode.


TOP

2. Make your default.png (splash) transition to your app nicely

Make your default.png nicely transition to your main screen. (Great for splash pages, or credits, or just to add another bit of polish to your app...)

in your app delegate.m:

- (void)doDefaultPngFade {
    // add the new image to fade out
    UIImageView * defaultFadeImage;
    defaultFadeImage = [[[UIImageView alloc] 
                                   initWithImage:[UIImage
                                   imageNamed:@"Default.png"]autorelease]];
[self.mainViewController.view addSubview:defaultFadeImage];


    // and start the default fadeout
    [UIView beginAnimations:@"InitialFadeIn" context:nil];
    [UIView setAnimationDelegate:defaultFadeImage];
    [UIView setAnimationDidStopSelector:@selector( removeFromSuperview )];
    [UIView setAnimationDelay:0.0]; // stay on this long extra
    [UIView setAnimationDuration:0.30]; // transition speed
    [defaultFadeImage setAlpha:0.0];
    [UIView commitAnimations];
}

- (void)applicationDidFinishLaunching:(UIApplication *)application {
    // do initialization stuff here
    // ...
    [self doDefaultPngFade];
}

TOP

3. copying in a bunch of images...

If you've worked with the simulator and need to have a large image set installed, create a project, drop in all of the images, and then call this code. It will copy all images in the base of the app bundle into your device/simulator library

// copyImagesToLibrary - copies all valid images in the app bundle into your simulator/device library
- (void) copyImagesToLibrary
{
    NSArray * fileList = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[NSBundle mainBundle].bundlePath error:nil];
    for(NSString * fn in fileList) {
        UIImage * img = [UIImage imageNamed:fn];
        if( img ) UIImageWriteToSavedPhotosAlbum(img, nil, nil, nil);
    }
}

TOP

4. combining platform libs for easy linking

This will take two platforms' architecture's static libraries, and mash 'em together to make them easier to link against for the simulator and the device

cp ../build/Debug-iphoneos/libFooBar.a ./libFooBarARM.a
cp ../build/Debug-iphonesimulator/libFooBar.a ./libFooBarSIM.a
lipo libFooBarARM.a libFooBarSIM.a -create -output libFooBar.a

TOP

5. Icon for AdHoc distributions, .IPA Generation

NOTE: This is obsolete with the [REDACTED] beta toolset.

Make a zip file with a renamed extension called "yourapp.ipa"

Or, to automate this;

cp assets/MyIcon_512x512.jpg iTunesArtwork
mkdir Payload
cp -rp build/myApp.app Payload/
zip -r myApp.zip iTunesArtwork Payload
mv myApp.zip myApp.ipa

Note: it does not seem to be necessary that the ZIP/IPA filename have anything to do with the real app name at all. You can name it "IEnjoyCheese.ipa" and iTunes will still get the app's display name from within the bundle appropriately.

An example Makefile showing this is also available.


TOP

6. Have your object respond to %@

If you like to use NSLog() for debugging, the best way to have your custom objects output text is via overriding the NSObject "description" method. Then you can do something like:

NSLog( @"My Object info:%@", myObject );

Just implement this in your object's class:

- (NSString *) description;

TOP

7. Transition times

If you want to get your animation moving at the same rate/duration as an AppleOS transition, start at 0.3 seconds. That's the duration that most of their transitions run for.


TOP

8. Do something one time

Sometimes, you only want to set up things (like default settings) the first time an app is run. Here's a little bit of code you can call in your AppDelegate;

#define THIS_APP_VERSION (42)
NSUserDefaults *sd = [NSUserDefaults standardUserDefaults];
int defaultsVersion = [sd integerForKey:@"This App Version"];
if( defaultsVersion != THIS_APP_VERSION )
{
    [sd setBool:NO forKey:@"Bool Value 1"];
    [sd setInteger:37 forKey:@"PlayMode"];
    [sd setInteger:THIS_APP_VERSION forKey:@"This App Version"];
}

TOP

9. Interface Builder image resolution

Interface Builder expects image assets to be at 72dpi, even though the iPhone's screen is not 72dpi. If you have images that are 160 or whatever dpi, you will need to convert them. You can either do this in Photoshop/Pixelmator by loading in each one, adjusting, saving it, or by using the ImageMagick tools with a command line similar to this:

convert -units PixelsPerInch -density 72x72 original.png fixed.png

or simply:

mogrify -units PixelsPerInch -density 72x72 theImage.png

TOP

10. Set the default Organization Name for newly created XCode files

defaults write com.apple.xcode PBXCustomTemplateMacroDefinitions '{ ORGANIZATIONNAME = "Your Company Name"; }'

TOP

11. Set the default com.yourcompany for XCode projects

First of all, head over to

/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Project Templates/Application

In here, you'll find the templates for all of the XCode projects. All of the .plist files are plain ascii (non-binary) plists.

First thing to do is to make a folder in: (home)/Library/Application Support/Developer/Shared/Xcode/Project Templates

And copy the templates over to this location. Edit them here, rather than the systemwide ones. Do note however that when you upgrade your SDK and tools, that the tool-provided templates might have been updated, and that would be a good time to update these template copies as well.

Once they're copied over, edit them in this new location:

vi */*plist

And then just replace 'yourcompany' with 'umlautllama' in my case.

<key>CFBundleIdentifier</key>
<string>com.yourcompany.${PRODUCT_NAME:identifier}</string>

to CFBundleIdentifier com.umlautllama.${PRODUCT_NAME:identifier}

Alternatively, you can change it to:

<key>CFBundleIdentifier</key>
<string>__DOTCOMNAME__.${PRODUCT_NAME:identifier}</string>

then

defaults write com.apple.xcode PBXCustomTemplateMacroDefinitions '{ DOTCOMNAME = "com.mycompany"; }'

It really doesn't matter which; in either case, you're changing all of the templates on your machine for your projects, so making it easily extensible with the defaults-settings is kinda pointless... so you might as well just set it in the .plist files and not bother with the second method.


TOP

12. Add a system volume slider to your IB views

  1. Create a new UIView, place it in your View
  2. Change the class of this UIView to: MPVolumeView
  3. Add "MediaPlayer.framework" to your project

TOP

13. Identify iPhone/Touch models

This code is borrowed from this blog post.

#include <sys/types.h>
#include <sys/sysctl.h>

- (NSString *)deviceModel
{
    NSString *deviceModel = nil;
    char buffer[32];
    size_t length = sizeof(buffer);
    if (sysctlbyname("hw.machine", &buffer, &length, NULL, 0) == 0) {
        deviceModel = [[NSString alloc] initWithCString:buffer encoding:NSASCIIStringEncoding];
    }
    return [deviceModel autorelease];
}

Possible response handler

- (NSString *) platformString{
    NSString *platform = [self platform];
    if ([platform isEqualToString:@"i386"]) return @"Simulator";

    if ([platform isEqualToString:@"iPhone1,1"]) return @"iPhone 1G";
    if ([platform isEqualToString:@"iPhone1,2"]) return @"iPhone 3G (China, no WiFi possibly)";

    if ([platform isEqualToString:@"iPhone2,1"]) return @"iPhone 3GS";

    if ([platform isEqualToString:@"iPhone3,1"]) return @"iPhone 4 )";
    if ([platform isEqualToString:@"iPhone3,2"]) return @"iPhone 4 (CDMA/Verizon)";

    if ([platform isEqualToString:@"iPod1,1"])   return @"iPod Touch 1G";
    if ([platform isEqualToString:@"iPod2,1"])   return @"iPod Touch 2G";
    if ([platform isEqualToString:@"iPod2,2"])   return @"iPod Touch 2.5G";
    if ([platform isEqualToString:@"iPod3,1"])   return @"iPod Touch 3G";
    if ([platform isEqualToString:@"iPod4,1"])   return @"iPod Touch 4G";

    if ([platform isEqualToString:@"iPad1,1"])   return @"iPad 1G (wifi)";
    if ([platform isEqualToString:@"iPad1,2"])   return @"iPad 1G (3G/GSM)";
    if ([platform isEqualToString:@"iPad2,1"])   return @"iPad 2G (wifi)";
    if ([platform isEqualToString:@"iPad2,2"])   return @"iPad 2G (GSM)";
    if ([platform isEqualToString:@"iPad2,3"])   return @"iPad 2G (CDMA)";

    if ([platform isEqualToString:@"AppleTV2,1"])   return @"Apple TV 2G";

    if ([platform isEqualToString:@"i386"])      return @"iPhone Simulator";

    return platform;
}

Here's another implementation from [http://www.clintharris.net/2009/iphone-model-via-sysctlbyname/]

- (NSString *) platform  
{  
    size_t size;  
    sysctlbyname("hw.machine", NULL, &size, NULL, 0);  
    char *machine = malloc(size);  
    sysctlbyname("hw.machine", machine, &size, NULL, 0);  
    NSString *platform = [NSString stringWithCString:machine];  
    free(machine);  
    return platform;  
}  

--

TOP

14. Is the screen being double-sized?

Check the scale value! [http://www.markj.net/]

+(BOOL) screenIs2xResolution {
  return 2.0 == [MyDeviceClass mainScreenScale];
}

+(CGFloat) mainScreenScale {
  CGFloat scale = 1.0;
  UIScreen* screen = [UIScreen mainScreen];
  if ([UIScreen instancesRespondToSelector:@selector(scale)]) {
    scale = [screen scale];
   }
  return scale;
}

On iOS 3.2, the best we can do is:

+(BOOL) isIPad {
  BOOL isIPad=NO;
  NSString* model = [UIDevice currentDevice].model;
  if ([model rangeOfString:@"iPad"].location != NSNotFound) {
    return YES;
  }
  return isIPad;
}

TOP

15. Amount of free memory available

#import <mach/mach.h> 
#import <mach/mach_host.h>

static natural_t get_free_memory () {
    mach_port_t host_port;
    mach_msg_type_number_t host_size;
    vm_size_t pagesize;

    host_port = mach_host_self();
    host_size = sizeof(vm_statistics_data_t) / sizeof(integer_t);
    host_page_size(host_port, &pagesize);        

    vm_statistics_data_t vm_stat;

    if (host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size) != KERN_SUCCESS) {
        NSLog(@"Failed to fetch vm statistics");
        return 0;
    }

    /* Stats in bytes */ 
    natural_t mem_free = vm_stat.free_count * pagesize;
    return mem_free;
}

TOP

16. Copy a file from your app bundle to Documents

You may want to include files with your bundle that get copied into your documents folder in the sandbox. The following code is adapted from the Apple "SQLiteBooks" example:

- (void)makeDocumentSubdir:(NSString *)subdirname
{
    // First, test for existence.
    BOOL success;
    NSFileManager *fileManager = [NSFileManager defaultManager];

    // set up the basic directory path name
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];

    // create the directory path name for the subdirectory
    NSString *subdirectory = [paths objectAtIndex:0];
    subdirectory = [documentsDirectory stringByAppendingPathComponent:subdirname];
    success = [fileManager createDirectoryAtPath:subdirectory withIntermediateDirectories:YES attributes:nil error:NULL ];
}

- (void)copyFileNamed:(NSString *)filename intoDocumentsSubfolder:(NSString *)dirname
{
    // First, test for existence.
    BOOL success;
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSError *error;
    // set up the basic directory path name
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];

    // set up the directory path name for the subdirectory
    NSString *subdirectory = [documentsDirectory stringByAppendingPathComponent:dirname];

    // set up the full path for the destination file
    NSString *writableFilePath = [subdirectory stringByAppendingPathComponent:filename];
    success = [fileManager fileExistsAtPath:writableFilePath];

    // if the file is already there, just return
    if (success)
            return;
    // The file not exist, so copy it to the documents flder.
    NSString *defaultFilePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:filename];
    success = [fileManager copyItemAtPath:defaultFilePath toPath:writableFilePath error:&error];
    if (!success) {
            //[self alert:@"Failed to copy resource file"];
            NSAssert1(0, @"Failed to copy file to documents with message '%@'.", [error localizedDescription]);
    }
}


- (void)firstRunSetup
{
    [self makeDocumentSubdir:@"FileDir1"];
    [self copyFileNamed:@"FirstFile.sqlite" intoDocumentsSubfolder:@"FileDir1"];
    [self copyFileNamed:@"SecondFile.sqlite" intoDocumentsSubfolder:@"FileDir1"];
}

TOP

17. Safe MIN/MAX

To prevent issues with extra increments and decrements with code like:

#define MAX(A,B)   (((A)<(B))?(A):(B))
int x = MAX( a++, --y );

Define it like this instead:

#define MAX(A,B)   
 ({ 
    __typeof__(A) __a = (A); 
    __typeof__(B) __b = (B); 
    __a < __b ? __b : __a; 
 })

TOP

18. Prevent XCode from messing with your PNGs

As you might have found out by now, XCode likes to "optimize" your PNG files when it builds your application bundle. Many of you probably also realize that "optimize" means "hack it into a format that prevents it from working as you'd expect it to in GL". Here's a fix to prevent it from doing this to your precious PNGs.

On the image file, right-click, and 'get info'. Change the file type from "image.png" to "image".


TOP

19. Revert accidental tab badging in XIBs

If you set the badge value on a tab in Interface Builder, you will notice that you can't clear/remove the badge from the tab anymore. If you clear out the text input box, you will notice that the badge just displays as an empty red circle. One way to eliminate it obviously is in code: (for example)

[[[[[self tabBarController] tabBar] items] objectAtIndex:2] setBadgeValue:nil];

But if it was unintentional, and you want to remove it in the XIB itself, you don't need to delete the tab and start over with it. Just save out the file, and load the .XIB file in your favorite text editor. Look for an XML tag like this:

<string key="IBUIBadgeValue"/>

Remove this item, save it out, and when you return to Interface Builder, it will ask you to revert to the saved version, accept, and the badge will be gone!


TOP

20. Do your own rotation thing...

To do something other than autorotate... (or to manually rotate)

first, subscribe to rotation notifications, and make sure they occur.

[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; 
[[NSNotificationCenter defaultCenter] addObserver:self                                            
                                         selector:@selector(didRotate:)
                                             name:UIDeviceOrientationDidChangeNotification
                                           object:nil];

also, catch them here...

- (void) didRotate:(NSNotification *)notification
{   
    UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];

    if (orientation == UIDeviceOrientationLandscapeLeft)
    {
        // manually set the status bar orientation so status bar, alerts and keyboard work
        [[UIApplication sharedApplication] setStatusBarOrientation:UIDeviceOrientationLandscapeLeft];
    }
}

And turn off autorotations...

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation) interfaceOrientation 
{
    // return NO - stay in the default orientation
    //  - also, you could just omit this method entirely.
    return NO;
}

Finally, set the default orientation in your app's Info.plist file:

Key: UIInterfaceOrientation
Val: UIInterfaceOrientationLandscapeRight  (or the appropriate value for your app)

TOP

21. Defining a selector to later call

In your class definition:

id theObject;
SEL theSelector;

To call it at runtime:

[theObject performSelector:theSelector];

TOP

22. More...