How do you debug layout and views with the view debugger, and how do you inspect what your app really sends over the network?

23.2.1 · L3 · Interview

Predict first

Does the recursive description of a small hierarchy contain the label, its frame and its text?

import UIKit

@MainActor func run() {
    let root = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 100))
    let label = UILabel(frame: CGRect(x: 10, y: 10, width: 100, height: 20))
    label.text = "Hi"
    root.addSubview(label)
    let tree = root.value(forKey: "recursiveDescription") as? String ?? ""
    print(tree.contains("UILabel"), tree.contains("frame = (10 10; 100 20)"), tree.contains("text = 'Hi'"))
}
MainActor.assumeIsolated { run() }
  • true true true
  • false false false
  • true false true

Predict first

Does code that reads task metrics (protocol, reuse, timing) compile?

import Foundation

func inspect(_ metrics: URLSessionTaskMetrics) {
    print("redirects:", metrics.redirectCount, "total:", metrics.taskInterval.duration)
    for t in metrics.transactionMetrics {
        print(t.networkProtocolName ?? "?", t.isReusedConnection, t.countOfResponseBodyBytesReceived)
        if let start = t.requestStartDate, let end = t.responseEndDate { print("request to response end:", end.timeIntervalSince(start)) }
    }
}
  • Yes
  • No, metrics are only available in Instruments

Mental model. Debugging a view with only the source is like guessing a room’s layout from the architect’s notes. The view debugger is walking into the room with a torch.

The view debugger (Xcode’s Debug View Hierarchy) captures the live view tree and lets you explode it in 3D, click any view and read its frame, constraints, clipping, hidden state and owning view controller. It answers “why is my view not visible”: it may be behind another view, clipped by a parent, zero-sized, off screen, transparent or have an ambiguous constraint, and the inspector shows which. For text-based debugging po view.value(forKey: "recursiveDescription") (a private debugging method, never ship it) prints the subview tree with frames and text, as the check below captures from a real hierarchy; for constraint problems turn on the console message and put a symbolic breakpoint on UIViewAlertForUnsatisfiableConstraints (Q277), and give constraints and views identifiers so the log names them. SwiftUI offers the same capture (and the view hierarchy still shows the hosting views), plus Self._printChanges() for why a body ran (Q433).

For the network, do not guess from the code. URLSessionTaskMetrics (delivered to a delegate or via the task’s metrics) reports the timing of each phase (DNS, connect, TLS, request, response), the protocol used (HTTP/1.1, h2, h3), whether the connection was reused, and redirect counts, so you can see why a request is slow. Instruments’ network tools and Xcode’s network gauge show traffic from a device, and an HTTP proxy tool (Charles, Proxyman, or mitmproxy) shows the actual requests and responses, including headers and bodies, which is how you find the wrong header, the missing token or the gzipped payload you did not expect; to read HTTPS you install and trust the proxy’s certificate on a test device or simulator, and certificate pinning will rightly block it unless the test build disables it. The Network Link Conditioner (and the developer settings on a device) simulates slow, lossy or offline networks, which is how you reproduce “works on my fast Wi-Fi”.

The debugger’s text dump lists every subview with its frame and key properties, which makes a layout problem visible without a screenshot.

URLSessionTaskMetrics is a public API your app can read and send to its own logging.

Common traps

  • Trusting a proxy’s certificate on a real user device or leaving it installed. It lets the proxy read all HTTPS; use a test device and remove it afterwards.
  • Shipping debugging helpers that call private selectors. recursiveDescription is for the debugger only.

They might follow up with

  • A button does not respond: where do you look first? The view debugger: is another view on top, is the frame zero, is user interaction disabled, is it clipped by its parent?
  • How do you find which phase of a request is slow? URLSessionTaskMetrics breaks the time into DNS, connect, TLS, request and response.

Related questions

  • 23.1.1 What kinds of breakpoints are there in Xcode and LLDB, and how do conditions, actions, symbolic and exception…
  • 23.6.2 How do you reproduce a bug reliably: environment, feature flags, seeded data, launch arguments, simulator…
  • 9.1.1 How does Auto Layout work, and what are intrinsic size, content hugging, compression resistance, priorities…
  • 21.4.1 What makes a SwiftUI body run again, how do you see it, and how do you reduce unnecessary invalidation?

What are zombie objects and malloc stack logging, and how do you… →