How to Debug Layout Issues Using the `debug` and `debugColor` Properties in FrameLayoutKit

FrameLayoutKit provides the debug and debugColor properties on the base FrameLayout class to draw colored dashed outlines around every layout element in Debug builds, making it easy to visualize frame bounds, alignment issues, and sizing errors during development.

FrameLayoutKit is a lightweight Swift layout framework that simplifies complex UI construction through declarative syntax. When building intricate hierarchies with nested stacks and grids, pinpointing the exact bounds of each layout element can be challenging. The framework's built-in debug and debugColor properties offer an instant visual debugging overlay that reveals the precise geometry of every frame without requiring manual logging or breakpoints.

Understanding the Debug Properties

FrameLayoutKit exposes two developer-friendly flags that render visual boundaries directly on top of your layouts. These properties are available on all layout classes that inherit from FrameLayout.

The debug Property

The debug property is a Bool that toggles the visualization overlay. When set to true, the layout draws a dashed outline around its own bounds rectangle. This visualization is automatically compiled out of Release builds using the #if DEBUG compiler directive, ensuring zero overhead in production.

The debugColor Property

The debugColor property accepts an optional UIColor. If you provide a specific color, all debug outlines for that layout instance use that hue. If you leave it as nil, the framework automatically assigns a random color the first time the view is drawn, making it easy to distinguish overlapping frames by their distinct colors.

Where These Properties Live in the Source Code

The debugging system is implemented across several key files in the kennic/framelayoutkit repository:

  • FrameLayout.swift (lines 155-170): The base class declares both properties and implements the actual drawing logic inside its overridden draw(_:) method.

  • StackFrameLayout.swift (lines 48-60): This container layout overrides the properties solely to propagate values down to every child layout in the hierarchy via applyCommonAttributes(to:).

  • Specialized layouts: GridFrameLayout.swift (lines 84-92), FlowFrameLayout.swift (lines 70-78), and DoubleFrameLayout.swift (lines 85-97) each forward debug and debugColor settings to their internal FrameLayout instances, ensuring consistent visualization across the entire layout tree.

How Debug Visualization Works Internally

The debugging mechanism operates through a tight integration between property setters and UIKit's display system.

Setting the Flags and Triggering Redraw

When you assign layout.debug = true (or use the chainable layout.debug(true) API), the setter in the concrete class updates its local property and immediately walks the child hierarchy to assign the same value to nested layouts. The didSet observer on both debug and debugColor in FrameLayout calls setNeedsDisplay(), but only within a #if DEBUG block. This forces UIKit to invoke draw(_:) on the next display pass without manual intervention.

Drawing the Dashed Outline

Inside draw(_:) in FrameLayout.swift, the method first validates the drawing conditions with guard debug, !isEmpty, bounds != .zero. If valid, the implementation:

  1. Selects a color using debugColor ?? randomColor()
  2. Creates a UIBezierPath representing the view's bounds
  3. Applies a dash pattern via setLineDash to create the characteristic dashed border
  4. Strokes the path to render the visible outline

Propagation Through Container Hierarchies

All container layouts, such as StackFrameLayout and its subclasses HStackLayout, VStackLayout, and ZStackLayout, override the debug properties to forward values to their children. When you set debug = true on the root container, the framework automatically visualizes every nested layout, instantly revealing misplaced frames, incorrect sizes, or hidden views that might otherwise be difficult to identify.

Step-by-Step Debugging Workflow

Follow this systematic approach to identify and resolve layout issues using the debug visualization system.

1. Enable Debugging on the Root Layout

Initialize your top-level layout with debugging enabled to visualize the entire hierarchy:

let root = VStackLayout {
    $0.debug(true)                    // Show outlines for all nested frames
    $0.debugColor(.systemRed)         // Optional: Force a specific color
}

2. Run in a Debug Build

Execute the app in a Debug configuration. You will see a series of colored dashed rectangles matching each FrameLayout instance's actual bounds. If you do not see the outlines, verify that your build settings define the DEBUG flag.

3. Inspect Unexpected Outlines

Analyze the rendered rectangles to diagnose specific issues:

  • Too large or too small: The rectangle's size reveals if a layout is expanding beyond its intended constraints. Check minSize, maxSize, and edgeInsets if the outline exceeds the allocated space.
  • Misaligned: The position of the rectangle exposes translation offsets set via translationX or translationY, or incorrect alignment values that push content outside expected boundaries.
  • Hidden content: If a layout's outline is present but the inner view is invisible, verify the ignoreHiddenView setting or confirm that isEnabled is not set to false.

4. Fine-Tune Colors for Complex Hierarchies

When many nested layouts overlap, assign distinct colors to separate the layers visually:

let header = HStackLayout {
    $0.debug(true).debugColor(.systemBlue)
}
let body = VStackLayout {
    $0.debug(true).debugColor(.systemGreen)
}

5. Disable Before Shipping

Remove the debug flag or set it to false before compiling for release. The drawing code is fully excluded from Release builds via conditional compilation, but clearing the property ensures clean configuration files.

Practical Code Examples

Basic Usage with the Chainable API

The framework provides fluent methods for configuring debug settings inline:

let stack = HStackLayout {
    $0.spacing = 8
    $0.debug(true)              // Enable visual debugging
    $0.debugColor(.orange)      // Optional: Set outline color
    $0.add(UILabel())           // Child views wrap in FrameLayout automatically
    $0.add(UIButton())
}

Selective Debugging on Sub-Layouts

You can disable debugging for specific child layouts while keeping the parent visualization active:

let root = VStackLayout {
    $0.debug(true)              // Root visualizes everything by default
    $0.add {
        $0.debug(false)         // Hide debug outline for this child only
        $0.add(UIImageView(image: placeholderImage))
    }
}

Direct Property Assignment

For layouts configured outside of trailing closure syntax, set the properties directly:

let footer = VStackLayout()
footer.debug = true
footer.debugColor = UIColor.purple
footer.add(UILabel())

Integration with Xcode View Debugger

The debug outlines are rendered as part of the view's draw(_:) implementation, meaning they appear in Xcode's view debugger:

  1. Run the app with debug = true on target layouts
  2. Pause execution and open the view debugger
  3. The dashed outlines appear in the hierarchy preview, making it easy to correlate a visual rectangle with the corresponding FrameLayout node in the tree

Summary

  • debug and debugColor are declared in FrameLayout.swift and inherited by all layout classes, providing a unified debugging interface across the framework.
  • The visualization renders as a dashed outline in Debug builds only (#if DEBUG), ensuring no production performance impact.
  • Container layouts in StackFrameLayout.swift propagate debug flags to all children, allowing a single toggle to visualize entire layout trees.
  • The drawing logic in draw(_:) uses setLineDash to stroke the view's bounds with either the specified debugColor or a randomly assigned color.
  • Use these properties to instantly identify sizing errors, alignment offsets, and hidden views without inserting manual logging or breakpoints.

Frequently Asked Questions

Do debug outlines appear in production App Store builds?

No. The debug drawing code is wrapped in #if DEBUG conditional compilation blocks. When you compile for Release, the draw(_:) method skips the outline rendering entirely, and the property setters do not call setNeedsDisplay(). This ensures zero runtime overhead in shipped applications.

How do I set different colors for nested layout levels?

Assign distinct debugColor values to each layout instance in your hierarchy. Because container layouts propagate their debug settings to children, you must override the color on the specific child if you want it to differ from its parent. For example, set the root to .systemRed, then configure a nested HStackLayout with .systemBlue to differentiate the layers visually.

Why don't I see outlines even with debug = true?

First, verify you are running a Debug build configuration, as the outlines are stripped from Release builds. Second, check that the layout's bounds are not zero and that the layout is not empty; the draw(_:) method guards against drawing when isEmpty or bounds == .zero. Finally, ensure the layout is actually added to the view hierarchy and visible on screen.

Does enabling debug mode affect layout performance?

The performance impact is negligible during development. The drawing operation uses simple Core Graphics path stroking on the CPU, and the setNeedsDisplay() calls are throttled by the normal UIKit display cycle. Since the code is removed from Release builds, there is no shipping performance penalty. However, extremely deep hierarchies with hundreds of nested debug layouts may see minor frame drops during development, which is why the feature is intended for debugging sessions only.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →