From a8ff4e805f875b8d565df63ad1e9fd56e699b3e1 Mon Sep 17 00:00:00 2001 From: "gtm.daemon" Date: Fri, 28 Aug 2009 23:00:28 +0000 Subject: [Author: oster] subtreeDescription is a debugging tool for displaying UIView hierarchies in the Xcode Console window in DEBUG builds. As it says in the .h file: pause in the debugger and type something like: (gdb) po [[[UIApplication sharedApplication] keyWindow] subtreeDescription] and a nicely pretty printed version of the view hierarchy will be printed in the debugger console window. DELTA=179 (179 added, 0 deleted, 0 changed) R=dmaclach,mikemorton,thomasvl --- iPhone/GTMUIView+SubtreeDescription.h | 47 ++++++++++ iPhone/GTMUIView+SubtreeDescription.m | 145 +++++++++++++++++++++++++++++ iPhone/GTMUIView+SubtreeDescriptionTest.m | 149 ++++++++++++++++++++++++++++++ 3 files changed, 341 insertions(+) create mode 100644 iPhone/GTMUIView+SubtreeDescription.h create mode 100644 iPhone/GTMUIView+SubtreeDescription.m create mode 100644 iPhone/GTMUIView+SubtreeDescriptionTest.m (limited to 'iPhone') diff --git a/iPhone/GTMUIView+SubtreeDescription.h b/iPhone/GTMUIView+SubtreeDescription.h new file mode 100644 index 0000000..1e714ed --- /dev/null +++ b/iPhone/GTMUIView+SubtreeDescription.h @@ -0,0 +1,47 @@ +// +// GTMUIView+SubtreeDescription.h +// +// Copyright 2009 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +#import + +// This entire file, and the corresponding .m is DEBUG only. +// But you can define INCLUDE_UIVIEW_SUBTREE_DESCRIPTION to no-zero to override. +#if DEBUG || INCLUDE_UIVIEW_SUBTREE_DESCRIPTION + +// Example, in debugger, pause the program, then type: +// po [[[UIApplication sharedApplication] keyWindow] subtreeDescription] + +@interface UIView (SubtreeDescription) + +// Returns one line, without leading indent, but with a trailing newline, +// describing the view. +// If you define a |myViewDescriptionLine| method in your own UIView classes, +// this will append that result to its description. +- (NSString *)gtm_subtreeDescriptionLine; + +// For debugging. Returns a nicely indented representation of this view's +// subview hierarchy, each with frame and isHidden. +- (NSString *)subtreeDescription; + +// For debugging. Returns a nicely indented representation of this view's +// layer hierarchy, with frames and isHidden. +// Requires QuartzCore to be useful, but your app will still link without it. +// TODO: should there be an analog of myViewDescriptionLine for layers? +- (NSString *)sublayersDescription; + +@end + +#endif // DEBUG diff --git a/iPhone/GTMUIView+SubtreeDescription.m b/iPhone/GTMUIView+SubtreeDescription.m new file mode 100644 index 0000000..bae0e2f --- /dev/null +++ b/iPhone/GTMUIView+SubtreeDescription.m @@ -0,0 +1,145 @@ +// +// GTMUIView+SubtreeDescription.m +// +// Copyright 2009 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +#import "GTMUIView+SubtreeDescription.h" + +#if DEBUG || INCLUDE_UIVIEW_SUBTREE_DESCRIPTION + +static void AppendLabelFloat(NSMutableString *s, NSString *label, float f) { + [s appendString:label]; + // Respects gcc warning about using == with floats. + if (fabs(f - floor(f)) < 1.0e-8) { // Essentially integer. + int d = f; + // Respects gcc warning about casting floats to ints. + [s appendFormat:@"%d", d]; + } else { + [s appendFormat:@"%3.1f", f]; + } +} + + +static NSMutableString *SublayerDescriptionLine(CALayer *layer) { + NSMutableString *result = [NSMutableString string]; + [result appendFormat:@"%@ %p {", [layer class], layer]; + CGRect frame = [layer frame]; + if (!CGRectIsEmpty(frame)) { + AppendLabelFloat(result, @"x:", frame.origin.x); + AppendLabelFloat(result, @" y:", frame.origin.y); + AppendLabelFloat(result, @" w:", frame.size.width); + AppendLabelFloat(result, @" h:", frame.size.height); + } + [result appendFormat:@"}"]; + if ([layer isHidden]) { + [result appendString:@" hid"]; + } + [result appendString:@"\n"]; + return result; +} + +// |sublayersDescription| has a guard so we'll only call this if it is safe +// to call. +static NSMutableString *SublayerDescriptionAtLevel(CALayer *layer, int level) { + NSMutableString *result = [NSMutableString string]; + for (int i = 0; i < level; ++i) { + [result appendString:@" "]; + } + [result appendString:SublayerDescriptionLine(layer)]; + // |sublayers| is defined in the QuartzCore framework, which isn't guaranteed + // to be linked to this program. (So we don't include the header.) + NSArray *layers = [layer performSelector:@selector(sublayers)]; + for (CALayer *l in layers) { + [result appendString:SublayerDescriptionAtLevel(l, level+1)]; + } + return result; +} + +@implementation UIView (SubtreeDescription) + +// TODO: Consider flagging things which might help in debugging: +// - alpha < 10% +// - origin not zero +// - non-opaque +// - transform if not identity +// - view not entirely within ancestor views +// - (possibly) tag==0 +- (NSString *)gtm_subtreeDescriptionLine { + NSMutableString *result = [NSMutableString string]; + [result appendFormat:@"%@ %p {", [self class], self]; + CGRect frame = [self frame]; + if (!CGRectIsEmpty(frame)) { + AppendLabelFloat(result, @"x:", frame.origin.x); + AppendLabelFloat(result, @" y:", frame.origin.y); + AppendLabelFloat(result, @" w:", frame.size.width); + AppendLabelFloat(result, @" h:", frame.size.height); + } + [result appendString:@"}"]; + if ([self isHidden]) { + [result appendString:@" hid"]; + } + + if ([self respondsToSelector:@selector(myViewDescriptionLine)]) { + NSString *customDescription = + [self performSelector:@selector(myViewDescriptionLine)]; + if (customDescription != nil) { + [result appendFormat:@" %@", customDescription]; + } + } + + [result appendString:@"\n"]; + return result; +} + +- (NSString *)gtm_subtreeDescriptionAtLevel:(int)level { + NSMutableString *result = [NSMutableString string]; + for (int i = 0; i < level; ++i) { + [result appendString:@" "]; + } + [result appendString:[self gtm_subtreeDescriptionLine]]; + for (UIView *v in [self subviews]) { + [result appendString:[v gtm_subtreeDescriptionAtLevel:level+1]]; + } + return result; +} + +- (NSString *)subtreeDescription { + NSMutableString *result = + [[[self gtm_subtreeDescriptionLine] mutableCopy] autorelease]; + for (UIView *v in [self subviews]) { + [result appendString:[v gtm_subtreeDescriptionAtLevel:1]]; + } + return result; +} + +// for debugging dump the layer hierarchy, frames and isHidden. +- (NSString *)sublayersDescription { + CALayer *layer = [self layer]; + if (![layer respondsToSelector:@selector(sublayers)]) { + return @"*** Sorry: This app is not linked with the QuartzCore framework."; + } + NSMutableString *result = SublayerDescriptionLine(layer); + NSArray *layers = [layer performSelector:@selector(sublayers)]; + for (CALayer *l in layers) { + [result appendString:SublayerDescriptionAtLevel(l, 1)]; + } + return result; +} + +@end + +#endif // DEBUG + + diff --git a/iPhone/GTMUIView+SubtreeDescriptionTest.m b/iPhone/GTMUIView+SubtreeDescriptionTest.m new file mode 100644 index 0000000..4459952 --- /dev/null +++ b/iPhone/GTMUIView+SubtreeDescriptionTest.m @@ -0,0 +1,149 @@ +// +// GTMUIView+SubtreeDescriptionTest.m +// +// Copyright 2009 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// + +#import "GTMNSObject+UnitTesting.h" +#import "GTMSenTestCase.h" +#import "GTMUIView+SubtreeDescription.h" + +#if !NDEBUG + +@interface GTMUIView_SubtreeDescriptionTest : SenTestCase +@end + +@implementation GTMUIView_SubtreeDescriptionTest + +- (void)testSubtreeDescription { + // Test a single, simple view. + CGRect frame1 = CGRectMake(0, 0, 100, 200); + UIView *view1 = [[[UIView alloc] initWithFrame:frame1] autorelease]; + NSString *actual = [view1 subtreeDescription]; + NSString *format1 = @"UIView %p {x:0 y:0 w:100 h:200}\n"; + NSString *expected = [NSString stringWithFormat:format1, view1]; + STAssertEqualObjects(actual, expected, @"a single, simple view failed"); + + // Test a view with one child. + CGRect frame2 = CGRectMake(2, 2, 102, 202); + UIView *view2 = [[[UIView alloc] initWithFrame:frame2] autorelease]; + [view1 addSubview:view2]; + NSString *actual2 = [view1 subtreeDescription]; + NSString *format2 = @"UIView %p {x:0 y:0 w:100 h:200}\n" + " UIView %p {x:2 y:2 w:102 h:202}\n"; + NSString *expected2 = [NSString stringWithFormat:format2, view1, view2]; + STAssertEqualObjects(actual2, expected2, @"a view with one child"); + + // Test a view with two children. + CGRect frame3 = CGRectMake(3, 3, 103, 203); + UIView *view3 = [[[UIView alloc] initWithFrame:frame3] autorelease]; + [view1 addSubview:view3]; + NSString *actual3 = [view1 subtreeDescription]; + NSString *format3 = @"UIView %p {x:0 y:0 w:100 h:200}\n" + " UIView %p {x:2 y:2 w:102 h:202}\n" + " UIView %p {x:3 y:3 w:103 h:203}\n"; + NSString *expected3 = [NSString stringWithFormat:format3, + view1, view2, view3]; + STAssertEqualObjects(actual3, expected3, @"a view with two children"); + + // Test a view with two children, one hidden. + [view3 setHidden:YES]; + NSString *format4 = @"UIView %p {x:0 y:0 w:100 h:200}\n" + " UIView %p {x:2 y:2 w:102 h:202}\n" + " UIView %p {x:3 y:3 w:103 h:203} hid\n"; + NSString *actual4 = [view1 subtreeDescription]; + NSString *expected4 = [NSString stringWithFormat:format4, + view1, view2, view3]; + STAssertEqualObjects(actual4, expected4, @"with two children, one hidden"); +} + +- (void)testSublayersDescription { + // Test a single, simple layer. + CGRect frame1 = CGRectMake(0, 0, 100, 200); + UIView *view1 = [[[UIView alloc] initWithFrame:frame1] autorelease]; + NSString *actual = [view1 sublayersDescription]; + NSString *format1 = @"CALayer %p {x:0 y:0 w:100 h:200}\n"; + NSString *expected = [NSString stringWithFormat:format1, [view1 layer]]; + STAssertEqualObjects(actual, expected, @"a single, simple layer failed"); + + // Test a layer with one child. + CGRect frame2 = CGRectMake(2, 2, 102, 202); + UIView *view2 = [[[UIView alloc] initWithFrame:frame2] autorelease]; + [view1 addSubview:view2]; + NSString *actual2 = [view1 sublayersDescription]; + NSString *format2 = @"CALayer %p {x:0 y:0 w:100 h:200}\n" + " CALayer %p {x:2 y:2 w:102 h:202}\n"; + NSString *expected2 = [NSString stringWithFormat:format2, + [view1 layer], [view2 layer]]; + STAssertEqualObjects(actual2, expected2, @"a layer with one child"); + + // Test a layer with two children. + CGRect frame3 = CGRectMake(3, 3, 103, 203); + UIView *view3 = [[[UIView alloc] initWithFrame:frame3] autorelease]; + [view1 addSubview:view3]; + NSString *actual3 = [view1 sublayersDescription]; + NSString *format3 = @"CALayer %p {x:0 y:0 w:100 h:200}\n" + " CALayer %p {x:2 y:2 w:102 h:202}\n" + " CALayer %p {x:3 y:3 w:103 h:203}\n"; + NSString *expected3 = [NSString stringWithFormat:format3, + [view1 layer], [view2 layer], [view3 layer]]; + STAssertEqualObjects(actual3, expected3, @"a layer with two children"); + + // Test a layer with two children, one hidden. + [view3 setHidden:YES]; + NSString *format4 = @"CALayer %p {x:0 y:0 w:100 h:200}\n" + " CALayer %p {x:2 y:2 w:102 h:202}\n" + " CALayer %p {x:3 y:3 w:103 h:203} hid\n"; + NSString *actual4 = [view1 sublayersDescription]; + NSString *expected4 = [NSString stringWithFormat:format4, + [view1 layer], [view2 layer], [view3 layer]]; + STAssertEqualObjects(actual4, expected4, @"with two children, one hidden"); +} + +@end + +@interface UIMyTestView : UIView +- (NSString *)myViewDescriptionLine; +@end + +@implementation UIMyTestView +- (NSString *)myViewDescriptionLine { + NSString *result = [NSString stringWithFormat:@"alpha: %3.1f", [self alpha]]; + return result; +} +@end + +@interface GTMUIView_SubtreeSubClassDescriptionTest : SenTestCase +@end + +@implementation GTMUIView_SubtreeSubClassDescriptionTest +- (void)testSubtreeDescription { + CGRect frame1 = CGRectMake(0, 0, 100, 200); + UIView *view1 = [[[UIView alloc] initWithFrame:frame1] autorelease]; + + // Test a view with one child. + CGRect frame2 = CGRectMake(2, 2, 102, 202); + UIView *view2 = [[[UIMyTestView alloc] initWithFrame:frame2] autorelease]; + [view1 addSubview:view2]; + NSString *actual2 = [view1 subtreeDescription]; + NSString *format2 = @"UIView %p {x:0 y:0 w:100 h:200}\n" + " UIMyTestView %p {x:2 y:2 w:102 h:202} alpha: 1.0\n"; + NSString *expected2 = [NSString stringWithFormat:format2, view1, view2]; + STAssertEqualObjects(actual2, expected2, @"a view with one subclassed child"); +} +@end + + +#endif -- cgit v1.2.3