SwiftUI Field Guide

Debugging Techniques

One of the most useful debugging techniques in SwiftUI is to add a border to a view. Because a border (under the hood) is just an overlay with a stroked rectangle, it shows the exact size and position of the view it is applied to. We use this technique throughout the entire site. For example, adding a border to the example from the introduction visualizes the padding:

Code
Text("Hello, World!")    .background { Color.accentColor }    .padding()    .border(Color.red)
Preview

Another very useful debugging technique is to use GeometryReader to visualize the size of a view. Again, by putting the geometry reader in an overlay we can see the exact size without changing the layout behavior of the underlying view. Note that we can do this in just a few lines, but we added some extra code to make things look nice.

Code
Text("Hello, world. This is a longer text.")    .overlay {        GeometryReader { proxy in            Text(                "\(proxy.size.width) x \(proxy.size.height)"            )                .font(.caption)                .foregroundStyle(.white)                .padding(4)                .background { Color.purple }                .fixedSize()                .frame(width: proxy.size.width,                       height: proxy.size.height)        }    }
Preview
200

In general, it can be helpful to add an overlay to a view with debug information. This is often easier to parse than when the information is logged to the console, because we see it directly inside our view.

When creating a new view, we often create a new throwaway Xcode project that also includes a Mac target. Because a Mac window is resizable, it quickly helps us debug the resizing behavior of our views. Alternatively, we can put a slider in the preview (or in the view) that lets us control the width of a view. For debugging different color schemes, size categories and orientation, the builtin previews are great.

It can also be helpful to add specific overlays to views. For example, to visualize the value of an alignment guide, we can add a 1pt tall red rectangle in an overlay:

Code
Text("Hello, world")    .font(.largeTitle)    .overlay(        alignment: .centerFirstTextBaseline    ) {        Color.red            .frame(height: 1)    }    .padding(30)
Preview

Logging Proposing and Reporting

To understand the process of proposing and reporting, we can use a custom layout that logs the proposed and reported sizes.

Code
struct DebugLayout: Layout {    let name: String    func sizeThatFits(proposal: ProposedViewSize,                      subviews: Subviews,                      cache: inout ()) -> CGSize {        let result =            subviews[0].sizeThatFits(proposal)        print(name, proposal, result)        return result    }        func placeSubviews(in bounds: CGRect,                       proposal: ProposedViewSize,                       subviews: Subviews,                       cache: inout ()) {        subviews[0].place(at: bounds.origin,                          proposal: proposal)    }}extension View {    func debugLog(_ name: String) -> some View {        DebugLayout(name: name) { self }    }}

Using the above code, we can add .debugLog to any view and see exactly what is proposed and reported. For example, if we add this to the each of the subviews of an HStack, we can see how the HStack is first probing the children for their flexibility by proposing both a zero and an infinite width, and then proposing in order of flexibility.