in iOS

View Controller Notification Changes on iOS5

If you use view controllers in your iOS apps, here’s a doozie: The behavior of viewWillAppear/viewDidApear/viewWillDisappear/viewDidDisappear silently changed on iOS5. Depending how your app is organized, this might range from not mattering to being a total disaster.Pre-iOS5, if you added a view with [self.view addSubview:myView], myView didn’t get any notifications. That was weird, but we learned to live with it. Tens (hundreds?) of thousands of developers worked around that and wrote gobs and gobs of code that worked with and counted on that behavior.

Then along comes the iOS5 SDK and someone decided that it would be good to “fix” that behavior, but doing it silently. By silently I mean no compile-time errors or warnings, but apparently it wasn’t deemed worthy of including in the iOS5 release notes either. I’m sure they thought they were doing us a favor. After all, if someone had a problem with it, it’s because they hadn’t structured their code correctly.

So now every view you add/remove will get those notifications. Which is fine, except if you had decided to call those by hand yourself. In that case, now they get executed twice, so you may be allocating things twice, making multiple network calls, and just causing general mayhem.

I also noticed that if you call presentModalViewController with a view controller, all the views underneath the modal view controller will get the viewWillDisappear/viewDidDisappear event. In my case this causes total chaos because those events trigger the freeing of some views that the modal view controller will need.

But wait, it gets even better! From what I’ve been able to tell, if your app compiled with the iOS5 SDK runs on iOS4, then you don’t get those events! That means that now you need to be ready to both deal with receiving them and not receiving them at the same time. Is that useful to anyone (other than devs making iOS5+ apps)?

Fortunately, existing apps compiled with the iOS4 SDK are consistent running both on iOS4 and iOS5 (no view events automatically generated). Phew! Dodged that bullet.

It seems I complain a lot lately, but this seems like another very justified complaint. Wasn’t there a way for Apple to add that behavior to new functions without changing the old ones? For example, they could have introduced UIView:addSubview:withNotifications: and eventually deprecated the old ones. Yes, that means that they would have had to duplicate 10-20 functions related to adding/removing views, but they wouldn’t have broken half the iOS code out there.

This is probably my fault for relying on viewDidAppear events and calling them myself. I started working around this problem by being super-careful about how and when I call viewDidAppear, but as soon as I realized that iOS4 does it differently, I threw in the towel and went for a different approach. Now, I just implement my own function in the different view controllers and call that instead of viewDidAppear. That way Apple can change those events all they want and it won’t affect me anymore.

Of course, all of this only matters if you’re making heavy use of view controllers (which unfortunately I am doing in Flower Garden). I’ve already said that my future game projects are going to be UIKit-free, so I won’t have to deal with this kind of problems again.

18 Comments

  1. UIKit-free is the way to go for games.

    Ever since transitioning away from games (all custom code) to applications (all UIKit) I find myself shouting at the screen a lot more and spending large amounts of time trying to get the ‘apple magic’ correct.

    The very disconcerting part is it is not possible to accurately estimate the amount of time it will take to do a UIKit related task. Will it be 30 seconds because its supported in IB? Will it be 2 days because it is custom roll-your-own code with hacks to workaround quirky and undocumented behaviour?

    The easy solution to iOS 5 quirks is to not compile against iOS 5. It’s always a good idea to use the oldest version possible.

    This means 3.1 (if no camera access is needed) and 4.2 (if camera access is needed).

    All the new fluff is good for Apple but bad for us developers.

    • I think that has more to do with a lack of framework knowledge more than with UIKit being “teh sucks”.
      The fact that you complain about not being able to estimate a task shows that. If you have enough experience you know what is possible and what is not. I wonder what “quirky and undocumented behaviour” you talk about.

      • I disagree. I’ve used UIKit extensively in all my game projects and I’ve *always* regretted it at the end. 
        – Behaviors change from under you
        – It’s really hard to to tell how many system resources you’re using or what performance impact something will have (like displaying a UIViewImage rotated and scaled for the first time)
        – ObjC isn’t cross platform (apart from Macs)
        – UIKit/ObjC insists on a programming model completely opposed to what I like (and the rest of my code)
        – Tracking down bugs/quirkiness in UIKit can take forever (and there’s no source code available for us). I went through that mixing touch events and UIScrollView in Casey’s Contraptions.

        In this next project I’ll take the extra days up front that it will take me to get some UI going as long as I get the benefit of not relying on it for the whole project. Definitely worth it.

      • +1 to what Noel said.

        I wouldn’t go as far as UIKit being “teh sucks” but I stand firmly by my original statement.

        If you think (accurately) estimating the time required for a UIKit task is easy, then I question if you have ever actually tried it.

        The challenges in UIKit are unknown until you hit them, and its a large enough library that you are always discovering new gotchas (I’ve been doing iOS since launch, and still hit them daily.)

        – How long to create a toolbar+navigation bar? Now what if that they are coloured dark green?
        – How long to customize a UITableViewCell? Now what about adding a custom look for when it is selected?
        – What about using a ScrollView? now what about using a scroll view that is default zoomed out?

        Subtle changes like these can take a 30 second task and turn it into a 2hr task. The solutions change hugely depending on what iOS version you are targeting.

      • That’s just the ‘not invented here’ syndrome. You know how long it takes in your custom framework because you wrote that one and know the internals. Take whatever 3rd party library and you ll encounter the same issues.

        And of course stuff changes when moving along iOS versions. Thats ICT, things are constantly evolving. customizing the GUI has become a whole lot easier in iOS5 than in iOS4.

        Then again I would never resort to using UIKit to build a full game renderer (using UIImages, CALayers and all that stuff), because for real-time situations you lack a lot of control. (the mysteries of UIImage internal caching for instance).

        But for the game GUI, why not, unless you want to do super-fancy stuff.

        FYI the 3 cases you mentioned I already did, more than once. The most painful one being scrollview juggling if you want to support paging and zooming. That quickly gets complicated, I agree.

      • Qt did not have those issues.
        Every other GUI library I have chosen has had warts.

      • A dozen examples of quirky and undocumented behaviour can be found with UIPopovers.

        – depending on how they are presented, tapping outside of the popover (eg: the navbar) will / will not dismiss them
        – depending on location and number of items, the arrow-head changes direction to ‘best fit’ (ignoring arrowDirections)
        - presentModalViewController retains the view, whereas the equivalent presentPopoverFromBarButtonItem does not retain the popover
        – …

    • You have a point, but you’re not completely right.

      There’s a huge amount of turbulence in learning UIKit.  You can be proficient in it but still not be to the level where you completely get it.  I was there myself and I remember what it was like.

      It all eventually makes sense.  The design is internally consistent and the problems are logical.  When faced with a new area in UIKit (and other libraries that are similar) you really can estimate the time you’ll take *and* anticipate what sort of problems might turn up.

      The comments about resource utilization are telling.  I remember that exactly and I now understand it quite well.  Once you get that part of it, a lot of other things become obvious.  Things really clicked into place when I paid attention to how things are rendered and started thinking about battery utilisation.

      That said, you’re not exactly wrong.  It takes a long time to really get it and you’re proficient in using it long before then.  I’d say most developers are operating way, way below that level but still producing decent apps.

      If you can’t take the time to get to that point, maybe you should roll your own interfaces in OpenGL ES.  It’s not a terrible idea.  Kind of a battery hog, though.

  2. It seem like putting a huge constraint on yourself or the project to be UIKit-free, don’t you agree?

  3. Unless I am mistaken you talk about UIViewController and not UIView viewDidAppear/viewWillAppear getting called?

    Well Apple always told not to use UIViewController subclasses if you are not using them as “real” UIViewControllers (for modal, navigation or tabbar use), but to inherit your controller class from NSObject if it is for managing a part of the screen (or part of something in a real UIViewController). Unless I am mistaken here it seems what you did is “abuse” UIViewController while you should have used NSObject. 

    Until iOS 5 it didn’t make sense to use UIViewController subclasses in those cases either, the typical methods (willAppear, didAppear) are not handled automatically, nor are the memory warnings or viewDidLoad/viewDidUnload.

    in iOS5 they finally allowed developers to use “the real deal” to handle several components of the screen by adding support for child view controllers.

  4. We have apps the still rely on custom container UIViewController classes, which manually call view{Will,Did}{Appear,Disappear} methods in “child” view controllers.  FWIW, we were able to easily “turn off” the new iOS5 behavior in our custom container view controllers by adding/removing our child view controller with -[UIViewController addChildViewController:] and -[UIViewController removeFromParentViewController], _and_ also implementing -[UIViewController automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers] to return NO in our container view controller class.  Maybe that approach might work for you. There is some useful info about these issues in the WWDC 2011 talk “Implementing UIViewController Containment”.

  5. We have apps the still rely on custom container UIViewController classes, which manually call view{Will,Did}{Appear,Disappear} methods in “child” view controllers.  FWIW, we were able to easily “turn off” the new iOS5 behavior in our custom container view controllers by adding/removing our child view controller with -[UIViewController addChildViewController:] and -[UIViewController removeFromParentViewController], _and_ also implementing -[UIViewController automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers] to return NO in our container view controller class.  Maybe that approach might work for you. There is some useful info about these issues in the WWDC 2011 talk “Implementing UIViewController Containment”.

    • Yeah, if you’re using multiple UIViewControllers (which weren’t supported before), then chapados’s suggestion should fix the problem. Apple added UIViewController containment in iOS 5, which officially allowed you to use multiple UIViewControllers in the hierarchy (containment). Apparently, though, it introduced some side effects for those who were using multiple UIViewControllers “unofficially”.

  6. Looks like the story just doesn’t ends there. I added a controllerB at a button press on controllerA, and the ‘view{Did/Will}Disappear’, never seems to get called. Here’s the log:
    A: loadView
    A: viewWillAppear
    A: viewDidAppear
    B: loadView
    B: viewWillAppear
    B: viewDidAppear

  7. I’m sympathetic to the problem, but… what Apple changed makes perfect sense.  There are only a few very specific cases where you should be calling those methods directly yourself.

    I’ve been warning clients away from misusing view controllers in similar aways for years now.  Ironically, most of their abuses will now work correctly.  So that’s a win.

  8. Hi are you the author of FlowerGarden? I found a big bug there, which is when there is a Bouquest Request, we can just seed it and cut the shoot, don’t need to wait until the flower grow mature. Earning green points becomes so so so easy, which makes me feel very frustrated, I feel like cheating… You may want to fix this bug? Thank you.

  9. I really like your approach.  I am going to do the same with my app (JoynMe).  I’m curious, are you calling  [super viewDidAppear:animated];  in your custom viewDidAppear methods?

Comments are closed.