Can I use two xibs with one viewcontroller - re: porting to iPhone 5
Asked Answered
D

3

15

I just submitted my first app to the app store (yay it was just approved!). I now want to update it to work with (look nicer on) the larger iPhone 5 screen. I don't intend to change anything other than to change the layout a bit for the larger screen.

NOTE: I don't want to have my current xib stretched.

Is it possible to create two xib files (ie: copy my current xib file for the main screen) and hook them both into the view controller and have it so that when the app launches, the app detects if there is an iPhone 5 screen or an earlier screen. Then, depending on which device it is, show the user a different screen.

I intend for underlying app to remain the same. All I want is to present a slightly different (taller) screen for iPhone 5 users with a few buttons/items moved around for the new layout. I otherwise won't be adding or removing anything from the interface.

This SO question/answer shows how to switch between an iPhone or iPad view. So to does this one. Both are helpful but I don't know how to modify this for the circumstance where the user is using an iPhone 5 with a larger screen or an iPhone 4S and below. Also, they assume two view controllers. I only want ONE view controller since absolutely NOTHING in the view controller logic changes - only the placement of the objects on the screen change and that is all done in the XIB.

I should think the answer should be that the view controller iteslf assesses what device it is running on then presents the appropriate xib? Yes? No?

If so, how would I go about this?

Displease answered 1/10, 2012 at 18:9 Comment(0)
D
25

[Revised with Complete Answer on : Oct 7, 2012]

After significant research I found the answer, partly on this SO page (which shows how to detect which iPhone version your app is running on) and partly this SO page (showing how to have two xib's share the same 'File's Owner'. The final piece of the puzzle (loading separate xib's via the loadNibNamed: method) I found in chapter 10 of The Big Nerd Ranch's excellent iOS Programming text. Here's how:

  1. Create a second xib (File, New..., File, select 'User Interface', select 'Empty' and save it. This creates the new xib. In the example below, my classic xib (for 3.5" iPhones) was named TipMainViewController.xib. I saved the new iPhone 5 xib with the name 'TipMainViewController-5.xib'

  2. Make that new xib's 'File's Owner' the same ViewController as your existing xib. To do this, in the new xib file, select 'File's Owners'. Then in the 'Identity Inspector' select the existing View Controller as the Custom Class. In my case I selected 'TipMainViewController'.

  3. Drag a new UIView onto the new xib's empty canvas. In the new UIView's attribute inspector set the 'Size' attribute to 'Retina 4 Full Screen'

  4. Select all the contents in the existing 'Classic' 3.5" xib - eg: all your controls, buttons, selectors, labels etc. Copy them and paste them into the new iPhone 5 xib. Resize/move etc. them to optimize for the iPhone's 4" display.

  5. Make all the connections to/from File's Owner as you did when you created your original xib.

  6. Finally, in the 'viewDidLoad' method of your 'single' ViewController, insert the following logic (using your nib/xib names of course):

    - (void)loadView
    {
        [super viewDidLoad];
        if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
        {
            CGSize result = [[UIScreen mainScreen] bounds].size;
            if(result.height == 480)
            {
              // iPhone Classic
              [[NSBundle mainBundle] loadNibNamed:@"TipMainViewController" owner:self options:nil];
            }
            if(result.height == 568)
            {
              // iPhone 5
              [[NSBundle mainBundle] loadNibNamed:@"TipMainViewController-5" owner:self options:nil];
            }
        }
    }
    
Displease answered 1/10, 2012 at 23:19 Comment(8)
This is very useful and this has helped me immensely.Mixologist
Should we check the auto layout option?Schoolteacher
Your code sample doesn't work and contains a typo. It's unclear whether you meant viewDidLoad or loadViewUniversalize
Shouldn't you be determining which nib to load in initWithNibNameorNil: method ? when you hit viewDidLoad you have already loaded your nib....Dreg
I have a file called MasterScreen.xib but nowhere in the source code is there any loadNibNamed. I'm not sure what to do. I do see things like this in the source: self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; I don't see MasterScreen.xib reference anywhere in any of my project's files (except in the xcodeproj file). Where do I add the conditional statements after making a new xib file? I don't know because I don't even know how MasterScreen.xib is included into the app.Megen
Ok, well I found something like [super viewDidLoad]; in the code, and I'm placing some conditionals right below that. Hope it works! I'm not the original maker of the app, so is there an easy way to copy the ui connections from File's Owner and not do it all manually? I have no idea what connects to what (or how, for that matter).Megen
this goes in loadView, not in viewDidLoadAnaemic
Are there any performance issues associated with this? It tells me that all three nibs are being loaded and the decision is being taken at runtime. Isn't this sort of bad practise?Straightjacket
U
7

Here is a simple, working code sample for your view controller that shows how to load myXib-5.xib on the iPhone 5 and myXib.xib on iPhones/iPods predating the iPhone 5:

- (void)loadView
{
    if([[UIScreen mainScreen] bounds].size.height == 568)
    {
        // iPhone 5
        self.view = [[NSBundle mainBundle] loadNibNamed:@"myXib-5" owner:self options:nil][0];
    }
    else
    {
        self.view = [[NSBundle mainBundle] loadNibNamed:@"myXib" owner:self options:nil][0];
    }
}

It assumes that you are only targeting the iPhone and not the iPad, to keep it simple.

The XIB's file owner's class property should also be set to the view controller that contains loadView.

Universalize answered 2/2, 2013 at 1:5 Comment(1)
Duplicate the xib, rename the copy (with -5), drop in your code. Done. Thanks!Circe
W
6

Code in answer was helpful, but I needed something that worked better for universal apps (iphone/ipad).

In case someone else needs the same thing, here's something to get you started.

Say you built a universal app using the nib/xib naming standards for ios for view controllers that have xibs with the same name:

The two built-in defaults for autoloading xibs when providing no name is passed to initWithNibName:

  • ExampleViewController.xib [iphone default when nib named empty for Retina 3.5 Full Screen for classic layouts iphone 4/4s etc...]
  • ExampleViewController~ipad.xib [ipad/ipad mini default when nib named empty]

Now say you need custom xibs for the iphone 5/5s in IB using Retina 4 Full Screen option, i.e., you don't want the 3.5 xibs displaying for any 568h devices.

Here's the custom naming convention using a category approach:

  • ExampleViewController-568h.xib [iphone non default/custom naming convention when nib name empty for Retina 4 Full Screen (568h)]

Instead of overriding the built-in naming defaults, use a category to help set the right xib for the controller.

https://gist.github.com/scottvrosenthal/4945884

ExampleViewController.m

#import "UIViewController+AppCategories.h"

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    nibNameOrNil = [UIViewController nibNamedForDevice:@"ExampleViewController"];

    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {

       // Do any additional customization 

    }

    return self;
}

UIViewController+AppCategories.h

#import <UIKit/UIKit.h>

@interface UIViewController (AppCategories)

   + (NSString*)nibNamedForDevice:(NSString*)name;

@end

UIViewController+AppCategories.m

// ExampleViewController.xib [iphone default when nib named empty for Retina 3.5 Full Screen]
// ExampleViewController-568h.xib [iphone custom naming convention when nib named empty for Retina 4 Full Screen (568h)]
// ExampleViewController~ipad.xib [ipad/ipad mini default when nib named empty]

#import "UIViewController+AppCategories.h"

@implementation UIViewController (AppCategories)

+ (NSString*)nibNamedForDevice:(NSString*)name
{

    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
    {
        if ([UIScreen mainScreen].bounds.size.height == 568)
        {
            //Check if there's a path extension or not
            if (name.pathExtension.length) {
                name = [name stringByReplacingOccurrencesOfString: [NSString stringWithFormat:@".%@", name.pathExtension] withString: [NSString stringWithFormat:@"-568h.%@", name.pathExtension ]
                ];

            } else {
                name = [name stringByAppendingString:@"-568h"];
            }

            // if 568h nib is found
            NSString *nibExists = [[NSBundle mainBundle] pathForResource:name ofType:@"nib"];
            if (nibExists) {
                return name;
           }

        }
    }

    // just default to ios universal app naming convention for xibs
    return Nil;
}

@end
Whitefaced answered 13/2, 2013 at 17:28 Comment(1)
this comes so damn close to working for me. i'm using storyboards, but deleting the view from the VC in the storyboard and letting each VC load from a nib. in doing so, my VC's are being initialized with initWithCoder. my understanding is that initWithNibName calls initWithCoder so there would be a bit of an unpleasant loop if i try this. any idea on adapting your method for this?Strappado

© 2022 - 2024 — McMap. All rights reserved.