Write a function that prints a UIView’s subview tree with indentation, like recursiveDescription. Recursive or iterative, and what is the real recursiveDescription?

7.13.2 · L3 · Interview

Reported at: Google

Predict first

What do the recursive and the iterative printer produce for a root with a header, a body and a button in the body?

import UIKit

func describe(_ v: UIView, depth: Int = 0) -> [String] {
    let name = v.accessibilityIdentifier ?? String(describing: type(of: v))
    let f = v.frame
    var lines = [String(repeating: "  ", count: depth) + "\(name) (\(Int(f.minX)),\(Int(f.minY)) \(Int(f.width))x\(Int(f.height)))"]
    for s in v.subviews { lines += describe(s, depth: depth + 1) }
    return lines
}
func describeIteratively(_ root: UIView) -> [String] {            // explicit stack: no recursion depth limit
    var out: [String] = []
    var stack: [(UIView, Int)] = [(root, 0)]
    while let (v, depth) = stack.popLast() {
        let name = v.accessibilityIdentifier ?? String(describing: type(of: v))
        out.append(String(repeating: "  ", count: depth) + name)
        for s in v.subviews.reversed() { stack.append((s, depth + 1)) }   // reversed so the first subview pops first
    }
    return out
}
let root = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 480)); root.accessibilityIdentifier = "root"
let header = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 60)); header.accessibilityIdentifier = "header"
let body = UIView(frame: CGRect(x: 0, y: 60, width: 320, height: 420)); body.accessibilityIdentifier = "body"
let button = UIButton(frame: CGRect(x: 20, y: 20, width: 100, height: 44)); button.accessibilityIdentifier = "button"
root.addSubview(header); root.addSubview(body); body.addSubview(button)
print(describe(root).joined(separator: "\n"))
print("---")
print(describeIteratively(root).joined(separator: "\n"))
  • root (0,0 320x480) / header (0,0 320x60) / body (0,60 320x420) / button (20,20 100x44) with indentation, then root / header / body / button
  • the views in reverse order
  • only root and its direct subviews

Mental model. A directory listing with indentation: you print the folder’s name, then everything inside it one level further in, using the same routine again for each sub-folder.

The straightforward answer is a recursive function: print the view’s line at the current depth, then call itself for each entry of subviews with depth + 1, building the line from the identifier or class name and the frame. subviews is ordered back to front, so the printout lists views in drawing order. The recursion uses stack proportional to the depth of the hierarchy, which is fine for real screens (tens of levels), but a good answer also gives the iterative version: an explicit stack of (view, depth) pairs, pushing the subviews in reverse so the first subview pops first, as the check does (both versions print the same tree).

UIKit already has recursiveDescription, but it is a private method meant for the debugger: in lldb, po view.value(forKey: "recursiveDescription") prints every view with its address and frame. Do not call it from shipping code (private API use can be rejected, and its format is not a contract); the view debugger in Xcode (Q456) gives the same information graphically. If the interviewer asks for more, add the class name, the frame, isHidden and the constraints, or skip subtrees that are hidden; and for very wide trees build one string and print once. Reported at Google (anonymous, unverified) on a phone screen. The program runs in the iOS Simulator.

A depth-first walk prints each view before its children, in the order of subviews; the stack version matches when subviews are pushed in reverse.

Common traps

  • Printing a subview before its parent. The order must be the view first, then its children.
  • Calling the private recursiveDescription from app code. It is a debugging aid, not an API.

They might follow up with

  • How would you print only views that are visible? Skip subtrees where isHidden is true or alpha is zero.
  • How would you find a view by its accessibility identifier? The same traversal, returning the first match.

Related questions

  • 7.13.1 Given two UIViews, find their closest common superview. How do you handle a view that is its own ancestor, or…
  • 23.2.1 How do you debug layout and views with the view debugger, and how do you inspect what your app really sends…
  • 7.2.2 What is the difference between frame and bounds, and what happens to them under a transform?

← Given two UIViews, find their closest common superview. How do you…   Users can push a very large number of view controllers onto a… →