Better Debugging with Quick Look

Quick Look was introduced back in OS X 10.5 Leopard as a system-wide quick preview feature for documents and images. When Apple announced Xcode 5 at WWDC 2013, one of the new tools introduced was Quick Look for debugging. With this you could preview images, colors, and other common types while stepping through your application. All you have to do is hover over a supported variable and click on the Quick Look icon Quick Look or hit the space bar when the variable is selected in the Variables View.

Quick Look is really useful when working with Core Graphics or any custom drawing code. For example, last year when working on Luvocracy, we designed a sliding panel over each collection view cell. This was implemented as a lightweight CAShapeLayer that was given a path and translated into a view when needed. Initially, I miscalculated the shape of the path and ended up with an incorrectly drawn overlay view. Being able to use Quick Look saved the day. Viewing the calculated path made it much easier to see what was wrong and fix it.

Quick Look during Debugging

Quick Look on Custom Types

Unfortunately, Xcode 5 only supports Quick Look for built-in types. Thankfully, with the recently released Xcode 5.1, developers can provide their own Quick Look previews by implementing a single method. Simply override ‑debugQuickLookObject and return a Quick Look-compatible type. These Quick Look compatible types can be one of the following:

  • Images using NSImage, UIImage, CIImage (only if the target is OS X Mavericks or later), or NSBitmapImageRep.
  • Colors using NSColor or UIColor.
  • Bezier Paths using NSBezierPath or UIBezierPath.
  • Locations as CLLocation.
  • Views using NSView or UIView.
  • Strings with NSString or NSAttributedString and their mutable variants.
  • URLs with NSURL.
  • Data using NSData.
  • Cursors with NSCursor.

Apple provides great examples of what each of these looks like.

With this new debugging feature in Xcode, the example above could have implemented this method and provided a better visualization of the CAShapeLayer. This layer was exposed to the cell via a custom UIView subclass called OverlayView. Since the path was cached until it needed to change, OverlayView could have enabled Quick Look with just one line of code:

‑ (id)debugQuickLookObject
{
   return [self overlayPath];
}

Let’s visualize Auto Layout

It can be really difficult to debug layout issues when building a view with complicated Auto Layout constraints. Being able to visualize the constraints at runtime could go a long way to help fix broken layouts. Below is a simplistic version of this idea, but it shows what you can do by adding custom Quick Look to your bag of debugging tricks.

The first step is to generate the image we’d like to display. In my sample view, I’ll be displaying a simple image and some text all aligned on their center Y values.

Sample View

I’ll create a simple method to create the constraints and, if we’re currently debugging, also create the visualization.

‑ (void)setupConstraints
{
    NSDictionary *views = NSDictionaryOfVariableBindings(_imageView, _nameLabel);
    NSArray *constraintStrings = @[ @"H:|-20-[_imageView(100)]-20-[_nameLabel]-20-|",
                                    @"V:|[_nameLabel]|", 
                                    @"V:|[_imageView]|" ];

    [self twt_addConstraintsWithVisualFormatStrings:​constraintStrings views:​views];

#if DEBUG 
    [self resetConstraintVisualization]; 
#endif
}

Note: ‑twt_addConstraintsWithVisualFormatStrings:​views: is from our convenient constraint addition category in Toast.

The ‑resetConstraintVisualization method can now scan through the constraints and draw redlines to help visualize them.

‑ (void)resetConstraintVisualization
{
    UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0);

    // draw the existing view hierarchy into the image context
    [self drawViewHierarchyInRect:​self.bounds afterScreenUpdates:​YES];

    // draw red lines for constraints
    CGContextRef context = UIGraphicsGetCurrentContext();
    [[UIColor redColor] setStroke];

    for (NSLayoutConstraint *constraint in self.constraints) {

        // only draw leading edge constraints for simplicity
        if (constraint.firstAttribute == NSLayoutAttributeLeading) {
            UIView *leadingView = (UIView *)[constraint secondItem];
            CGFloat startX = CGRectGetMaxX(leadingView.frame);
            CGFloat startY = CGRectGetMidY(leadingView.frame);
            if (leadingView == self) {
                startX = CGRectGetMinX(self.bounds);
                startY = CGRectGetMidY(self.bounds);
            }

            UIView *trailingView = (UIView *)[constraint firstItem];
            CGFloat endX = CGRectGetMinX(trailingView.frame);
            CGFloat endY = CGRectGetMidY(trailingView.frame);

            // draw line for this constraint
            CGContextMoveToPoint(context, startX, startY);
            CGContextAddLineToPoint(context, endX, endY);
            CGContextStrokePath(context);
        }
    }

    self.debugLayoutImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
}

The second and final step is the easiest. Implement ‑debugQuickLookObject and return self.debugLayoutImage.

‑ (id)debugQuickLookObject
{
    return self.debugLayoutImage;
}

Quick Look at Auto Layout

With great power, comes great responsibility

It’s important to remember that ‑debugQuickLookObject runs in the debugger inside a paused application. Your implementation should do as little work as possible and avoid any side effects. The recommended approach is to cache the Quick Look object and simply return it as in the two examples above.

Debugging with Quick Look is a huge boost to productivity. Instead of logging graphics-related objects and properties, you can now visualize what those objects actually look like. This means you can spend less time debugging and more time solving the fun problems in your app.

Tweet about this on Twitter