Exploring View Hierarchy

Dmytro Anokhin
6 min readJan 30, 2019

Controlling view hierarchy is important for architecture, preventing visual and user interaction bugs, and also performance optimizations. Many issues I see related to touch events not handled or layout off from expected. Understanding how view and view controller hierarchies interact and ability to easily navigate through this interconnection will delight your development process.

This article is about how UIView, UIViewController, and UIWindow connect. I explain how to traverse hierarchy of views, view controllers, and the responder chain. We will explore how app’s structure looks with push, modal, and embed presentations. And build the console version of Debug View Hierarchy tool in Xcode.

Debug View Hierarchy in Xcode

UI Tools

The Debug View Hierarchy in Xcode captures a snapshot of user interface and allows to navigate and inspect view objects.

Sometimes the console is more convenient. We can use private methods to introspect the UI.

-[UIView recursiveDescription]-[UIViewController _printHierarchy]

You can pause app execution and use expressions in the console to call recursiveDescription and _printHierarchy.

(lldb) expr -l objc -O -- [[[UIApplication sharedApplication] keyWindow] recursiveDescription](lldb) expr -l objc -O -- [[[[UIApplication sharedApplication] keyWindow] rootViewController] _printHierarchy]

-l objc -O -- is used to switch context from Swift to Objective-C.

View Hierarchy

Apps often contain many views and more than one view controller, building up two hierarchies:

  • UIView hierarchy is a tree with it’s root in a window (UIWindow is a subclass of UIView). We can traverse this tree using subviews and superview properties.
  • UIViewController hierarchy starts from the rootViewController of a window. View controller can present or be a container for other view controllers, creating two different hierarchies.

Presented View Controllers

When we display view controller using Present Modally or Preset As Popover segue or present(_:animated:completion:) method, we create presenting presented connection.

Presented view controllers build up a stack of a kind: the last view controller we present is visible and typically dismissed first.

Presented view controllers connect in a doubly linked list with presentedViewController and presentingViewController properties.

Presented View Controller Hierarchy

Container View Controllers

Container view controller manages other view controllers it owns. In a storyboard we use Show and Show Detail segue or we can use addChild(_:) method, creating parent child connection.

This forms a tree, that can be traversed with children and parent properties.

Container View Controller Hierarchy

Many common view controllers are containers: UINavigationController, UITabBarController, UISplitViewController, UIPageViewController.

Combination is possible, i.e. when a container presents a different view controller. But it is not possible for a view controller to be presented and in a container at the same time.

When a container presents we get combination of two structures, presented view controller can form another tree.

Container and Presented View Controller Hierarchy

Differences between two hierarchies become distinct when it comes to event handling. In a tree siblings are equivalent where in a stack the top has priority.

Responder Chain

UIKit uses the responder chain to receive and handle events. This will help us traverse view and view controller hierarchies.

The responder chain connects responder objects from a view to application delegate:

UIView → UIViewController → UIWindow → UIApplication → UIApplicationDelegate

There can be many views and view controllers in the responder chain. We can traverse the responder chain using the next property:

  • For the view the next is it’s superview, or the view controller if the view is the root view;
  • For the view controller the next is it’s presenting view controller or the window.

The responder chain is a list and we can write a simple function to traverse it:

UIResponder+ResponderChain.swift

We can use it in LLDB in Swift, but in a rather cumbersome way. We need to import UIKit and the app module first:

(lldb) expr import UIKit
(lldb)
expr import <App Module Name>

Then we can take the address of the responder object from one of the previous commands, cast it to UIResponder, and use the extension:

(lldb) expr print(unsafeBitCast(<UIResponder Address>, UIResponder.self).responderChain)

Note: you may also need to set target language:

(lldb) settings set target.language swift

Or switch context for every expression: expr -l Swift -- import UIKit

You can create a breakpoint somewhere early to automate this:

Common Hierarchies

Let’s take a look at a storyboard with some common kinds of segues and hierarchies they create.

We have push, modal, and embed segue. The initial view controller is UINavigationController.

Root view controller with Embed (Yellow), Modal (Magenta), and Push (Cyan) view controllers

We can pause the app execution and introspect view hierarchy, using the console and commands above, to see the structure. I used different class names to better illustrate it.

Root and Embed view controller

In the root scene we can track path in the view controller’s tree:

navigation → [ root → [ yellow ] ]

View hierarchy is alike with additional views introduced by navigation controller.

We can see how an event will travel in the responder chain from the yellow view to the app delegate.

View Controller, View, and Responder Chain for the YellowView (Embed)
View controller pushed in navigation controller

Push presentation is implemented by the navigation controller, and we can see this reflected in view controller hierarchy:

navigation → [ root → [ yellow ] , cyan ]

View hierarchy has changed because the navigation controller displays only the top view controller in the navigation stack.

View Controller, View, and Responder Chain for the CyanView (Push)
View controller presented modally

Presented view controller marked with + in the log to distinguish it from the child view controllers:

navigation → [ root → [ yellow ] ] + magenta

View hierarchy contains only views of the visible view controller and some private views.

View Controller, View, and Responder Chain for the MagentaView (Modal)

Building Debug View Hierarchy

View, view controller hierarchies, and responder chain are all the pieces needed to build the Debug View Hierarchy tool. Here how it works:

  • traverseHierarchy method is build using variation of the visitor design pattern with closure;
  • Traversal starts in the window and using Depth First Search goes through view and view controllers in the hierarchy. This way one branch of the hierarchy traversed fully before the next one;
  • Only visible views are processed;
  • The responder chain rules are followed to connect the view and the view controller.
UIWindow+TraverseHierarchy.swift

The traverseHierarchy can be used to print out the view hierarchy in the console:

(lldb) expr UIApplication.shared.keyWindow?.traverseHierarchy { responder, _ in print(responder) }

By implementing a convenience method:

UIWindow+PrintHierarchy.swift

Voilà:

(lldb) expr UIWindow.printKeyWindowHierarchy()

To better understand view hierarchy I recommend reading documentation and View Controller Programming Guide for iOS, The View Controller Hierarchy.

And add the Debug View Hierarchy in Xcode tool to your workflow.

Hope you find this article useful. Cheers!

--

--

Dmytro Anokhin

iOS Developer, here to share best practices learned through my experience. You can find me on Twitter: https://twitter.com/dmytroanokhin