Understanding iOS View Controllers and Orientation
Overview of View Controller Hierarchy
In iOS development, a UIViewController
is responsible for managing the visual appearance and behavior of its associated view. A typical application consists of multiple view controllers, which are organized in a hierarchical structure. Each view controller has a designated parent-child relationship, where a child view controller inherits properties and behavior from its parent.
The Problem with Fixed Orientation
In this scenario, we have two view controllers: vc1
and vc2
. vc1
supports only portrait orientation, while vc2
supports all orientations. We want to achieve the following:
vc1
remains in portrait mode when displaying.vc2
can be rotated to landscape mode independently ofvc1
.
The Solution: Using View Controllers as Containers
One approach to solving this problem is by using a single UIViewController
(let’s call it vs1
) as the container and two other view controllers (vc1
and vc2
) as child view controllers. This setup allows us to manage the orientation of both vc1
and vc2
.
Adding Child View Controllers
To add child view controllers, we use the addChildViewController:
method in conjunction with addSubview:
.
[vs1 addChildViewController:vc1];
[vs1.view addSubview:vc1.view];
[vs1 addChildViewController:vc2];
[vs1.view addSubview:vc2.view];
This code adds both vc1
and vc2
as child view controllers of vs1
, which serves as the main container.
Detecting Active View Controller
To detect the currently active view controller, we can use the viewControllers
property of the parent view controller (vs1
). However, this approach has a limitation: it only returns an array of child view controllers, not the current active one. To resolve this issue, we’ll need to implement our own method to identify the active view controller.
Detecting Active View Controller
We can modify the viewControllers
property by overriding the didMoveToParentViewController:
method and checking if it’s called for each child view controller.
- (void)addChildViewController:(UIViewController *)controller {
// ...
[controller didMoveToParentViewController:self];
// ...
}
However, this approach still has limitations. We need a more reliable way to detect the active view controller.
Detecting Active View Controller: Using the Window
The most straightforward method is by checking the window
property of the parent view controller (vs1
). The window contains the root view of the application, which always points to the currently active view controller.
- (void)awakeFromNestingTransition:(id<OS_nestingtransaction>)transition {
UIViewController *nextVC = [self.nextVC valueForKey:@"nextViewController"];
// Check if this is the current view controller:
self = nextVC;
}
In the code above, we override awakeFromNestingTransition:
, which gets called when a child view controller (in our case, vc1
and vc2
) moves into its parent.
[self.viewcontroller transitionWillBeginWithCompletionHandler:^(void) {
// Do nothing
}];
When the window changes to our root view controller (vs1
), we know that this is the current active view controller. We can use this information to determine which child view controller should be in landscape mode.
Controlling Orientation
To control the orientation of each child view controller, we’ll implement the supportedInterfaceOrientations
method for both vc1
and vc2
.
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
// Portrait only
return UIInterfaceOrientationMaskPortrait;
}
- (BOOL)shouldAutomaticallyAdjustForOrientation:(UIInterfaceOrientation)interfaceOrientation {
// No, we don't want to adjust for orientation.
return NO;
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
// Landscape only
return UIInterfaceOrientationMaskLandscape;
}
- (BOOL)shouldAutomaticallyAdjustForOrientation:(UIInterfaceOrientation)interfaceOrientation {
// No, we don't want to adjust for orientation.
return NO;
}
However, when using these methods, we’re not setting any of our child controllers’ orientations.
@interface Vc1 : UIViewController
@end
@implementation Vc1
- (void) didShow {
[self setViewOrientation:UIInterfaceOrientationLandscapeRight];
}
// ...
@end
@interface Vc2 : UIViewController
@end
@implementation Vc2
- (void) didShow {
[self setViewOrientation:UIInterfaceOrientationPortrait];
}
// ...
@end
This way, vc1
will display in landscape orientation and vc2
will display in portrait orientation when they appear on the screen.
Putting it all Together
To sum up our solution: we use a single parent view controller (vs1
) as the main container. The child view controllers are added to this container using addChildViewController:
and then managed accordingly.
We implement our own method to detect the active view controller by overriding the didMoveToParentViewController:
method and checking if it’s called for each child view controller.
For controlling the orientation of each child, we use the supportedInterfaceOrientations
method and set their orientations accordingly using setViewOrientation:
, in the code that appears in our child controllers.
By following this approach, we’re able to manage the orientation of multiple view controllers independently.
Last modified on 2025-04-13