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:
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.
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:
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.
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.