Developer Tools for UI Debugging

User Interface of a modern app is a complex structure. Sophisticated relationship between UI elements, layout, and drawing makes debugging process harsh. Knowing available tools can reduce hours of debugging and make process of discovering issues trivial.

In this article I would like to do a quick review of UI debugging tools provided by Apple.

View Hierarchy Debugger

View Hierarchy Debugger provides the ability to inspect and understand the UI hierarchy.

Your app’s hierarchy, views, constraints, and view controllers, displayed as a hierarchical list. Object inspector and size inspector. And even interactive 3D model. All built in in Xcode. How cool is that!

Debug View Hierarchy button is located in the debug bar. Xcode pauses your app in its current state.

View debugging is available for iOS, tvOS, and macOS, simulator and device.

lldb

If you prefer the console over visual tools, you’ll be thrilled to know that UIKit includes nice logging APIs. Small issue — this APIs are private. But we’re not using debug functions in release anyway.

-[UIView recursiveDescription]

— Print UIView hierarchy in a convenient form:

(lldb) po [view recursiveDescription]
<UIView: 0x7faa328013c0; frame = (0 0; 414 736); autoresize = W+H; layer = <CALayer: 0x60400003e260>>
| <UILabel: 0x7faa328045d0; frame = (175 198; 95 21); text = 'Hello, world!'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x6040000916c0>>
| <UIButton: 0x7faa2f408000; frame = (152 325; 50 30); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x60c00003a420>>

Each UIView node includes layout and drawing information, CALayer, and additional specific fields.

-[UIViewController _printHierarchy]

Similar method for UIViewController includes presentation information:

(lldb) po [viewController _printHierarchy]
<UISplitViewController 0x7fb23a605920>, state: disappeared, view: <UILayoutContainerView 0x7fb23d80dc60> not in the window
| <UINavigationController 0x7fb23b817000>, state: disappeared, view: <UILayoutContainerView 0x7fb23d80e280> not in the window
| | <MasterDetailTemplate.MasterViewController 0x7fb23a606140>, state: disappeared, view: <UITableView 0x7fb23b05ac00> not in the window
+ <UINavigationController 0x7fb23b01d600>, state: appeared, view: <UILayoutContainerView 0x7fb23a503150>, presented with: <_UIFullscreenPresentationController 0x7fb23d808f20>
| | <MasterDetailTemplate.ModalViewController 0x7fb23a409660>, state: appeared, view: <UIView 0x7fb23a51dfc0>
  • | symbol indicates child view controllers;
  • + stands for modal presentation.

_printHierarchy method is especially useful when called on the rootViewController of a UIWindow:

(lldb) po [[[UIWindow keyWindow] rootViewController] _printHierarchy]

Worth mentioning — in Swift the console behaves differently depending on the case:

  • when program execution paused from the debug bar we can use Objective-C syntax for messaging, e.g. po [view recursiveDescription] will work (with condition that you have access to the view variable or know the address)
  • when lldb stops on a breakpoint, you must use Swift syntax: po view.recursiveDescription()

Of course, you won’t be able to use private APIs in Swift without exposing them publicly:

Note, po command prints special characters in String. NSString when printed looks more convenient.

Now you should be able to print both hierarchies in Swift:

(lldb) po view.recursiveDescription
(lldb) po UIWindow.key.rootViewController!.printHierarchy

Instruments — Core Animation

Moving from inspecting UI structure to optimizing performance. First thing we should to do is measure Frames Per Second. Human perceives app as responsive when it can work at ~60 FPS. This is our goal.

Core Animation template measures graphics performance of the app. For this example we’re focused only on rendering performance. My app is a UITableView with custom cells. For now it only displays section and row.

I notice scrolling performance is not that smooth. Profiling the app with Instruments reveals that my performance is far from 60 FPS. When I scroll (seconds 4–23) the performance drops to 44 FPS — 16 FPS below the goal, 27% slower.

I suspect the issue is with my cell rendering. With Color Blended Layers debug option enabled I can get better understanding of what is happening.

Color Blended Layers shows view layers that are drawn on top of each other with blending enabled.

To understand why this is a problem, we need to know how final image is created. Final pixel the user see is a composition, blending of top and underlying pixels at the coordinate. This mean that every view layer must be rendered to create final image. When view layer is opaque, blending can be optimized by not drawing underlying layers.

To create rounded corners and shadow I use CALayer properties:

let cardView = UIView()
cardView.layer.cornerRadius = 10.0
cardView.layer.shadowColor = UIColor.black.cgColor
cardView.layer.shadowOpacity = 0.5
cardView.layer.shadowOffset = CGSize(width: 0.0, height: 2.0)
cardView.layer.shadowRadius = 4.0

This makes parts of my view layer transparent, causing unnecessary blending. Because the background is color fill, rendering can be optimized. I will set solid background on my views and use background image without alpha:

let cardView = UIImageView(image: UIImage(named: "cardBackground"))

Simple trick allows me to get rid of blending:

Performance is now 58–59 FPS and scrolling feels smooth.

Best part, you have quick access to Color Blended Layers from iOS Simulator.

Accessibility Inspector

UIKit provides impressive support for accessibility and VoiceOver in particular. When things don’t work way we expect, Xcode provides Accessibility Inspector.

Besides debugging, the tool can perform audit of your app and highlight various issues, for instance Dynamic Type support.

Accessibility Inspector is hidden in Xcode > Open Developer Tool > Accessibility Inspector.

Pixie

Pixie will help you implement pixel-perfect design. This tiny tool allows you literally inspect every pixel of your app:

Pixie can be downloaded from Apple Developer website (Xcode > Open Developer Tool > More Developer Tools…) in Additional Tools for Xcode package.


Thank you for reading. If you like the article, please follow me and check my github.

Show authors more ❤️ with 👏’s

And hope to see you next time 😊