views:

68

answers:

5

I just updated my app so that it's a universal app. In doing so I added support for UIPopoverController in a certain place. Now the app seems to be crashing on 3.1.3 iPhone/iTouch devices:

OS Version:      iPhone OS 3.1.3 (7E18)
Report Version:  104

Exception Type:  EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x00000001, 0xe7ffdefe
Crashed Thread:  0

Dyld Error Message:
  Symbol not found: _OBJC_CLASS_$_UIPopoverController

What I don't get is that I'm only trying to call UIPopoverController if the hardware is an iPad:

if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
    UIPopoverController *popover = [[UIPopoverController alloc] initWithContentViewController:btc];
    CGSize popoverSize = { 300.0, 500.0 };
    popover.delegate = self;
    popover.popoverContentSize = popoverSize;
    self.bmPopover = popover;
    [popover release];

    [self.bmPopover presentPopoverFromBarButtonItem:self.bmBarButtonItem permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
} else {
    [self presentModalViewController:nav animated:YES];
}

I do have an iVar and a property of type UIPopoverController declared but I wouldn't have expected this to cause an issue at runtime if I didn't actually try to call methods in the class.

What am I supposed to do to make sure that the system doesn't try to link with UIPopoverController at runtime when this isn't supported?

A: 

The easy answer:

In your Xcode project, Select the top level project icon. Do a Get Info, go to the BUILD panel. set Configurations to All Configurations, set Show: to ** All Settings**

Set the iOS Deployment Target to iOS 3.1.

Recompile. Your program is failing because the minimum OS is set too high, so the loader can't resolve the symbol UIPopoverController. The change I just walked you through makes that symbol weak: it will resolve to NULL, but at least your program will load.


Are you loading a NIB file that implicitly creates a UIPopoverController? That's another way to crash.

DavidPhillipOster
This is not true. If you instantiate a UIPopoverController with your base SDK set to 4.0 and your deployment target set to 3.0, the app will crash on 3.0 - 3.1.3
rickharrison
rickharrison is correct. However it will crash *only* when you create/use a `UIPopoverController`. So it is possible to avoid the crash.
Jon-Eric
@DavidPhillipOster, that's a good point about possibly using a `UIPopoverController` in a NIB.
Jon-Eric
there are 3 possible crashes: (1) you reference UIPopoverController, even though you never instantiate it, and your deployment target is set to 3.2. In that case, you'll fail at load time. (2) you try to instantiate it in the Obj-C code (3) you load a nib file that instantiates it. The original poster is suffering from a (1)-type crash. Hence my answer is correct, the others deal with a (2)-type crash.
DavidPhillipOster
A: 

You need to stop the compiler errors by referencing the class from a string. This should be used in tandem with UI_USER_INTERFACE_IDIOM() and will compile on an SDK that does not know about UIPopoverController. Here is an example:

Class popClass = NSClassFromString(@"UIPopoverController");
if(popClass) {
    id infoPop = [[popClass alloc] initWithContentViewController:popViewController];
    [infoPop presentPopoverFromRect:CGRectMake(20, 70, 10, 10) inView:self.view permittedArrowDirections:4 animated:YES];
}
Ben
Nimrod doesn't indicate that he is getting any *compiler* errors.
Jon-Eric
I did this and used weak linking.
Nimrod
A: 

Even though this code would most likely never run on the iPhone, it will still be linked and thus you are receiving the error. Before instantiating, you need to check if the class exists. You can modify your code above to the following which will fix it.

if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
    Class UIPopoverControllerClass = NSClassFromString(@"UIPopoverController");
    if (UIPopoverControllerClass != nil) {
        UIPopoverController *popover = [[UIPopoverControllerClass alloc] initWithContentViewController:btc];
        CGSize popoverSize = { 300.0, 500.0 };
        popover.delegate = self;
        popover.popoverContentSize = popoverSize;
        self.bmPopover = popover;
        [popover release];

        [self.bmPopover presentPopoverFromBarButtonItem:self.bmBarButtonItem permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
    }
} else {
    [self presentModalViewController:nav animated:YES];
}

Also, you could weak link against the UIKit framework, which would also solve the problem. I prefer the code solution as it is more safe.

rickharrison
I'm trying this now. Unfortunately I have to send the app off to someone with a 3.1 device for testing, because I can't download old versions of iOS... :(
Nimrod
@rickharrison, he must already be weak linking correctly, or his app wouldn't compile and run.
Jon-Eric
If his base sdk is 3.2 or higher it will compile and run. It just will not start up on a 3.0 device.
rickharrison
A: 

Hi, For Universal app you should not only check if this is ipad or does this class exists but you should link UIKit as Weak reference a not default ( strong ), to do this:

  1. get to target info
  2. select general
  3. in linked libraries change UIKit required to UIKit weak

Have fun making universal apps :]

Krzysztof Zabłocki
If he was "strong" linking, his app wouldn't compile and run on a 3.1 device. Right?
Jon-Eric
Looks like switching the framework from "Required" to "Weak" fixed the problem. Thanks!
Nimrod
A: 

I found it useful to add this, so that I could use UI_USER_INTERFACE_IDIOM() on pre-3.2 systems.

#ifndef __IPHONE_3_2    // if iPhoneOS is 3.2 or greater then __IPHONE_3_2 will be defined

typedef enum {
    UIUserInterfaceIdiomPhone,           // iPhone and iPod touch style UI
    UIUserInterfaceIdiomPad,             // iPad style UI
} UIUserInterfaceIdiom;

#define UI_USER_INTERFACE_IDIOM() UIUserInterfaceIdiomPhone

#endif // ifndef __IPHONE_3_2
westsider