17

What object is responsible for dipatching the UIViewController rotation method calls, i.e:

  • shouldAutorotateToInterfaceOrientation:
  • willRotateToInterfaceOrientation:duration:
  • willAnimateFirstHalfOfRotationToInterfaceOrientation:duration:
  • willAnimateSecondHalfOfRotationFromInterfaceOrientation:duration:
  • didRotateFromInterfaceOrientation:

I imagine it is UIApplication (but maybe the AppDelegate or UIWindow).

The next question, is how does the object know which UIViewController to talk to?

How does it know which UIViewController has its view as the subview of the window?

Is there a message you can send or a property you can set (of some object) that sets the "Active" UIViewController for the app?

Dolda2000
  • 25,216
  • 4
  • 51
  • 92
Corey Floyd
  • 25,929
  • 31
  • 126
  • 154
  • Good question, I had this doubt in my mind since days. Was about to ask similar question and bumped across this! – Raj Pawan Gumdal Jun 07 '10 at 05:52
  • I should have asked my question like you did. These are all questions related to the universal one, "custom framework, read the documentation, ok now how am I supposed to know how it all should work". – Henrik Erlandsson Nov 08 '10 at 07:34

4 Answers4

13

It seems that UIApplication is dispatching a message to the active view controller.

But how does your View Controller instance get these messages?

The message is forwarded to the first view controller whose view has been added to the UIWindow instance.

This boils down to 3 basic scenarios:

  1. The ViewController whose view is added directly to the UIWindow instance (single view app)

  2. The navigation Controller in a Navigation based app, then the navigation controller forwards the message to the active views view controller.

  3. The tab bar Controller in a tab bar based app, then the tab bar controller forwards the message to the active views view controller (or the active navigation controller).

The problem you will have, is if you build an app with multiple views, but DO NOT use a Navigation controller or Tab Bar controller. If you swap views in and out of the UIWindow instance manually, you will not receive these messages reliably. This is similar to posts like this one: iPhone viewWillAppear not firing

Just use Apple's conventions for multiple views and you be fine. Hope this saves some one an hour or two

Community
  • 1
  • 1
Corey Floyd
  • 25,929
  • 31
  • 126
  • 154
  • One doubt though, does navigation controller / tab bar controller pass the messages to the subView's controllers or rather they just resize the views? I think that they only resize their subviews, the first view controller does not pass the message down its view hierarchy, they only resize their subviews. – Raj Pawan Gumdal Jun 07 '10 at 05:58
  • exactly, Apple recommends only using one VC per screen. So using "sub" view controllers is not supported and the messages are not automatically forwarded. – Corey Floyd Jun 17 '10 at 04:56
  • Corey, how is it possible to limit oneself to one VC per screen, when for instance you have an app that needs two table views on the same screen? You'd need a VC for each table view, then a VC to manage both of them. Or consider another example: an app which needs tab-bar like behavior but for graphical/art requirements needs a custom tab bar controller? Having only one VC per screen seems an infeasible goal for complex UIs. In these cases, there must be *some* way to reliably control interface rotation events? – Yetanotherjosh Mar 22 '11 at 01:53
  • In my own tests, I've found that when nesting views that have different view controllers controlling them, it can sometimes be arbitrary which view controller receives the shouldAutorotateToInterfaceOrientation message - it's definitely *not* always sent to the view controller that manages the highest child view of the UIWindow's hierarchy - it can most certainly be sent to view controllers corresponding to children of that view controller's view. – Yetanotherjosh Mar 22 '11 at 01:57
4

This is all Magic You Don't Ever Worry About. Just add your controller's root view to the window--or get a tab bar or navigation controller to do it for you--and it will receive these messages.

(But if you poke around with the debugger, you might come to the same conclusion I have: there's some sort of internal table mapping each controller's view back to the controller, and the messages are dispatched based on that link.)


Update: This was truly private magic in 2009 when I first wrote this answer, but subsequent changes to iOS have made the APIs behind it public. The root view controller is now accessible through the UIWindow.rootViewController property, and the tree of descendant view controllers is formed using the UIViewController.childViewControllers property.

Parent view controllers are responsible for notifying their children of orientation changes. (I'm not sure how the root view controller is notified, but you could set a breakpoint and find out yourself.) The -shouldAutomaticallyForwardRotationMethods method decides whether UIViewController will do this for you. If it returns NO, you become responsible for doing so in your -willRotateToInterfaceOrientation:duration:, -willAnimateRotationToInterfaceOrientation:duration:, and -didRotateFromInterfaceOrientation: methods.

Becca Royal-Gordon
  • 17,541
  • 7
  • 56
  • 91
  • It is indeed magic, +1. The root view controller is rotated and resized and the frames of subviews are altered and resized based on autoresizing masks. Dont know why people have down voted this poor guy! – Raj Pawan Gumdal Aug 24 '10 at 05:42
  • +1, I dont see a reason to down vote this answer. It is a shame that people don't say why they down vote an answer. – Piotr Czapla Feb 18 '11 at 11:12
  • And what about when the "magic" doesn't work? I have an app that cannot be shipped because `didRotateFromInterfcaeOrientation:` is never called and auto-resize masks aren't able to position views in the correct location. That is a perfectly valid reason to worry about it, none of the answers in this question have solved it for me - so clearly there is more to it than just "get a tab bar to do it for you". – Abhi Beckert May 31 '13 at 00:00
  • @AbhiBeckert I gave this answer several years ago, at a time when there was no public API for the view controller hierarchy. That API is now available for anyone to use. Look at `UIViewController.childViewControllers` and the other view controller containment APIs. They might help you find what you're looking for. – Becca Royal-Gordon May 31 '13 at 00:48
4

How does it know which UIViewController has its view as the subview of the window?

UIViewController class maintains a static map between views and their view controllers. This map is queried in a few key places inside CocoaTouch. In particular, [UIView nextResponder] queries it and returns the controller if found.

I guess UIWindow does the same lookup with its root view to know which controller to forward rotation events to. Will check this next time I'll be looking something up in the disassembly. (I've spent some time reverse-engineering CocoaTouch to understand how things work there.)

Andrey Tarantsov
  • 8,965
  • 7
  • 54
  • 58
1

I have a similar problem.

I have a game working in landscape orientation (both left and right landscape).

In order to manage a multi-view application I use a root UIviewController, i.e. a dummy UIViewcontroller with a UIview that does nothing. I then add every other UIview to it as a subview.

When starting the app, the emulator quickly rotates to a portrait orientation, and i get every one of my views with it's frame property looking like (0,-80,320,480). This results in the view being clipped by a 160 X 320 rectangle on the right side.

I've noticed that the – shouldAutorotateToInterfaceOrientation: method of each subview is only called once, and that happens when it is added to the root view. Later on, when rotating the device, only the root view gets it's method called.

Unsurprisingly, any one of the – willAnimate* methods calls are only sent to the root view, so, defining any of these methods in subviews is pointless.

These methods are called everytime the orientation is changed to an orientation accepted by the – shouldAutorotateToInterfaceOrientation: method. so I figured i could chnage my subviews' frame property from there.

To accomplish this, I defined the following method in my rootViewController class:

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration
{
    subview1_ViewController.view.frame = CGRectMake(0,0,480,320);
    subview2_ViewController.view.frame = CGRectMake(0,0,480,320);
        ...
}

I don't understand why the frame property is not updated, but I know this workaround works.

Hope this helps....

Robin
  • 10,011
  • 5
  • 49
  • 75
OliveiraX
  • 11
  • 1