Search code examples
iosxcode4interface-builderios5universal

Making universal apps without Interface Builder


I'm using XCode 4.2, with iOS 5, and would like to make a universal app, not using Interface Builder, as outlined in this post, not using a MainWindow.xib.

I would also like to separate my iPhone and iPad code, using the technique outlined in Kotan Code's post.

So I have the main appdelegate, and appdelegate_iPhone and an appdelegate_iPad that inherit from it.

Back in XCode 4.0 (and in Kotan's post), the appropriate appdelegate was used because there was a MainWindow_iPhone.xib (or MainWindow_iPad.xib) associated with it. If I'm not using .xibs, how do I programatically go to the correct appdelegate?

(I'm hoping the solution doesn't involve if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {} etc)


Solution

  • Have a look at your main.c file, it should contain a statement like this:

    int retVal = UIApplicationMain(argc, argv, nil, nil);
    

    where UIApplicationMain is defined as:

    int UIApplicationMain (
      int argc,
      char *argv[],
      NSString *principalClassName,
      NSString *delegateClassName
    );
    

    So, you simply execute:

    int retVal = UIApplicationMain(argc, argv, nil, <YOUR_DELEGATE_CLASS_NAME>);
    

    and you program will use that delegate class instead of the one defined in the nib.

    Also consider editing the info.plist file, where you should remove all entries about the main nib files you find there, otherwise your program will crash if you remove the xib files from the project.

    EDIT: I realized late that you are also asking about how to differentiate an iPad from an iPhone... and hoping this does not involve checking if UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad.

    AFAIK, UI_USER_INTERFACE_IDIOM is the official way to do that. Indeed, if you check the definition of that macro you will see it is based on a UIDevice class that returns information about the device where you program runs. So, I don't see anything bad in it.

    An alternative approach might be using UIDevice itself, or UIDevice-Extension, which is a framework extending UIDevice.


    EDIT 2:

    To the questions you ask in the comments:

    1) If there are .xibs, I don't need to specify UI_USER_INTERFACE_IDIOM (as per Kotan's post linked above). Must I choose between nibs and this if statement?

    I think so; either you do it at xib level, or programmatically.

    2) Are you suggesting I put the if statement to track the device in main.m? It's true, it does work there. Note the change in XCode 4.2:

    return UIApplicationMain(argc, argv, nil, 
                             NSStringFromClass([AppDelegate class]));
    

    If you do it programmatically, you have to do it where you need it.

    Likely, the app delegate is a part of the app that does not depend on the device, so you would not strictly need to instantiate one for the iphone and a different one for the ipad. What I was pointing to is that if you are not providing a nib, you should specify a delegate, which simply defines for your app a high-level entry-point.

    In your – application:didFinishLaunchingWithOptions: (the app entry point) you should then create your UI, that is creating that set of objects that you would otherwise define in a nib (controllers, views, whatever; a nib is just a mechanism to visually "create" and connect objects; if you don't do it visually, you do it programmatically) and connecting them; now, some of those objects do depend on the device (i.e., views), others not. For the former ones, you can decide on which class to instantiate them from by using the UI_USER_INTERFACE_IDIOM check.

    Alternatively, you could have two different delegate classes, one that creates the iphone UI and the other the ipad UI; this is also a perfectly reasonable approach. It basically depends on the complexity of your app and what trade-offs you are willing to accept.


    This is more or less how I see things, apologies for possibly being pedantic, but I hope I could make myself clear.

    If you have many ifs around your app, you could also define a "decorator" function (e.g.: decoratedClassNameFromGenericClassName:(NSString*), never mind the verbosity) to hide in it UI_USER_INTERFACE_IDIOM or whatever other "decoration" need might arise in the future...