Oh My God They Killed parentViewController

Pre iOS 5, ‘parentViewController’ would return the view-stack’s parent view controller, or the controller which presented that controller when called on a modal view controller. This was the intended, documented functionality of the method, not some mistake.

iOS 5 changes this by splitting out the latter functionality to ‘presentingViewController’. Unfortunately, rather than creating 2 new methods with new functionality, they simply changed the old one and created the new one. So for anyone like me who missed this particular change (it’s not even in the API diffs as it’s not changed, or removed), [self parentViewController] starts returning nil, your code continus to run, but will probably hang if when you expect to have dismissed the view controller.

What terrible design. I think it would have been better to deprecated the old function & split it into two new ones. Or at least provide some warnings to the change. If Objective-C actually crashed on nil values like java, again this would be easy to spot & change – but hang bugs are particularly nasty to try and solve.

Anyway, here are my testing results of [self parentViewController] where self is a controller that was presented as modal:

XCode 4.1 & SDK 4.3: iOS 5: presentingViewController, iOS 4.x: presentingViewController
XCode 4.2 & SDK 5.0: iOS 5: nil, iOS 4.x: presentingViewController

As you can see, when iOS5 runs the older SDK it doesn’t break (because that would break everyone’s code), and iOS4 running with code from the new SDK doesn’t break either. Just SDK & iOS 5 together.

Fixing this if you only support iOS 5 is simple. Replace every instance of [self parentViewController] where you want the model view controllers parent to [self presentingViewController].

Most people need to support iOS 4. This is a bit of a pain because you can’t just call ‘presentingViewController’ without it crashing (due to this method not existing on iOS4). Here’s my #define to get the old behaviour back:

#define self_parentViewController (([self parentViewController] != nil || ![self respondsToSelector:@selector(presentingViewController)]) ? [self parentViewController] : [self presentingViewController])

In english, this means: if the parentViewController is not nil, or the presentingViewController method doesn’t exist yet (in the case of < iOS 5.0), return the result of presentingViewController. Else return the result of presentingViewController.

EDIT: if you need to call the old parentViewController on something other than self, try my revised #define.

NB. just like before – this *may* not return the presenting view controller, if self has it’s own parentViewController that isn’t the one which presented it. But this is equal to the old SDK behaviour, so I don’t think it will introduce bugs.

Everywhere in your code, replace [self parentViewController] with self_parentViewController (no square brackets).

#defines are normally pretty evil but in this case, until you drop 4.0 support I actually feel it’s cleaner. Now you could do [self parentViewController] || [self presentingViewController], and this will work in many cases, that is until parentViewController returns nil on iOS 4, when your app will crash due to the unrecognised ‘presentingViewController’ selector. Safter to use my #define for full backwards compatibility.

Pre iOS 5.0 docs:

parentViewController
The parent of the current view controller. (read-only)

@property(nonatomic, readonly) UIViewController *parentViewController
Discussion
Parent view controllers are relevant in navigation, tab bar, and modal view controller hierarchies. In each of these hierarchies, the parent is the object responsible for displaying the current view controller. If you are using a view controller as a standalone object—that is, not as part of a view controller hierarchy—the value in this property is nil.

Now it states (emphasis added)

parentViewController
The parent of the current view controller. (read-only)

@property(nonatomic, readonly) UIViewController *parentViewController
Discussion
Parent view controllers are relevant in navigation, tab bar, and modal view controller hierarchies. In each of these hierarchies, the parent is the object responsible for displaying the current view controller. If you are using a view controller as a standalone object—that is, not as part of a view controller hierarchy—the value in this property is nil.

Prior to iOS 5.0, if a view did not have a parent view controller and was being presented modally, the view controller that was presenting it would be returned. This is no longer the case. You can get the presenting view controller using the presentingViewController property.

Yet it still says it’s relevant in the ‘modal view controller hierarchies’, is it?

Also I love that modalViewController still says ‘see also: parentViewController’, there isn’t really a need for this now.

modalViewController
The controller for the active modal view—that is, the view that is temporarily displayed on top of the view managed by the receiver. (read-only)

@property(nonatomic, readonly) UIViewController *modalViewController
Discussion
Typically, a modal view is used to present an edit page or additional details of a model object. The modal view is optionally displayed using a vertical sheet transition.

Availability
Available in iOS 2.0 and later.
See Also
@property parentViewController
Declared In
UIViewController.h

Here’s the kicker in the docs. If you search for UIViewController and you have SDK 4.3 and 5.0 docs installed (common when you upgrade), you get multiple hits. If you pick the one that is the 5.0 sdk, then find the link to ‘parentViewController’. Clicking that link will open that method, but rather than scrolling down the page it will show it in the 4.3 docs! Without this warning. This makes this communication issue just that little bit worse.


16 comments on “Oh My God They Killed parentViewController

  1. Just like to say thanks for this article. Had me stumped. Many Thanks

  2. Very nice article. I had the same problem when navigating between parent and modal. My app is now working with iOS 5 ;)
    Thank you.

  3. Nice article. It looks like there might be an easier fix if you just need to dismissModalViewController. According to iOS 4.2 docs

    “If you call this method on the modal view controller itself, however, the modal view controller automatically forwards the message to its parent view controller”.

    This also seems to do the right think on iOS 5 + iOS 5 SDK, so they probably mean to say “forwards the message to its presenting view controller”

  4. Thank You! I was banging my head on the wall trying to figure out why an old code base wasn’t behaving correctly. But I do have a question. Using your #define workaround how would that apply to this . . .

    NSInteger overlay_index = [[[self parentViewController] parentViewController] overlay_index];

    I’ve gotten as far as [[self_parentViewController parentViewController] loadOverlay:i];

    but im not sure how to correct the 2nd “parentViewController”, the one without [self …]. I don’t want to just replace it with presentingViewController because that would defeat the purpose of the backwards compatibility ;(

    Any suggestions would help. Thanks!

  5. Thank you for explaining this. I wondered how I suddenly had “bugs” throughout my app with regards to modal views. Really stupid execution by Apple IMO.

  6. Chris, if you want to use a #define, then try this one:

    #define view_parentViewController(_view_) (([_view_ parentViewController] != nil || ![_view_ respondsToSelector:@selector(presentingViewController)]) ? [_view_ parentViewController] : [_view_ presentingViewController])
    

    so

    [[self_parentViewController parentViewController] loadOverlay:i];
    

    becomes

    [view_parentViewController(view_parentViewController(self))  loadOverlay:i];
    

    Another option would be to monkey-patch ViewController and add a “parentViewController2″ method with the old logic (i.e. based on the code in my #define).

    Steve,

    I completely agree – this is *not* how you deprecate APIs! This situation could have been handled much better by simply deprecating the old parentViewController & creating a new one.

  7. Pingback: Beyond code » iOS programming: parentViewController in iOS5

  8. Pingback: parentViewController property…. YOU ARE DIE!!!(NIL) | Programming In Paradise parentViewController property…. YOU ARE DIE!!!(NIL) | Just another WordPress site

  9. Yes! Thank you VERY much for this post. What a bugger! I was using presentingViewController because I was dev’ing against an iOS 5.x iPad 2. When I ran it on a 4.x iPad 2, I saw the described problem. What crappy docs!

  10. Pingback: Auto-rotation broken in iOS 6 with XCode 4.5? « OmegaDelta

  11. Thanks a lot for clarifying things!

    I’ve created a category method for UIViewController based on your #define. I find it easier to use in any context:


    - (UIViewController*) myParentViewController {
    UIViewController* ret = [self parentViewController];
    if(ret == nil) {
    if([self respondsToSelector:@selector(presentingViewController)]) {
    ret = [self presentingViewController];
    }
    }
    return ret;
    }

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>