// // 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 1 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); } CGRect bounds = [layer bounds]; if (!CGRectIsEmpty(bounds)) { AppendLabelFloat(result, @"bounds x:", bounds.origin.x); AppendLabelFloat(result, @" y:", bounds.origin.y); AppendLabelFloat(result, @" w:", bounds.size.width); AppendLabelFloat(result, @" h:", bounds.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:NSSelectorFromString(@"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); } CGRect bounds = [self bounds]; if (!CGRectIsEmpty(bounds)) { AppendLabelFloat(result, @"-- bounds x:", bounds.origin.x); AppendLabelFloat(result, @" y:", bounds.origin.y); AppendLabelFloat(result, @" w:", bounds.size.width); AppendLabelFloat(result, @" h:", bounds.size.height); } [result appendString:@"}"]; CGAffineTransform t = [self transform]; if (!CGAffineTransformIsIdentity(t)) { AppendLabelFloat(result, @"-- matrix 11:", t.a); AppendLabelFloat(result, @" 12:", t.b); AppendLabelFloat(result, @" 21:", t.c); AppendLabelFloat(result, @" 22:", t.d); AppendLabelFloat(result, @" tx:", t.tx); AppendLabelFloat(result, @" ty:", t.ty); } if ([self isKindOfClass:[UIScrollView class]]) { UIScrollView* sv = (UIScrollView*)self; float scale = sv.zoomScale; AppendLabelFloat(result, @" scale:", scale); } AppendLabelFloat(result, @" alpha:", self.alpha); 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]; SEL sublayers = NSSelectorFromString(@"sublayers"); if (![layer respondsToSelector:sublayers]) { return @"*** Sorry: This app is not linked with the QuartzCore framework."; } NSMutableString *result = SublayerDescriptionLine(layer); NSArray *layers = [layer performSelector:sublayers]; for (CALayer *l in layers) { [result appendString:SublayerDescriptionAtLevel(l, 1)]; } return result; } @end #endif // DEBUG