21

I have converted an iPhone application using the wizard like thing in XCode into a universal app.

It builds fine but obviously looks a bit rubbish in some areas :)

I need to load nibs according to which device is being used. I dont wish to create my view controllers using initWithNib as I already have code to create the controllers with some data (initWithMyLovelyData) which doesnt do anything to do with nib loading.

I know to find out the device you use UI_USER_INTERFACE_IDIOM() so I tried overriding the initWithNibName within the actual view controllers themselves, assuming they get called internally somehow. But it's not working as I guess I am unsure of the syntax.

I have tried

if(ipad..) self = [super initWithNibName:@"MyIpadNib" bundle:nibBundleOrNil];

And that doesnt work :/

EDIT - I know I have massively edited this, made my question a bit more specific after doing some more research - apologies!

Laurent Etiemble
  • 27,111
  • 5
  • 56
  • 81
Chris James
  • 11,571
  • 11
  • 61
  • 89

5 Answers5

77

Actually, Apple does all this automatically, just name your NIB files:

MyViewController~iphone.xib // iPhone
MyViewController~ipad.xib // iPad

and load your view controller with the smallest amount of code:

[[MyViewController alloc] initWithNibName:nil bundle:nil]; // Apple will take care of everything
Adam
  • 32,900
  • 16
  • 126
  • 153
  • 2
    if you use this statement: [MyViewController alloc] init]; in above example instead, is it doing exactly the same thing? or is it doing something slightly different? – hokkuk Mar 09 '12 at 16:24
  • I expect "init" does nothing. If you want it to load a NIB, you need to call initWithNib - or else manually load a NIB from the bundle – Adam Mar 10 '12 at 14:50
  • 2
    [[MyViewController alloc] init] is exactly the same as [[MyViewController alloc] initWithNibName:nil nibBundle:nil] This is mentioned in the UIViewController.h file: "If you subclass UIViewController, you must call the super implementation of this method, even if you aren't using a NIB. (As a convenience, the default init method will do this for you, and specify nil for both of this methods arguments.)" – jsd Jul 25 '12 at 22:00
  • Where is this documented? The documentation of "nibName" in UIViewController specifies how the nib name is resolved when none is provided, but does not seem to mention this approach. –  Jan 19 '13 at 08:51
  • IIRC this feature is only documented in one place - the bits about "loading resources / using Bundles", which many people never happen to read. Technically, nibName etc re-uses the underlying bundle/resource-loading system - so Apple assumes you'll read this elsewhere and remember it. – Adam Jan 21 '13 at 00:02
  • 2
    Just want to add that the bits of the documentation describing how "iOS Supports Device-Specific Resources" can be found here: https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/LoadingResources/Introduction/Introduction.html – Lasse Christiansen Feb 09 '13 at 11:29
  • This is definitely the better answer. No reason to switch on interface idiom. No reason to copy/paste the init code. No reason to type the nib name twice (or at all). – John Cromartie Feb 15 '13 at 19:34
  • `initWithNibName:nibBundle` is the designated initializer for `UIViewController`, so even if it wasn't explicitly documented anywhere, you would know that calling `init` on a view controller would eventually call through to `initWithNibName:nibBundle`. – kubi May 09 '13 at 17:26
  • Apple skipped the designated initializers specifically in UIViewController / UIView since iOS version 2.0. Anything loaded from NIBs used initWithCoder/awakeFromNib combo instead (bypassing their own - documented! - designated init'r). So ... you can't trust "designated initializers" in UIKit, sadly – Adam May 10 '13 at 08:42
  • In what scenario will name of the xib play a role ? In my case it works regardless of how I name xib file. I guess framework simple relies of value in `customClass` attribute. Is that correct, @Adam? – expert Aug 30 '13 at 01:12
22

EDIT: @Adam's answer below is the correct answer.

To determine which nib to load, do the following, and scrap your initWithMyLovelyData method and use a property to set the data. You should be able to easily move all your init code into the property setter method.

MyViewController *viewController;

if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
    viewController = [[MyViewController alloc] initWithNibName:@"ipadNIB" bundle:nil];
} else {
    viewController = [[MyViewController alloc] initWithNibName:@"iphoneNIB" bundle:nil];
}

viewController.myLovelyData = someData;
kubi
  • 48,104
  • 19
  • 94
  • 118
  • Sorry to dig up old(er) posts but: This is not working for me. Any ideas? – Linuxmint Mar 15 '11 at 00:08
  • @Linuxmint What problem are you having? – kubi Mar 15 '11 at 09:21
  • http://stackoverflow.com/questions/5306346/trouble-with-loading-a-separate-xib-for-ipad-or-iphone – Linuxmint Mar 15 '11 at 18:11
  • 5
    FWIW, @adam's answer is the correct one and much better than this. – kubi May 09 '13 at 17:22
  • Since the nib name is just one of two strings, and the rest of the VC allocation/initialization is the same, you can shorten the code by having just one call to alloc/init the VC, using an NSString variable which is either set to one nib name or the other. – Alexander Nov 13 '14 at 02:34
2

I just put these two methods in a class called IPadHelper, and use the addIPadSuffixWhenOnIPad method to conditionally pick between two nibs depending on platform

+ (BOOL)isIPad{
    if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iPhoneOS_3_2){
        if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad){
            return YES;
        }
    }
    return NO;
}

+ (NSString *)addIPadSuffixWhenOnIPad:(NSString *)resourceName{
    if([IPadHelper isIPad]){
        return [resourceName stringByAppendingString:@"-iPad"];
    }
    else {
        return resourceName;
    }   
}

see here http://cocoawithlove.com/2010/07/tips-tricks-for-conditional-ios3-ios32.html for more explanation of the first method...

Dori
  • 18,283
  • 17
  • 74
  • 116
  • Compiler said that this is wrong, because Class methods should return objects (id). So If think it will be better to add this to C file. – rowwingman Jan 17 '12 at 22:10
2

You can have your cake and eat it too. Just add a method that wraps initWithNibName:bundle: and adds your myLovelyData parameter:

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil  myLovelyData:(id)data
{
    if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) 
    {
        // Custom initialization using myLovelyData
        //
    }
    return self;
}
Bogatyr
  • 19,255
  • 7
  • 59
  • 72
0

I think it will be better to create C file.
FileHelper.h

#import <Foundation/Foundation.h>
BOOL isIPad();
NSString *addIPadSuffixWhenOnIPad(NSString *resourceName);

FileHelper.m

#import "FileHelper.h"
BOOL isIPad() {
    if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iPhoneOS_3_2) {
        if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
            return YES;
        }
    }
    return NO;
}

NSString *addIPadSuffixWhenOnIPad(NSString *resourceName) {
    if(isIPad()) {
        return [resourceName stringByAppendingString:@"-iPad"];
    }
    else {
        return resourceName;
    }   
}
rowwingman
  • 5,589
  • 7
  • 33
  • 50