diff options
-rw-r--r-- | GTMiPhone.xcodeproj/project.pbxproj | 10 | ||||
-rw-r--r-- | iPhone/GTMUIView+SubtreeDescription.h | 47 | ||||
-rw-r--r-- | iPhone/GTMUIView+SubtreeDescription.m | 145 | ||||
-rw-r--r-- | iPhone/GTMUIView+SubtreeDescriptionTest.m | 149 |
4 files changed, 351 insertions, 0 deletions
diff --git a/GTMiPhone.xcodeproj/project.pbxproj b/GTMiPhone.xcodeproj/project.pbxproj index 3f0adce..aad11ef 100644 --- a/GTMiPhone.xcodeproj/project.pbxproj +++ b/GTMiPhone.xcodeproj/project.pbxproj @@ -21,6 +21,8 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 13C1ED4F104896C900907CD8 /* GTMUIView+SubtreeDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = 13C1ED4C104896C900907CD8 /* GTMUIView+SubtreeDescription.m */; }; + 13C1ED50104896C900907CD8 /* GTMUIView+SubtreeDescriptionTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 13C1ED4D104896C900907CD8 /* GTMUIView+SubtreeDescriptionTest.m */; }; 1D3623EC0D0F72F000981E51 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D3623EB0D0F72F000981E51 /* CoreGraphics.framework */; }; 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; }; 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; }; @@ -132,6 +134,9 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 13C1ED4C104896C900907CD8 /* GTMUIView+SubtreeDescription.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMUIView+SubtreeDescription.m"; sourceTree = "<group>"; }; + 13C1ED4D104896C900907CD8 /* GTMUIView+SubtreeDescriptionTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMUIView+SubtreeDescriptionTest.m"; sourceTree = "<group>"; }; + 13C1ED4E104896C900907CD8 /* GTMUIView+SubtreeDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMUIView+SubtreeDescription.h"; sourceTree = "<group>"; }; 1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 1D3623EB0D0F72F000981E51 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 1D6058910D05DD3D006BFB54 /* GTMiPhoneTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GTMiPhoneTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -374,6 +379,9 @@ 64D0F5C50FD3E65C00506CC7 /* GTMUIImage+Resize.h */, 64D0F5C70FD3E65C00506CC7 /* GTMUIImage+Resize.m */, 64D0F5C60FD3E65C00506CC7 /* GTMUIImage+ResizeTest.m */, + 13C1ED4C104896C900907CD8 /* GTMUIView+SubtreeDescription.m */, + 13C1ED4D104896C900907CD8 /* GTMUIView+SubtreeDescriptionTest.m */, + 13C1ED4E104896C900907CD8 /* GTMUIView+SubtreeDescription.h */, 64D0F5CB0FD3E66A00506CC7 /* TestData */, ); path = iPhone; @@ -736,6 +744,8 @@ 8BF4D4190FC7499D009ABC3F /* GTMGoogleSearch.m in Sources */, 64D0F5C80FD3E65C00506CC7 /* GTMUIImage+ResizeTest.m in Sources */, 64D0F5C90FD3E65C00506CC7 /* GTMUIImage+Resize.m in Sources */, + 13C1ED4F104896C900907CD8 /* GTMUIView+SubtreeDescription.m in Sources */, + 13C1ED50104896C900907CD8 /* GTMUIView+SubtreeDescriptionTest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 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 <UIKit/UIKit.h> + +// 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 |