Logging the view hierarchy

I found some nice code on Stack Overflow for outputting the view hierarchy to the console, and I thought it was worthy of a few mention after some minor modifications of course.

The code takes in a view and iterates through the whole view hierarchy from that view, outputting the vital statistics of the views to the console. I modified the code a bit to pass in a parameter initially to control if the logging routine walks the superviews, and I also made the output show the screen coordinates in integers instead of showing floats with 4 decimal places.

Now, with a line like this in my application (don’t forget of course to import the InspectView header file):

[InspectView dumpViewToLog:self.view findParent:YES];

I can get this in the console:

2011-07-09 16:33:10.475 MyApplication[1038:207] 
Inspect view hierarchy -----------------------------------
Original view is (0x6840800)
UIWindow (0x6314b00): frame origin: (0, 0) size: (320, 480) [tag=0] UIView : UIResponder : NSObject : 
.   UIImageView (0x6314a70): frame origin: (0, 20) size: (320, 460) [tag=0] UIView : UIResponder : NSObject : 
.   UILayoutContainerView (0x63190a0): frame origin: (0, 0) size: (320, 480) [tag=0] UIView : UIResponder : NSObject : 
.   .   UINavigationTransitionView (0x631a7e0): frame origin: (0, 0) size: (320, 480) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   UIViewControllerWrapperView (0x6149dd0): frame origin: (0, 64) size: (320, 416) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   UITableView (0x6840800): frame origin: (0, 0) size: (320, 416) [tag=0] UIScrollView : UIView : UIResponder : NSObject : 
.   .   .   .   .   UITableViewCell (0x615cad0): frame origin: (0, 290) size: (320, 129) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   UIGroupTableViewCellBackground (0x614b9b0): frame origin: (9, 0) size: (302, 129) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   UITableViewCellContentView (0x615d590): frame origin: (10, 1) size: (300, 126) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   .   UILabel (0x614b2f0): frame origin: (10, 5) size: (279, 21) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   .   UILabel (0x6149800): frame origin: (10, 34) size: (227, 84) [tag=4] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   UIImageView (0x615c950): frame origin: (10, 1) size: (300, 10) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   UITableViewCell (0x6385000): frame origin: (0, 204) size: (320, 66) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   UIGroupTableViewCellBackground (0x615b0f0): frame origin: (9, 0) size: (302, 66) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   UITableViewCellContentView (0x6385710): frame origin: (10, 1) size: (300, 63) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   .   UILabel (0x6379b10): frame origin: (10, 6) size: (277, 21) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   .   UILabel (0x6379eb0): frame origin: (10, 35) size: (227, 21) [tag=3] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   UIImageView (0x6147b70): frame origin: (10, 1) size: (300, 10) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   UITableViewCell (0x6386b60): frame origin: (0, 76) size: (320, 108) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   UIGroupTableViewCellBackground (0x63872c0): frame origin: (9, 0) size: (302, 108) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   UITableViewCellContentView (0x6386d50): frame origin: (10, 1) size: (267, 105) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   .   UILabel (0x6387de0): frame origin: (10, 35) size: (227, 63) [tag=2] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   .   UILabel (0x6388350): frame origin: (10, 6) size: (235, 21) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   UIButton (0x6385c10): frame origin: (267, 1) size: (43, 105) [tag=0] UIControl : UIView : UIResponder : NSObject : 
.   .   .   .   .   .   .   UIImageView (0x6386d80): frame origin: (7, 37) size: (29, 31) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   UIImageView (0x6387eb0): frame origin: (10, 1) size: (300, 10) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   UITableViewCell (0x63860a0): frame origin: (0, 10) size: (320, 46) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   UIGroupTableViewCellBackground (0x6386de0): frame origin: (9, 0) size: (302, 46) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   UITableViewCellContentView (0x63882e0): frame origin: (10, 1) size: (267, 43) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   .   UILabel (0x6385530): frame origin: (10, 0) size: (247, 43) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   UIButton (0x6385860): frame origin: (267, 1) size: (43, 43) [tag=0] UIControl : UIView : UIResponder : NSObject : 
.   .   .   .   .   .   .   UIImageView (0x6386000): frame origin: (7, 6) size: (29, 31) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   .   UIImageView (0x6386450): frame origin: (10, 1) size: (300, 10) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   UIImageView (0x615c020): frame origin: (0, 409) size: (320, 7) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   .   UIImageView (0x614a810): frame origin: (312, 1) size: (7, 144) [tag=0] UIView : UIResponder : NSObject : 
.   .   UINavigationBar (0x63192c0): frame origin: (0, 20) size: (320, 44) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   UILabel (0x6149360): frame origin: (62, 0) size: (200, 44) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   UILabel (0x61493d0): frame origin: (0, 19) size: (200, 20) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   .   UILabel (0x615c810): frame origin: (0, -1) size: (200, 24) [tag=0] UIView : UIResponder : NSObject : 
.   .   .   UINavigationItemButtonView (0x6349b80): frame origin: (5, 7) size: (49, 30) [tag=0] UINavigationItemView : UIView : UIResponder : NSObject : 
End of view hierarchy -----------------------------------

Notice above that my table view that I passed in is the first entry that is indented 4 levels in the above listing. This is because I used YES as the boolean parameter, which walks all the way to the top of the view hierarchy before dumping out all of the views.

Here is the code that generates it. First, the InspectView header file:

//
//  InspectView.h
//
 
#define objectString(anObject) [[anObject description] UTF8String]
 
#import <Foundation/Foundation.h>
#import <CoreFoundation/CoreFoundation.h>
 
@interface InspectView : NSObject 
{
}
 
+ (void)dumpViewToLog:(id)viewObj findParent:(BOOL)findTheParent;
+ (NSString *)dumpViewToString:(id)viewObj findParent:(BOOL)findTheParent;
+ (NSString *)dumpViewToString:(id)viewObj level:(int)level;
 
@end

And the implementation file:

//
//  InspectView.m
//
 
#import "InspectView.h"
 
#define THE_LOG NSLog
 
@implementation InspectView
 
+ (void)dumpViewToLog:(id)viewObj findParent:(BOOL)findTheParent
{
    THE_LOG(@"%@", [self dumpViewToString:viewObj findParent:findTheParent]);
}
 
+ (NSString *)dumpViewToString:(id)viewObj findParent:(BOOL)findTheParent 
{    
    NSString *s = @"\nInspect view hierarchy -----------------------------------" ;
 
    // go up to outtermost view.
    if (findTheParent)
    {
        s = [s stringByAppendingFormat:@"\nOriginal view is (0x%x)", viewObj];
 
        while ([viewObj superview]) 
        {
            viewObj = [viewObj superview];
        }
    }
 
    s = [s stringByAppendingString:[self dumpViewToString:viewObj level:0]];
    s = [s stringByAppendingString:@"\nEnd of view hierarchy -----------------------------------"];
    return s;
}
 
+ (NSString *) dumpViewToString:(id)viewObj level:(int)level 
{
    NSString *s = @"\n";
    // indent to show the current level
    for (int i = 0; i < level; i++) 
    {
        s = [s stringByAppendingString:@".   "];
    }
 
    s = [s stringByAppendingFormat:@"%@ (0x%x): frame origin: (%d, %d) size: (%d, %d) [tag=%d] ", 
         [[viewObj class] description], viewObj,
         (int) (((UIView*)viewObj).frame.origin.x), 
         (int) (((UIView*)viewObj).frame.origin.y),
         (int) (((UIView*)viewObj).frame.size.width),
         (int) (((UIView*)viewObj).frame.size.height),
         ((UIView*)viewObj).tag
         ];  // shows the hex address of input view.
    //  s = [s stringByAppendingFormat:@"%@ : ", [[viewObj class] description] ];
 
    id obj = [viewObj superclass];
 
    while (NULL != obj) 
    {
        s = [s stringByAppendingFormat:@"%@ : ", [[obj class] description]];
        obj = [obj superclass];
    }
 
    // recurse for all subviews
    for (UIView *sub in [viewObj subviews]) 
    {
        s = [s stringByAppendingString: [self dumpViewToString:sub level:(level + 1)]];
    }
 
    return s;
}
 
@end

If you combine today’s post with yesterday’s post (delayed block execution), you can do something like this at the end of the viewDidLoad method of your UITableViewController:

[self performBlock:^{ [InspectView dumpViewToLog:self.view findParent:NO]; } afterDelay:1.0f];
[self performBlock:^{ [InspectView dumpViewToLog:self.view findParent:NO]; } afterDelay:11.0f];

The result of this is that you will get a dump of your table view hierarchy after 1 second (this gives the table a chance to get drawn), the user will be able to work with the table view for another 10 seconds, and then another dump. This could help you diagnose any kind of issues you may be having with your table views or table view cells.

As usual, keep in mind that since this is code to be used for debugging purposes, this is not production quality code.

BTW, Happy Birthday to one of the all time great Leafs, Red Kelly.

Leave a Reply