├── .gitignore ├── DCIntrospect ├── DCCrossHairView.h ├── DCCrossHairView.m ├── DCFrameView.h ├── DCFrameView.m ├── DCIntrospect.h ├── DCIntrospect.m ├── DCIntrospectSettings.h ├── DCStatusBarOverlay.h └── DCStatusBarOverlay.m ├── DCIntrospectDemo ├── DCIntrospectDemo.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata └── DCIntrospectDemo │ ├── DCIntrospectDemo-Info.plist │ ├── DCIntrospectDemo-Prefix.pch │ ├── DCIntrospectDemoAppDelegate.h │ ├── DCIntrospectDemoAppDelegate.m │ ├── DCIntrospectDemoViewController.h │ ├── DCIntrospectDemoViewController.m │ ├── circle.png │ ├── en.lproj │ ├── InfoPlist.strings │ └── MainWindow.xib │ └── main.m ├── license.txt └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.diff 2 | *.mode1v3 3 | *.orig 4 | *.pbxuser 5 | *.perspective 6 | *.perspectivev3 7 | *.swp 8 | .DS_Store 9 | build/ 10 | dist/ 11 | project.xcworkspace/ 12 | tags 13 | xcuserdata/ 14 | docs/*.html 15 | docs/referencia 16 | .DS_Store 17 | DCIntrospectDemo/DCIntrospectDemo.xcodeproj/project.xcworkspace/xcuserdata/aircat.xcuserdatad/UserInterfaceState.xcuserstate 18 | DCIntrospectDemo/DCIntrospectDemo.xcodeproj/project.xcworkspace/xcuserdata/domesticcat.xcuserdatad/UserInterfaceState.xcuserstate 19 | DCIntrospectDemo/DCIntrospectDemo.xcodeproj/xcuserdata/aircat.xcuserdatad/xcschemes/xcschememanagement.plist 20 | DCIntrospectDemo/DCIntrospectDemo.xcodeproj/xcuserdata/domesticcat.xcuserdatad/xcschemes/DCIntrospectDemo.xcscheme 21 | DCIntrospectDemo/DCIntrospectDemo/.DS_Store 22 | DCIntrospectDemo/DCIntrospectDemo/DCIntrospectDemoViewController (Air-Cat's conflicted copy 2011-07-27).h 23 | DCIntrospectDemo/DCIntrospectDemo/DCIntrospectDemoViewController (Air-Cat's conflicted copy 2011-07-27).m 24 | DCIntrospectDemo/DCIntrospectDemo/en.lproj/MainWindow (Air-Cat's conflicted copy 2011-07-27).xib -------------------------------------------------------------------------------- /DCIntrospect/DCCrossHairView.h: -------------------------------------------------------------------------------- 1 | // 2 | // DCCrossHairView.h 3 | // 4 | // Created by Domestic Cat on 3/05/11. 5 | // 6 | 7 | 8 | @interface DCCrossHairView : UIView 9 | { 10 | } 11 | 12 | @property (nonatomic, retain) UIColor *color; 13 | 14 | - (id)initWithFrame:(CGRect)frame color:(UIColor *)aColor; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /DCIntrospect/DCCrossHairView.m: -------------------------------------------------------------------------------- 1 | // 2 | // DCCrossHairView.m 3 | // 4 | // Created by Domestic Cat on 3/05/11. 5 | // 6 | 7 | #import "DCCrossHairView.h" 8 | 9 | @implementation DCCrossHairView 10 | @synthesize color; 11 | 12 | - (void)dealloc 13 | { 14 | [color release]; 15 | [super dealloc]; 16 | } 17 | 18 | - (id)initWithFrame:(CGRect)frame color:(UIColor *)aColor 19 | { 20 | if ((self = [super initWithFrame:frame])) 21 | { 22 | self.color = aColor; 23 | self.backgroundColor = [UIColor clearColor]; 24 | self.opaque = NO; 25 | } 26 | 27 | return self; 28 | } 29 | 30 | - (void)drawRect:(CGRect)rect 31 | { 32 | CGContextRef context = UIGraphicsGetCurrentContext(); 33 | [self.color set]; 34 | CGContextMoveToPoint(context, floorf(self.bounds.size.width / 2.0f) + 0.5f, 0.0f); 35 | CGContextAddLineToPoint(context, floorf(self.bounds.size.width / 2.0f) + 0.5f, self.bounds.size.height); 36 | CGContextStrokePath(context); 37 | 38 | CGContextMoveToPoint(context, 0, floorf(self.bounds.size.height / 2.0f) + 0.5f); 39 | CGContextAddLineToPoint(context, self.bounds.size.width, floorf(self.bounds.size.height / 2.0f) + 0.5f); 40 | CGContextStrokePath(context); 41 | } 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /DCIntrospect/DCFrameView.h: -------------------------------------------------------------------------------- 1 | // 2 | // DCFrameView.h 3 | // 4 | // Created by Domestic Cat on 29/04/11. 5 | // 6 | 7 | #import 8 | #import "DCCrossHairView.h" 9 | 10 | @protocol DCFrameViewDelegate 11 | 12 | @required 13 | 14 | - (void)touchAtPoint:(CGPoint)point; 15 | 16 | @end 17 | 18 | @interface DCFrameView : UIView 19 | { 20 | 21 | } 22 | 23 | @property (nonatomic, assign) id delegate; 24 | @property (nonatomic) CGRect mainRect; 25 | @property (nonatomic) CGRect superRect; 26 | @property (nonatomic, retain) UILabel *touchPointLabel; 27 | @property (nonatomic, retain) NSMutableArray *rectsToOutline; 28 | @property (nonatomic, retain) DCCrossHairView *touchPointView; 29 | 30 | /////////// 31 | // Setup // 32 | /////////// 33 | 34 | - (id)initWithFrame:(CGRect)frame delegate:(id)aDelegate; 35 | 36 | //////////////////// 37 | // Custom Setters // 38 | //////////////////// 39 | 40 | - (void)setMainRect:(CGRect)newMainRect; 41 | - (void)setSuperRect:(CGRect)newSuperRect; 42 | 43 | ///////////////////// 44 | // Drawing/Display // 45 | ///////////////////// 46 | 47 | - (void)drawRect:(CGRect)rect; 48 | 49 | //////////////////// 50 | // Touch Handling // 51 | //////////////////// 52 | 53 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /DCIntrospect/DCFrameView.m: -------------------------------------------------------------------------------- 1 | // 2 | // DCFrameView.m 3 | // 4 | // Created by Domestic Cat on 29/04/11. 5 | // 6 | 7 | #import "DCFrameView.h" 8 | 9 | @implementation DCFrameView 10 | @synthesize delegate; 11 | @synthesize mainRect, superRect; 12 | @synthesize touchPointLabel; 13 | @synthesize rectsToOutline; 14 | @synthesize touchPointView; 15 | 16 | - (void)dealloc 17 | { 18 | self.delegate = nil; 19 | [touchPointLabel release]; 20 | [touchPointView release]; 21 | 22 | [super dealloc]; 23 | } 24 | 25 | #pragma mark - Setup 26 | 27 | - (id)initWithFrame:(CGRect)frame delegate:(id)aDelegate 28 | { 29 | self = [super initWithFrame:frame]; 30 | if (self) 31 | { 32 | self.delegate = aDelegate; 33 | self.backgroundColor = [UIColor clearColor]; 34 | self.opaque = NO; 35 | 36 | self.touchPointLabel = [[[UILabel alloc] initWithFrame:CGRectZero] autorelease]; 37 | self.touchPointLabel.text = @"X 320 Y 480"; 38 | self.touchPointLabel.font = [UIFont boldSystemFontOfSize:12.0f]; 39 | self.touchPointLabel.textAlignment = UITextAlignmentCenter; 40 | self.touchPointLabel.textColor = [UIColor whiteColor]; 41 | self.touchPointLabel.backgroundColor = [UIColor colorWithWhite:0.0f alpha:0.65f]; 42 | self.touchPointLabel.layer.cornerRadius = 5.5f; 43 | self.touchPointLabel.layer.masksToBounds = YES; 44 | self.touchPointLabel.alpha = 0.0f; 45 | [self addSubview:self.touchPointLabel]; 46 | 47 | self.rectsToOutline = [NSMutableArray array]; 48 | 49 | self.touchPointView = [[[DCCrossHairView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 17.0f, 17.0f) color:[UIColor blueColor]] autorelease]; 50 | self.touchPointView.alpha = 0.0f; 51 | [self addSubview:self.touchPointView]; 52 | } 53 | return self; 54 | } 55 | 56 | #pragma mark - Custom Setters 57 | 58 | - (void)setMainRect:(CGRect)newMainRect 59 | { 60 | mainRect = newMainRect; 61 | [self setNeedsDisplay]; 62 | } 63 | 64 | - (void)setSuperRect:(CGRect)newSuperRect 65 | { 66 | superRect = newSuperRect; 67 | [self setNeedsDisplay]; 68 | } 69 | 70 | #pragma mark - Drawing/Display 71 | 72 | - (void)drawRect:(CGRect)rect 73 | { 74 | CGContextRef context = UIGraphicsGetCurrentContext(); 75 | 76 | if (self.rectsToOutline.count > 0) 77 | { 78 | for (NSValue *value in self.rectsToOutline) 79 | { 80 | UIColor *randomColor = [UIColor colorWithRed:(arc4random() % 256) / 256.0f 81 | green:(arc4random() % 256) / 256.0f 82 | blue:(arc4random() % 256) / 256.0f 83 | alpha:1.0f]; 84 | [randomColor set]; 85 | CGRect valueRect = [value CGRectValue]; 86 | valueRect = CGRectMake(valueRect.origin.x + 0.5f, 87 | valueRect.origin.y + 0.5f, 88 | valueRect.size.width - 1.0f, 89 | valueRect.size.height - 1.0f); 90 | CGContextStrokeRect(context, valueRect); 91 | } 92 | return; 93 | } 94 | 95 | if (CGRectIsEmpty(self.mainRect)) 96 | return; 97 | 98 | CGRect mainRectOffset = CGRectOffset(mainRect, -superRect.origin.x, -superRect.origin.y); 99 | BOOL showAntialiasingWarning = NO; 100 | if (! CGRectIsEmpty(self.superRect)) 101 | { 102 | if ((mainRectOffset.origin.x != floorf(mainRectOffset.origin.x) && mainRect.origin.x != 0) || (mainRectOffset.origin.y != floor(mainRectOffset.origin.y) && mainRect.origin.y != 0)) 103 | showAntialiasingWarning = YES; 104 | } 105 | 106 | if (showAntialiasingWarning) 107 | { 108 | [[UIColor redColor] set]; 109 | NSLog(@"DCIntrospect: *** WARNING: One or more values of this view's frame are non-integer values. This view will likely look blurry. ***"); 110 | } 111 | else 112 | { 113 | [[UIColor blueColor] set]; 114 | } 115 | 116 | CGRect adjustedMainRect = CGRectMake(self.mainRect.origin.x + 0.5f, 117 | self.mainRect.origin.y + 0.5f, 118 | self.mainRect.size.width - 1.0f, 119 | self.mainRect.size.height - 1.0f); 120 | CGContextStrokeRect(context, adjustedMainRect); 121 | 122 | UIFont *font = [UIFont systemFontOfSize:10.0f]; 123 | 124 | float dash[2] = {3, 3}; 125 | CGContextSetLineDash(context, 0, dash, 2); 126 | 127 | // edge->left side 128 | CGContextMoveToPoint(context, CGRectGetMinX(self.superRect), floorf(CGRectGetMidY(adjustedMainRect)) + 0.5f); 129 | CGContextAddLineToPoint(context, CGRectGetMinX(adjustedMainRect), floorf(CGRectGetMidY(adjustedMainRect)) + 0.5f); 130 | CGContextStrokePath(context); 131 | 132 | NSString *leftDistanceString = (showAntialiasingWarning) ? [NSString stringWithFormat:@"%.1f", CGRectGetMinX(mainRectOffset)] : [NSString stringWithFormat:@"%.0f", CGRectGetMinX(mainRectOffset)]; 133 | CGSize leftDistanceStringSize = [leftDistanceString sizeWithFont:font]; 134 | [leftDistanceString drawInRect:CGRectMake(CGRectGetMinX(superRect) + 1.0f, 135 | floorf(CGRectGetMidY(adjustedMainRect)) - leftDistanceStringSize.height, 136 | leftDistanceStringSize.width, 137 | leftDistanceStringSize.height) 138 | withFont:font]; 139 | 140 | // right side->edge 141 | if (CGRectGetMaxX(self.mainRect) < CGRectGetMaxX(self.superRect)) 142 | { 143 | CGContextMoveToPoint(context, CGRectGetMaxX(adjustedMainRect), floorf(CGRectGetMidY(adjustedMainRect)) + 0.5f); 144 | CGContextAddLineToPoint(context, CGRectGetMaxX(self.superRect), floorf(CGRectGetMidY(adjustedMainRect)) + 0.5f); 145 | CGContextStrokePath(context); 146 | } 147 | NSString *rightDistanceString = (showAntialiasingWarning) ? [NSString stringWithFormat:@"%.1f", CGRectGetMaxX(self.superRect) - CGRectGetMaxX(adjustedMainRect) - 0.5] : [NSString stringWithFormat:@"%.0f", CGRectGetMaxX(self.superRect) - CGRectGetMaxX(adjustedMainRect) - 0.5]; 148 | CGSize rightDistanceStringSize = [rightDistanceString sizeWithFont:font]; 149 | [rightDistanceString drawInRect:CGRectMake(CGRectGetMaxX(self.superRect) - rightDistanceStringSize.width - 1.0f, 150 | floorf(CGRectGetMidY(adjustedMainRect)) - 0.5f - rightDistanceStringSize.height, 151 | rightDistanceStringSize.width, 152 | rightDistanceStringSize.height) 153 | withFont:font]; 154 | 155 | // edge->top side 156 | CGContextMoveToPoint(context, floorf(CGRectGetMidX(adjustedMainRect)) + 0.5f, self.superRect.origin.y); 157 | CGContextAddLineToPoint(context, floorf(CGRectGetMidX(adjustedMainRect)) + 0.5f, CGRectGetMinY(adjustedMainRect)); 158 | CGContextStrokePath(context); 159 | NSString *topDistanceString = (showAntialiasingWarning) ? [NSString stringWithFormat:@"%.1f", mainRectOffset.origin.y] : [NSString stringWithFormat:@"%.0f", mainRectOffset.origin.y]; 160 | CGSize topDistanceStringSize = [topDistanceString sizeWithFont:font]; 161 | [topDistanceString drawInRect:CGRectMake(floorf(CGRectGetMidX(adjustedMainRect)) + 3.0f, 162 | floorf(CGRectGetMinY(self.superRect)), 163 | topDistanceStringSize.width, 164 | topDistanceStringSize.height) 165 | withFont:font]; 166 | 167 | // bottom side->edge 168 | if (CGRectGetMaxY(self.mainRect) < CGRectGetMaxY(self.superRect)) 169 | { 170 | CGContextMoveToPoint(context, floorf(CGRectGetMidX(adjustedMainRect)) + 0.5f, CGRectGetMaxY(adjustedMainRect)); 171 | CGContextAddLineToPoint(context, floorf(CGRectGetMidX(adjustedMainRect)) + 0.5f, CGRectGetMaxY(self.superRect)); 172 | CGContextStrokePath(context); 173 | } 174 | NSString *bottomDistanceString = (showAntialiasingWarning) ? [NSString stringWithFormat:@"%.1f", CGRectGetMaxY(self.superRect) - CGRectGetMaxY(mainRectOffset)] : [NSString stringWithFormat:@"%.0f", self.superRect.size.height - mainRectOffset.origin.y - mainRectOffset.size.height]; 175 | CGSize bottomDistanceStringSize = [bottomDistanceString sizeWithFont:font]; 176 | [bottomDistanceString drawInRect:CGRectMake(floorf(CGRectGetMidX(adjustedMainRect)) + 3.0f, 177 | floorf(CGRectGetMaxY(self.superRect)) - bottomDistanceStringSize.height - 1.0f, 178 | bottomDistanceStringSize.width, 179 | bottomDistanceStringSize.height) 180 | withFont:font]; 181 | 182 | } 183 | 184 | #pragma mark - Touch Handling 185 | 186 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 187 | { 188 | CGFloat labelDistance = 16.0f; 189 | CGPoint touchPoint = [[touches anyObject] locationInView:self]; 190 | 191 | // adjust the point so it's exactly on the point of the mouse cursor 192 | touchPoint.x -= 1; 193 | touchPoint.y -= 2; 194 | 195 | NSString *touchPontLabelString = [NSString stringWithFormat:@"%.0f, %.0f", touchPoint.x, touchPoint.y]; 196 | self.touchPointLabel.text = touchPontLabelString; 197 | 198 | CGSize stringSize = [touchPontLabelString sizeWithFont:touchPointLabel.font]; 199 | CGRect frame = CGRectMake(touchPoint.x - floorf(stringSize.width / 2.0f) - 5.0f, 200 | touchPoint.y - stringSize.height - labelDistance, 201 | stringSize.width + 11.0f, 202 | stringSize.height + 4.0f); 203 | 204 | // make sure the label stays inside the frame 205 | UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; 206 | CGFloat minY = UIInterfaceOrientationIsPortrait(orientation) ? [UIApplication sharedApplication].statusBarFrame.size.height : [UIApplication sharedApplication].statusBarFrame.size.width; 207 | minY += 2.0f; // to keep it touching the top bar 208 | if (frame.origin.x < 2.0f) 209 | frame.origin.x = 2.0f; 210 | else if (CGRectGetMaxX(frame) > self.bounds.size.width - 2.0f) 211 | frame.origin.x = self.bounds.size.width - frame.size.width - 2.0f; 212 | if (frame.origin.y < minY) 213 | frame.origin.y = touchPoint.y + stringSize.height + 4.0f; 214 | 215 | self.touchPointLabel.frame = frame; 216 | self.touchPointView.center = CGPointMake(touchPoint.x + 0.5f, touchPoint.y + 0.5f); 217 | self.touchPointView.alpha = self.touchPointLabel.alpha = 1.0f; 218 | 219 | [self.delegate touchAtPoint:touchPoint]; 220 | } 221 | 222 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 223 | { 224 | [self touchesBegan:touches withEvent:event]; 225 | } 226 | 227 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 228 | { 229 | [UIView animateWithDuration:0.08 animations:^{ 230 | self.touchPointView.alpha = self.touchPointLabel.alpha = 0.0f; 231 | }]; 232 | } 233 | 234 | @end 235 | -------------------------------------------------------------------------------- /DCIntrospect/DCIntrospect.h: -------------------------------------------------------------------------------- 1 | // 2 | // DCIntrospect.h 3 | // 4 | // Created by Domestic Cat on 29/04/11. 5 | // 6 | 7 | #define kDCIntrospectNotificationIntrospectionDidStart @"kDCIntrospectNotificationIntrospectionDidStart" 8 | #define kDCIntrospectNotificationIntrospectionDidEnd @"kDCIntrospectNotificationIntrospectionDidEnd" 9 | #define kDCIntrospectAnimationDuration 0.08 10 | 11 | #import 12 | #include "TargetConditionals.h" 13 | 14 | #import "DCIntrospectSettings.h" 15 | #import "DCFrameView.h" 16 | #import "DCStatusBarOverlay.h" 17 | 18 | #ifdef DEBUG 19 | 20 | @interface UIView (debug) 21 | 22 | - (NSString *)recursiveDescription; 23 | 24 | @end 25 | 26 | #endif 27 | 28 | @interface DCIntrospect : NSObject 29 | { 30 | } 31 | 32 | @property (nonatomic) BOOL keyboardBindingsOn; // default: YES 33 | @property (nonatomic) BOOL showStatusBarOverlay; // default: YES 34 | @property (nonatomic, retain) UIGestureRecognizer *invokeGestureRecognizer; // default: nil 35 | 36 | @property (nonatomic) BOOL on; 37 | @property (nonatomic) BOOL handleArrowKeys; 38 | @property (nonatomic) BOOL viewOutlines; 39 | @property (nonatomic) BOOL highlightNonOpaqueViews; 40 | @property (nonatomic) BOOL flashOnRedraw; 41 | @property (nonatomic, retain) DCFrameView *frameView; 42 | @property (nonatomic, retain) UITextView *inputTextView; 43 | @property (nonatomic, retain) DCStatusBarOverlay *statusBarOverlay; 44 | 45 | @property (nonatomic, retain) NSMutableDictionary *objectNames; 46 | 47 | @property (nonatomic, assign) UIView *currentView; 48 | @property (nonatomic) CGRect originalFrame; 49 | @property (nonatomic) CGFloat originalAlpha; 50 | @property (nonatomic, retain) NSMutableArray *currentViewHistory; 51 | 52 | @property (nonatomic) BOOL showingHelp; 53 | 54 | /////////// 55 | // Setup // 56 | /////////// 57 | 58 | + (DCIntrospect *)sharedIntrospector; // this returns nil when NOT in DEGBUG mode 59 | - (void)start; // NOTE: call setup AFTER [window makeKeyAndVisible] so statusBarOrientation is reported correctly. 60 | 61 | //////////////////// 62 | // Custom Setters // 63 | //////////////////// 64 | 65 | - (void)setInvokeGestureRecognizer:(UIGestureRecognizer *)newGestureRecognizer; 66 | - (void)setKeyboardBindingsOn:(BOOL)keyboardBindingsOn; 67 | 68 | ////////////////// 69 | // Main Actions // 70 | ////////////////// 71 | 72 | - (void)invokeIntrospector; // can be called manually 73 | - (void)touchAtPoint:(CGPoint)point; // can be called manually 74 | - (void)selectView:(UIView *)view; 75 | - (void)statusBarTapped; 76 | 77 | ////////////////////// 78 | // Keyboard Capture // 79 | ////////////////////// 80 | 81 | - (void)textViewDidChangeSelection:(UITextView *)textView; 82 | - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)string; 83 | 84 | ///////////////////////////////// 85 | // Logging Code & Object Names // 86 | ///////////////////////////////// 87 | 88 | - (void)logCodeForCurrentViewChanges; 89 | 90 | // make sure all names that are added are removed at dealloc or else they will be retained here! 91 | - (void)setName:(NSString *)name forObject:(id)object accessedWithSelf:(BOOL)accessedWithSelf; 92 | - (NSString *)nameForObject:(id)object; 93 | - (void)removeNamesForViewsInView:(UIView *)view; 94 | - (void)removeNameForObject:(id)object; 95 | 96 | //////////// 97 | // Layout // 98 | //////////// 99 | 100 | - (void)updateFrameView; 101 | - (void)updateStatusBar; 102 | - (void)updateViews; 103 | - (void)showTemporaryStringInStatusBar:(NSString *)string; 104 | 105 | ///////////// 106 | // Actions // 107 | ///////////// 108 | 109 | - (void)logRecursiveDescriptionForCurrentView; 110 | - (void)logRecursiveDescriptionForView:(UIView *)view; 111 | - (void)forceSetNeedsDisplay; 112 | - (void)forceSetNeedsLayout; 113 | - (void)forceReloadOfView; 114 | - (void)toggleOutlines; 115 | - (void)addOutlinesToFrameViewFromSubview:(UIView *)view; 116 | - (void)toggleNonOpaqueViews; 117 | - (void)setBackgroundColor:(UIColor *)color ofNonOpaqueViewsInSubview:(UIView *)view; 118 | - (void)toggleRedrawFlashing; 119 | - (void)callDrawRectOnViewsInSubview:(UIView *)subview; 120 | - (void)flashRect:(CGRect)rect inView:(UIView *)view; 121 | 122 | ///////////////////////////// 123 | // (Somewhat) Experimental // 124 | ///////////////////////////// 125 | 126 | - (void)logPropertiesForCurrentView; 127 | - (void)logPropertiesForObject:(id)object; 128 | - (void)logAccessabilityPropertiesForObject:(id)object; 129 | - (NSArray *)subclassesOfClass:(Class)parentClass; 130 | 131 | ///////////////////////// 132 | // Description Methods // 133 | ///////////////////////// 134 | 135 | - (NSString *)describeProperty:(NSString *)propertyName value:(id)value; 136 | - (NSString *)describeColor:(UIColor *)color; 137 | 138 | ///////////////////////// 139 | // DCIntrospector Help // 140 | ///////////////////////// 141 | 142 | - (void)toggleHelp; 143 | - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType; 144 | 145 | //////////////////// 146 | // Helper Methods // 147 | //////////////////// 148 | 149 | - (UIWindow *)mainWindow; 150 | - (NSMutableArray *)viewsAtPoint:(CGPoint)touchPoint inView:(UIView *)view; 151 | - (void)fadeView:(UIView *)view toAlpha:(CGFloat)alpha; 152 | - (BOOL)view:(UIView *)view containsSubview:(UIView *)subview; 153 | - (BOOL)shouldIgnoreView:(UIView *)view; 154 | 155 | @end 156 | -------------------------------------------------------------------------------- /DCIntrospect/DCIntrospect.m: -------------------------------------------------------------------------------- 1 | // 2 | // DCIntrospect.m 3 | // 4 | // Created by Domestic Cat on 29/04/11. 5 | // 6 | 7 | #import "DCIntrospect.h" 8 | #import 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | // break into GDB code complied from following sources: 17 | // http://blog.timac.org/?p=190, http://developer.apple.com/library/mac/#qa/qa1361/_index.html, http://cocoawithlove.com/2008/03/break-into-debugger.html 18 | 19 | // Returns true if the current process is being debugged (either 20 | // running under the debugger or has a debugger attached post facto). 21 | static bool AmIBeingDebugged(void) 22 | { 23 | int junk; 24 | int mib[4]; 25 | struct kinfo_proc info; 26 | size_t size; 27 | 28 | // Initialize the flags so that, if sysctl fails for some bizarre 29 | // reason, we get a predictable result. 30 | 31 | info.kp_proc.p_flag = 0; 32 | 33 | // Initialize mib, which tells sysctl the info we want, in this case 34 | // we're looking for information about a specific process ID. 35 | 36 | mib[0] = CTL_KERN; 37 | mib[1] = KERN_PROC; 38 | mib[2] = KERN_PROC_PID; 39 | mib[3] = getpid(); 40 | 41 | // Call sysctl. 42 | 43 | size = sizeof(info); 44 | junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0); 45 | assert(junk == 0); 46 | 47 | // We're being debugged if the P_TRACED flag is set. 48 | 49 | return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); 50 | } 51 | 52 | #if TARGET_CPU_ARM 53 | #define DEBUGSTOP(signal) __asm__ __volatile__ ("mov r0, %0\nmov r1, %1\nmov r12, %2\nswi 128\n" : : "r"(getpid ()), "r"(signal), "r"(37) : "r12", "r0", "r1", "cc"); 54 | #define DEBUGGER do { int trapSignal = AmIBeingDebugged () ? SIGINT : SIGSTOP; DEBUGSTOP(trapSignal); if (trapSignal == SIGSTOP) { DEBUGSTOP (SIGINT); } } while (false); 55 | #else 56 | #define DEBUGGER do { int trapSignal = AmIBeingDebugged () ? SIGINT : SIGSTOP; __asm__ __volatile__ ("pushl %0\npushl %1\npush $0\nmovl %2, %%eax\nint $0x80\nadd $12, %%esp" : : "g" (trapSignal), "g" (getpid ()), "n" (37) : "eax", "cc"); } while (false); 57 | #endif 58 | 59 | @interface DCIntrospect () 60 | 61 | - (void)takeFirstResponder; 62 | 63 | @end 64 | 65 | 66 | DCIntrospect *sharedInstance = nil; 67 | 68 | @implementation DCIntrospect 69 | @synthesize keyboardBindingsOn, showStatusBarOverlay, invokeGestureRecognizer; 70 | @synthesize on; 71 | @synthesize handleArrowKeys; 72 | @synthesize viewOutlines, highlightNonOpaqueViews, flashOnRedraw; 73 | @synthesize statusBarOverlay; 74 | @synthesize inputTextView; 75 | @synthesize frameView; 76 | @synthesize objectNames; 77 | @synthesize currentView, originalFrame, originalAlpha; 78 | @synthesize currentViewHistory; 79 | @synthesize showingHelp; 80 | 81 | #pragma mark Setup 82 | 83 | + (void)load 84 | { 85 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 86 | 87 | NSString *simulatorRoot = [[[NSProcessInfo processInfo] environment] objectForKey:@"IPHONE_SIMULATOR_ROOT"]; 88 | if (simulatorRoot) 89 | { 90 | void *AppSupport = dlopen([[simulatorRoot stringByAppendingPathComponent:@"/System/Library/PrivateFrameworks/AppSupport.framework/AppSupport"] fileSystemRepresentation], RTLD_LAZY); 91 | CFStringRef (*CPCopySharedResourcesPreferencesDomainForDomain)(CFStringRef domain) = (CFStringRef (*)())dlsym(AppSupport, "CPCopySharedResourcesPreferencesDomainForDomain"); 92 | if (CPCopySharedResourcesPreferencesDomainForDomain) 93 | { 94 | CFStringRef accessibilityDomain = CPCopySharedResourcesPreferencesDomainForDomain(CFSTR("com.apple.Accessibility")); 95 | if (accessibilityDomain) 96 | { 97 | // This must be done *before* UIApplicationMain, hence +load 98 | CFPreferencesSetValue(CFSTR("ApplicationAccessibilityEnabled"), kCFBooleanTrue, accessibilityDomain, kCFPreferencesAnyUser, kCFPreferencesAnyHost); 99 | CFRelease(accessibilityDomain); 100 | } 101 | } 102 | } 103 | 104 | [pool drain]; 105 | } 106 | 107 | static void *originalValueForKeyIMPKey = &originalValueForKeyIMPKey; 108 | 109 | id UITextInputTraits_valueForKey(id self, SEL _cmd, NSString *key); 110 | id UITextInputTraits_valueForKey(id self, SEL _cmd, NSString *key) 111 | { 112 | static NSMutableSet *textInputTraitsProperties = nil; 113 | if (!textInputTraitsProperties) 114 | { 115 | textInputTraitsProperties = [[NSMutableSet alloc] init]; 116 | unsigned int count = 0; 117 | objc_property_t *properties = protocol_copyPropertyList(@protocol(UITextInputTraits), &count); 118 | for (unsigned int i = 0; i < count; i++) 119 | { 120 | objc_property_t property = properties[i]; 121 | NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)]; 122 | [textInputTraitsProperties addObject:propertyName]; 123 | } 124 | free(properties); 125 | } 126 | 127 | IMP valueForKey = (IMP)[objc_getAssociatedObject([self class], originalValueForKeyIMPKey) pointerValue]; 128 | if ([textInputTraitsProperties containsObject:key]) 129 | { 130 | id textInputTraits = valueForKey(self, _cmd, @"textInputTraits"); 131 | return valueForKey(textInputTraits, _cmd, key); 132 | } 133 | else 134 | { 135 | return valueForKey(self, _cmd, key); 136 | } 137 | } 138 | 139 | // See http://stackoverflow.com/questions/6617472/why-does-valueforkey-on-a-uitextfield-throws-an-exception-for-uitextinputtraits 140 | + (void)workaroundUITextInputTraitsPropertiesBug 141 | { 142 | Method valueForKey = class_getInstanceMethod([NSObject class], @selector(valueForKey:)); 143 | const char *valueForKeyTypeEncoding = method_getTypeEncoding(valueForKey); 144 | 145 | unsigned int count = 0; 146 | Class *classes = objc_copyClassList(&count); 147 | for (unsigned int i = 0; i < count; i++) 148 | { 149 | Class class = classes[i]; 150 | if (class_getInstanceMethod(class, NSSelectorFromString(@"textInputTraits"))) 151 | { 152 | IMP originalValueForKey = class_replaceMethod(class, @selector(valueForKey:), (IMP)UITextInputTraits_valueForKey, valueForKeyTypeEncoding); 153 | if (!originalValueForKey) 154 | originalValueForKey = (IMP)[objc_getAssociatedObject([class superclass], originalValueForKeyIMPKey) pointerValue]; 155 | if (!originalValueForKey) 156 | originalValueForKey = class_getMethodImplementation([class superclass], @selector(valueForKey:)); 157 | 158 | objc_setAssociatedObject(class, originalValueForKeyIMPKey, [NSValue valueWithPointer:(void *)originalValueForKey], OBJC_ASSOCIATION_RETAIN_NONATOMIC); 159 | } 160 | } 161 | free(classes); 162 | } 163 | 164 | + (DCIntrospect *)sharedIntrospector 165 | { 166 | #ifdef DEBUG 167 | if (!sharedInstance) 168 | { 169 | sharedInstance = [[DCIntrospect alloc] init]; 170 | sharedInstance.keyboardBindingsOn = YES; 171 | sharedInstance.showStatusBarOverlay = ![UIApplication sharedApplication].statusBarHidden; 172 | [self workaroundUITextInputTraitsPropertiesBug]; 173 | } 174 | #endif 175 | return sharedInstance; 176 | } 177 | 178 | - (void)start 179 | { 180 | UIWindow *mainWindow = [self mainWindow]; 181 | if (!mainWindow) 182 | { 183 | NSLog(@"DCIntrospect: Couldn't setup. No main window?"); 184 | return; 185 | } 186 | 187 | if (!self.statusBarOverlay) 188 | { 189 | self.statusBarOverlay = [[[DCStatusBarOverlay alloc] init] autorelease]; 190 | } 191 | 192 | if (!self.inputTextView) 193 | { 194 | self.inputTextView = [[[UITextView alloc] initWithFrame:CGRectZero] autorelease]; 195 | self.inputTextView.delegate = self; 196 | self.inputTextView.autocorrectionType = UITextAutocorrectionTypeNo; 197 | self.inputTextView.autocapitalizationType = UITextAutocapitalizationTypeNone; 198 | self.inputTextView.inputView = [[[UIView alloc] init] autorelease]; 199 | self.inputTextView.scrollsToTop = NO; 200 | [mainWindow addSubview:self.inputTextView]; 201 | } 202 | 203 | if (self.keyboardBindingsOn) 204 | { 205 | if (![self.inputTextView becomeFirstResponder]) 206 | { 207 | [self performSelector:@selector(takeFirstResponder) withObject:nil afterDelay:0.5]; 208 | } 209 | } 210 | 211 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarTapped) name:kDCIntrospectNotificationStatusBarTapped object:nil]; 212 | 213 | // reclaim the keyboard after dismissal if it is taken 214 | [[NSNotificationCenter defaultCenter] addObserverForName:UIKeyboardWillHideNotification 215 | object:nil 216 | queue:nil 217 | usingBlock:^(NSNotification *notification) { 218 | if (self.keyboardBindingsOn) 219 | { 220 | [self performSelector:@selector(takeFirstResponder) 221 | withObject:nil 222 | afterDelay:[[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue]]; 223 | } 224 | }]; 225 | 226 | // dirty hack for UIWebView keyboard problems 227 | [[NSNotificationCenter defaultCenter] addObserverForName:UIKeyboardWillShowNotification 228 | object:nil 229 | queue:nil 230 | usingBlock:^(NSNotification *notification) { 231 | [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(takeFirstResponder) object:nil]; 232 | }]; 233 | 234 | // listen for device orientation changes to adjust the status bar 235 | [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; 236 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateViews) name:UIDeviceOrientationDidChangeNotification object:nil]; 237 | 238 | if (!self.currentViewHistory) 239 | self.currentViewHistory = [[[NSMutableArray alloc] init] autorelease]; 240 | 241 | NSLog(@"DCIntrospect is setup. %@ to start.", [kDCIntrospectKeysInvoke isEqualToString:@" "] ? @"Push the space bar" : [NSString stringWithFormat:@"Type '%@'", kDCIntrospectKeysInvoke]); 242 | } 243 | 244 | - (void)takeFirstResponder 245 | { 246 | if (![self.inputTextView becomeFirstResponder]) 247 | NSLog(@"DCIntrospect: Couldn't reclaim keyboard input. Is the keyboard used elsewhere?"); 248 | } 249 | 250 | - (void)resetInputTextView 251 | { 252 | self.inputTextView.text = @"\n2 4567 9\n"; 253 | self.handleArrowKeys = NO; 254 | self.inputTextView.selectedRange = NSMakeRange(5, 0); 255 | self.handleArrowKeys = YES; 256 | } 257 | 258 | #pragma mark Custom Setters 259 | - (void)setInvokeGestureRecognizer:(UIGestureRecognizer *)newGestureRecognizer 260 | { 261 | UIWindow *mainWindow = [self mainWindow]; 262 | [mainWindow removeGestureRecognizer:invokeGestureRecognizer]; 263 | 264 | [invokeGestureRecognizer release]; 265 | invokeGestureRecognizer = nil; 266 | invokeGestureRecognizer = [newGestureRecognizer retain]; 267 | [invokeGestureRecognizer addTarget:self action:@selector(invokeIntrospector)]; 268 | [mainWindow addGestureRecognizer:invokeGestureRecognizer]; 269 | } 270 | 271 | - (void)setKeyboardBindingsOn:(BOOL)areKeyboardBindingsOn 272 | { 273 | keyboardBindingsOn = areKeyboardBindingsOn; 274 | if (self.keyboardBindingsOn) 275 | [self.inputTextView becomeFirstResponder]; 276 | else 277 | [self.inputTextView resignFirstResponder]; 278 | } 279 | 280 | #pragma mark Main Actions 281 | 282 | - (void)invokeIntrospector 283 | { 284 | self.on = !self.on; 285 | 286 | if (self.on) 287 | { 288 | [self updateViews]; 289 | [self updateStatusBar]; 290 | [self updateFrameView]; 291 | 292 | if (self.keyboardBindingsOn) 293 | [self.inputTextView becomeFirstResponder]; 294 | else 295 | [self.inputTextView resignFirstResponder]; 296 | 297 | [self resetInputTextView]; 298 | 299 | [[NSNotificationCenter defaultCenter] postNotificationName:kDCIntrospectNotificationIntrospectionDidStart 300 | object:nil]; 301 | } 302 | else 303 | { 304 | if (self.viewOutlines) 305 | [self toggleOutlines]; 306 | if (self.highlightNonOpaqueViews) 307 | [self toggleNonOpaqueViews]; 308 | if (self.showingHelp) 309 | [self toggleHelp]; 310 | 311 | self.statusBarOverlay.hidden = YES; 312 | self.frameView.alpha = 0; 313 | self.currentView = nil; 314 | 315 | [[NSNotificationCenter defaultCenter] postNotificationName:kDCIntrospectNotificationIntrospectionDidEnd 316 | object:nil]; 317 | } 318 | } 319 | 320 | - (void)touchAtPoint:(CGPoint)point 321 | { 322 | // convert the point into the main window 323 | CGPoint convertedTouchPoint = [[self mainWindow] convertPoint:point fromView:self.frameView]; 324 | 325 | // find all the views under that point – will be added in order on screen, ie mainWindow will be index 0, main view controller at index 1 etc. 326 | NSMutableArray *views = [self viewsAtPoint:convertedTouchPoint inView:[self mainWindow]]; 327 | if (views.count == 0) 328 | return; 329 | 330 | // get the topmost view and setup the UI 331 | [self.currentViewHistory removeAllObjects]; 332 | UIView *newView = [views lastObject]; 333 | [self selectView:newView]; 334 | } 335 | 336 | - (void)selectView:(UIView *)view 337 | { 338 | self.currentView = view; 339 | self.originalFrame = self.currentView.frame; 340 | self.originalAlpha = self.currentView.alpha; 341 | 342 | if (self.frameView.rectsToOutline.count > 0) 343 | { 344 | [self.frameView.rectsToOutline removeAllObjects]; 345 | [self.frameView setNeedsDisplay]; 346 | self.viewOutlines = NO; 347 | } 348 | 349 | [self updateFrameView]; 350 | [self updateStatusBar]; 351 | 352 | if (![self.currentViewHistory containsObject:self.currentView]) 353 | [self.currentViewHistory addObject:self.currentView]; 354 | } 355 | 356 | - (void)statusBarTapped 357 | { 358 | if (self.showingHelp) 359 | { 360 | [self toggleHelp]; 361 | return; 362 | } 363 | } 364 | 365 | #pragma mark Keyboard Capture 366 | 367 | - (void)textViewDidChangeSelection:(UITextView *)textView 368 | { 369 | if (!(self.on && self.handleArrowKeys)) 370 | return; 371 | 372 | NSUInteger selectionLocation = textView.selectedRange.location; 373 | NSUInteger selectionLength = textView.selectedRange.length; 374 | BOOL shiftKey = selectionLength != 0; 375 | BOOL optionKey = selectionLocation % 2 == 1; 376 | 377 | CGRect frame = self.currentView.frame; 378 | if (shiftKey) 379 | { 380 | if (selectionLocation == 4 && selectionLength == 1) 381 | frame.origin.x -= 10.0f; 382 | else if (selectionLocation == 5 && selectionLength == 1) 383 | frame.origin.x += 10.0f; 384 | else if (selectionLocation == 0 && selectionLength == 5) 385 | frame.origin.y -= 10.0f; 386 | else if (selectionLocation == 5 && selectionLength == 5) 387 | frame.origin.y += 10.0f; 388 | } 389 | else if (optionKey) 390 | { 391 | if (selectionLocation == 7) 392 | frame.size.width += 1.0f; 393 | else if (selectionLocation == 3) 394 | frame.size.width -= 1.0f; 395 | else if (selectionLocation == 9) 396 | frame.size.height += 1.0f; 397 | else if (selectionLocation == 1) 398 | frame.size.height -= 1.0f; 399 | } 400 | else 401 | { 402 | if (selectionLocation == 4) 403 | frame.origin.x -= 1.0f; 404 | else if (selectionLocation == 6) 405 | frame.origin.x += 1.0f; 406 | else if (selectionLocation == 0) 407 | frame.origin.y -= 1.0f; 408 | else if (selectionLocation == 10) 409 | frame.origin.y += 1.0f; 410 | } 411 | 412 | self.currentView.frame = CGRectMake(floorf(frame.origin.x), 413 | floorf(frame.origin.y), 414 | floorf(frame.size.width), 415 | floorf(frame.size.height)); 416 | 417 | [self updateFrameView]; 418 | [self updateStatusBar]; 419 | 420 | [self resetInputTextView]; 421 | } 422 | 423 | - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)string 424 | { 425 | if ([string isEqualToString:kDCIntrospectKeysDisableForPeriod]) 426 | { 427 | [self setKeyboardBindingsOn:NO]; 428 | [[self inputTextView] resignFirstResponder]; 429 | NSLog(@"DCIntrospect: Disabled for %.1f seconds", kDCIntrospectTemporaryDisableDuration); 430 | [self performSelector:@selector(setKeyboardBindingsOn:) withObject:[NSNumber numberWithFloat:YES] afterDelay:kDCIntrospectTemporaryDisableDuration]; 431 | return NO; 432 | } 433 | 434 | if ([string isEqualToString:kDCIntrospectKeysInvoke]) 435 | { 436 | [self invokeIntrospector]; 437 | return NO; 438 | } 439 | 440 | if (!self.on) 441 | return NO; 442 | 443 | if (self.showingHelp) 444 | { 445 | [self toggleHelp]; 446 | return NO; 447 | } 448 | 449 | if ([string isEqualToString:kDCIntrospectKeysToggleViewOutlines]) 450 | { 451 | [self toggleOutlines]; 452 | return NO; 453 | } 454 | else if ([string isEqualToString:kDCIntrospectKeysToggleNonOpaqueViews]) 455 | { 456 | [self toggleNonOpaqueViews]; 457 | return NO; 458 | } 459 | else if ([string isEqualToString:kDCIntrospectKeysToggleFlashViewRedraws]) 460 | { 461 | [self toggleRedrawFlashing]; 462 | return NO; 463 | } 464 | else if ([string isEqualToString:kDCIntrospectKeysToggleShowCoordinates]) 465 | { 466 | [UIView animateWithDuration:0.15 467 | delay:0 468 | options:UIViewAnimationOptionAllowUserInteraction 469 | animations:^{ 470 | self.frameView.touchPointLabel.alpha = !self.frameView.touchPointLabel.alpha; 471 | } completion:^(BOOL finished) { 472 | NSString *coordinatesString = [NSString stringWithFormat:@"Coordinates are %@", (self.frameView.touchPointLabel.alpha) ? @"on" : @"off"]; 473 | if (self.showStatusBarOverlay) 474 | [self showTemporaryStringInStatusBar:coordinatesString]; 475 | else 476 | NSLog(@"DCIntrospect: %@", coordinatesString); 477 | }]; 478 | return NO; 479 | } 480 | else if ([string isEqualToString:kDCIntrospectKeysToggleHelp]) 481 | { 482 | [self toggleHelp]; 483 | return NO; 484 | } 485 | 486 | if (self.on && self.currentView) 487 | { 488 | if ([string isEqualToString:kDCIntrospectKeysLogProperties]) 489 | { 490 | [self logPropertiesForObject:self.currentView]; 491 | return NO; 492 | } 493 | else if ([string isEqualToString:kDCIntrospectKeysLogAccessibilityProperties]) 494 | { 495 | [self logAccessabilityPropertiesForObject:self.currentView]; 496 | return NO; 497 | } 498 | else if ([string isEqualToString:kDCIntrospectKeysLogViewRecursive]) 499 | { 500 | [self logRecursiveDescriptionForView:self.currentView]; 501 | return NO; 502 | } 503 | else if ([string isEqualToString:kDCIntrospectKeysSetNeedsDisplay]) 504 | { 505 | [self forceSetNeedsDisplay]; 506 | return NO; 507 | } 508 | else if ([string isEqualToString:kDCIntrospectKeysSetNeedsLayout]) 509 | { 510 | [self forceSetNeedsLayout]; 511 | return NO; 512 | } 513 | else if ([string isEqualToString:kDCIntrospectKeysReloadData]) 514 | { 515 | [self forceReloadOfView]; 516 | return NO; 517 | } 518 | else if ([string isEqualToString:kDCIntrospectKeysMoveUpInViewHierarchy]) 519 | { 520 | if (self.currentView.superview) 521 | { 522 | [self selectView:self.currentView.superview]; 523 | } 524 | else 525 | { 526 | NSLog(@"DCIntrospect: At top of view hierarchy."); 527 | return NO; 528 | } 529 | return NO; 530 | } 531 | else if ([string isEqualToString:kDCIntrospectKeysMoveBackInViewHierarchy]) 532 | { 533 | if (self.currentViewHistory.count == 0) 534 | return NO; 535 | 536 | int indexOfCurrentView = [self.currentViewHistory indexOfObject:self.currentView]; 537 | if (indexOfCurrentView == 0) 538 | { 539 | NSLog(@"DCIntrospect: At bottom of view history."); 540 | return NO; 541 | } 542 | 543 | [self selectView:[self.currentViewHistory objectAtIndex:indexOfCurrentView - 1]]; 544 | } 545 | else if ([string isEqualToString:kDCIntrospectKeysMoveDownToFirstSubview]) 546 | { 547 | if (self.currentView.subviews.count>0) { 548 | [self selectView:[self.currentView.subviews objectAtIndex:0]]; 549 | }else{ 550 | NSLog(@"DCIntrospect: No subviews."); 551 | return NO; 552 | } 553 | return NO; 554 | } 555 | else if ([string isEqualToString:kDCIntrospectKeysMoveToNextSiblingView]) 556 | { 557 | NSUInteger currentViewsIndex = [self.currentView.superview.subviews indexOfObject:self.currentView]; 558 | 559 | if (currentViewsIndex==NSNotFound) { 560 | NSLog(@"DCIntrospect: BROKEN HIERARCHY."); 561 | } else if (self.currentView.superview.subviews.count>currentViewsIndex + 1) { 562 | [self selectView:[self.currentView.superview.subviews objectAtIndex:currentViewsIndex + 1]]; 563 | }else{ 564 | NSLog(@"DCIntrospect: No next sibling views."); 565 | return NO; 566 | } 567 | return NO; 568 | } 569 | else if ([string isEqualToString:kDCIntrospectKeysMoveToPrevSiblingView]) 570 | { 571 | NSUInteger currentViewsIndex = [self.currentView.superview.subviews indexOfObject:self.currentView]; 572 | if (currentViewsIndex==NSNotFound) { 573 | NSLog(@"DCIntrospect: BROKEN HIERARCHY."); 574 | } else if (currentViewsIndex!=0) { 575 | [self selectView:[self.currentView.superview.subviews objectAtIndex:currentViewsIndex - 1]]; 576 | } else { 577 | NSLog(@"DCIntrospect: No previous sibling views."); 578 | } 579 | return NO; 580 | } 581 | else if ([string isEqualToString:kDCIntrospectKeysLogCodeForCurrentViewChanges]) 582 | { 583 | [self logCodeForCurrentViewChanges]; 584 | return NO; 585 | } 586 | 587 | CGRect frame = self.currentView.frame; 588 | if ([string isEqualToString:kDCIntrospectKeysNudgeViewLeft]) 589 | frame.origin.x -= 1.0f; 590 | else if ([string isEqualToString:kDCIntrospectKeysNudgeViewRight]) 591 | frame.origin.x += 1.0f; 592 | else if ([string isEqualToString:kDCIntrospectKeysNudgeViewUp]) 593 | frame.origin.y -= 1.0f; 594 | else if ([string isEqualToString:kDCIntrospectKeysNudgeViewDown]) 595 | frame.origin.y += 1.0f; 596 | else if ([string isEqualToString:kDCIntrospectKeysCenterInSuperview]) 597 | frame = CGRectMake(floorf((self.currentView.superview.frame.size.width - frame.size.width) / 2.0f), 598 | floorf((self.currentView.superview.frame.size.height - frame.size.height) / 2.0f), 599 | frame.size.width, 600 | frame.size.height); 601 | else if ([string isEqualToString:kDCIntrospectKeysIncreaseWidth]) 602 | frame.size.width += 1.0f; 603 | else if ([string isEqualToString:kDCIntrospectKeysDecreaseWidth]) 604 | frame.size.width -= 1.0f; 605 | else if ([string isEqualToString:kDCIntrospectKeysIncreaseHeight]) 606 | frame.size.height += 1.0f; 607 | else if ([string isEqualToString:kDCIntrospectKeysDecreaseHeight]) 608 | frame.size.height -= 1.0f; 609 | else if ([string isEqualToString:kDCIntrospectKeysIncreaseViewAlpha]) 610 | { 611 | if (self.currentView.alpha < 1.0f) 612 | self.currentView.alpha += 0.05f; 613 | } 614 | else if ([string isEqualToString:kDCIntrospectKeysDecreaseViewAlpha]) 615 | { 616 | if (self.currentView.alpha > 0.0f) 617 | self.currentView.alpha -= 0.05f; 618 | } 619 | else if ([string isEqualToString:kDCIntrospectKeysEnterGDB]) 620 | { 621 | UIView *view = self.currentView; 622 | view.tag = view.tag; // suppress the xcode warning about an unused variable. 623 | NSLog(@"DCIntrospect: access current view using local 'view' variable."); 624 | DEBUGGER; 625 | return NO; 626 | } 627 | 628 | self.currentView.frame = CGRectMake(floorf(frame.origin.x), 629 | floorf(frame.origin.y), 630 | floorf(frame.size.width), 631 | floorf(frame.size.height)); 632 | 633 | [self updateFrameView]; 634 | [self updateStatusBar]; 635 | } 636 | 637 | return NO; 638 | } 639 | 640 | #pragma mark Object Names 641 | 642 | - (void)logCodeForCurrentViewChanges 643 | { 644 | if (!self.currentView) 645 | return; 646 | 647 | NSString *varName = [self nameForObject:self.currentView]; 648 | if ([varName isEqualToString:[NSString stringWithFormat:@"%@", self.currentView.class]]) 649 | varName = @"<#view#>"; 650 | 651 | NSMutableString *outputString = [NSMutableString string]; 652 | if (!CGRectEqualToRect(self.originalFrame, self.currentView.frame)) 653 | { 654 | [outputString appendFormat:@"%@.frame = CGRectMake(%.1f, %.1f, %.1f, %.1f);\n", varName, self.currentView.frame.origin.x, self.currentView.frame.origin.y, self.currentView.frame.size.width, self.currentView.frame.size.height]; 655 | } 656 | 657 | if (self.originalAlpha != self.currentView.alpha) 658 | { 659 | [outputString appendFormat:@"%@.alpha = %.2f;\n", varName, self.currentView.alpha]; 660 | } 661 | 662 | if (outputString.length == 0) 663 | NSLog(@"DCIntrospect: No changes made to %@.", self.currentView.class); 664 | else 665 | printf("\n\n%s\n", [outputString UTF8String]); 666 | } 667 | 668 | - (void)setName:(NSString *)name forObject:(id)object accessedWithSelf:(BOOL)accessedWithSelf 669 | { 670 | if (!self.objectNames) 671 | self.objectNames = [NSMutableDictionary dictionary]; 672 | 673 | if (accessedWithSelf) 674 | name = [@"self." stringByAppendingString:name]; 675 | 676 | [self.objectNames setValue:object forKey:name]; 677 | } 678 | 679 | - (NSString *)nameForObject:(id)object 680 | { 681 | __block NSString *objectName = [NSString stringWithFormat:@"%@", [object class]]; 682 | if (!self.objectNames) 683 | return objectName; 684 | 685 | [self.objectNames enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { 686 | if (obj == object) 687 | { 688 | objectName = (NSString *)key; 689 | *stop = YES; 690 | } 691 | }]; 692 | 693 | return objectName; 694 | } 695 | 696 | - (void)removeNamesForViewsInView:(UIView *)view 697 | { 698 | if (!self.objectNames) 699 | return; 700 | 701 | NSMutableArray *objectsToRemove = [NSMutableArray array]; 702 | [self.objectNames enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { 703 | if ([[obj class] isSubclassOfClass:[UIView class]]) 704 | { 705 | UIView *subview = (UIView *)obj; 706 | if ([self view:view containsSubview:subview]) 707 | [objectsToRemove addObject:key]; 708 | } 709 | }]; 710 | 711 | [objectsToRemove enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 712 | NSString *key = (NSString *)obj; 713 | [self.objectNames removeObjectForKey:key]; 714 | }]; 715 | } 716 | 717 | - (void)removeNameForObject:(id)object 718 | { 719 | if (!self.objectNames) 720 | return; 721 | 722 | NSString *objectName = [self nameForObject:object]; 723 | [self.objectNames removeObjectForKey:objectName]; 724 | } 725 | 726 | #pragma mark Layout 727 | 728 | - (void)updateFrameView 729 | { 730 | UIWindow *mainWindow = [self mainWindow]; 731 | if (!self.frameView) 732 | { 733 | self.frameView = [[[DCFrameView alloc] initWithFrame:(CGRect){ CGPointZero, mainWindow.frame.size } delegate:self] autorelease]; 734 | [mainWindow addSubview:self.frameView]; 735 | self.frameView.alpha = 0.0f; 736 | [self updateViews]; 737 | } 738 | 739 | [mainWindow bringSubviewToFront:self.frameView]; 740 | 741 | if (self.on) 742 | { 743 | if (self.currentView) 744 | { 745 | self.frameView.mainRect = [self.currentView.superview convertRect:self.currentView.frame toView:self.frameView]; 746 | if (self.currentView.superview == mainWindow) 747 | self.frameView.superRect = CGRectZero; 748 | else if (self.currentView.superview.superview) 749 | self.frameView.superRect = [self.currentView.superview.superview convertRect:self.currentView.superview.frame toView:self.frameView]; 750 | else 751 | self.frameView.superRect = CGRectZero; 752 | } 753 | else 754 | { 755 | self.frameView.mainRect = CGRectZero; 756 | } 757 | 758 | [self fadeView:self.frameView toAlpha:1.0f]; 759 | } 760 | else 761 | { 762 | [self fadeView:self.frameView toAlpha:0.0f]; 763 | } 764 | } 765 | 766 | - (void)updateStatusBar 767 | { 768 | if (self.currentView) 769 | { 770 | NSString *nameForObject = [self nameForObject:self.currentView]; 771 | 772 | // remove the 'self.' if it's there to save space 773 | if ([nameForObject hasPrefix:@"self."]) 774 | nameForObject = [nameForObject substringFromIndex:@"self.".length]; 775 | 776 | if (self.currentView.tag != 0) 777 | self.statusBarOverlay.leftLabel.text = [NSString stringWithFormat:@"%@ (tag: %i)", nameForObject, self.currentView.tag]; 778 | else 779 | self.statusBarOverlay.leftLabel.text = [NSString stringWithFormat:@"%@", nameForObject]; 780 | 781 | self.statusBarOverlay.rightLabel.text = NSStringFromCGRect(self.currentView.frame); 782 | } 783 | else 784 | { 785 | self.statusBarOverlay.leftLabel.text = @"DCIntrospect"; 786 | self.statusBarOverlay.rightLabel.text = [NSString stringWithFormat:@"'%@' for help", kDCIntrospectKeysToggleHelp]; 787 | } 788 | 789 | if (self.showStatusBarOverlay) 790 | self.statusBarOverlay.hidden = NO; 791 | else 792 | self.statusBarOverlay.hidden = YES; 793 | } 794 | 795 | - (void)updateViews 796 | { 797 | // current interface orientation 798 | UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; 799 | CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width; 800 | CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height; 801 | 802 | CGFloat pi = (CGFloat)M_PI; 803 | if (orientation == UIDeviceOrientationPortrait) 804 | { 805 | self.frameView.transform = CGAffineTransformIdentity; 806 | self.frameView.frame = CGRectMake(0, 0, screenWidth, screenHeight); 807 | } 808 | else if (orientation == UIDeviceOrientationLandscapeLeft) 809 | { 810 | self.frameView.transform = CGAffineTransformMakeRotation(pi * (90) / 180.0f); 811 | self.frameView.frame = CGRectMake(screenWidth - screenHeight, 0, screenHeight, screenHeight); 812 | } 813 | else if (orientation == UIDeviceOrientationLandscapeRight) 814 | { 815 | self.frameView.transform = CGAffineTransformMakeRotation(pi * (-90) / 180.0f); 816 | self.frameView.frame = CGRectMake(0, 0, screenWidth, screenHeight); 817 | } 818 | else if (orientation == UIDeviceOrientationPortraitUpsideDown) 819 | { 820 | self.frameView.transform = CGAffineTransformMakeRotation(pi); 821 | self.frameView.frame = CGRectMake(0, 0, screenWidth, screenHeight); 822 | } 823 | 824 | self.currentView = nil; 825 | [self updateFrameView]; 826 | } 827 | 828 | - (void)showTemporaryStringInStatusBar:(NSString *)string 829 | { 830 | [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(updateStatusBar) object:nil]; 831 | 832 | self.statusBarOverlay.leftLabel.text = string; 833 | self.statusBarOverlay.rightLabel.text = nil; 834 | [self performSelector:@selector(updateStatusBar) withObject:nil afterDelay:0.75]; 835 | } 836 | 837 | #pragma mark Actions 838 | 839 | - (void)logRecursiveDescriptionForCurrentView 840 | { 841 | [self logRecursiveDescriptionForView:self.currentView]; 842 | } 843 | 844 | - (void)logRecursiveDescriptionForView:(UIView *)view 845 | { 846 | #ifdef DEBUG 847 | // [UIView recursiveDescription] is a private method. This should probably be re-written to avoid any potential problems. 848 | NSLog(@"DCIntrospect: %@", [view recursiveDescription]); 849 | #endif 850 | } 851 | 852 | - (void)forceSetNeedsDisplay 853 | { 854 | [self.currentView setNeedsDisplay]; 855 | } 856 | 857 | - (void)forceSetNeedsLayout 858 | { 859 | [self.currentView setNeedsLayout]; 860 | } 861 | 862 | - (void)forceReloadOfView 863 | { 864 | if ([self.currentView class] == [UITableView class]) 865 | [(UITableView *)self.currentView reloadData]; 866 | } 867 | 868 | - (void)toggleOutlines 869 | { 870 | UIWindow *mainWindow = [self mainWindow]; 871 | self.viewOutlines = !self.viewOutlines; 872 | 873 | if (self.viewOutlines) 874 | [self addOutlinesToFrameViewFromSubview:mainWindow]; 875 | else 876 | [self.frameView.rectsToOutline removeAllObjects]; 877 | 878 | [self.frameView setNeedsDisplay]; 879 | 880 | NSString *string = [NSString stringWithFormat:@"Showing view outlines is %@", (self.viewOutlines) ? @"on" : @"off"]; 881 | if (self.showStatusBarOverlay) 882 | [self showTemporaryStringInStatusBar:string]; 883 | else 884 | NSLog(@"DCIntrospect: %@", string); 885 | } 886 | 887 | - (void)addOutlinesToFrameViewFromSubview:(UIView *)view 888 | { 889 | for (UIView *subview in view.subviews) 890 | { 891 | if ([self shouldIgnoreView:subview]) 892 | continue; 893 | 894 | CGRect rect = [subview.superview convertRect:subview.frame toView:frameView]; 895 | 896 | NSValue *rectValue = [NSValue valueWithCGRect:rect]; 897 | [self.frameView.rectsToOutline addObject:rectValue]; 898 | [self addOutlinesToFrameViewFromSubview:subview]; 899 | } 900 | } 901 | 902 | - (void)toggleNonOpaqueViews 903 | { 904 | self.highlightNonOpaqueViews = !self.highlightNonOpaqueViews; 905 | 906 | UIWindow *mainWindow = [self mainWindow]; 907 | [self setBackgroundColor:(self.highlightNonOpaqueViews) ? kDCIntrospectOpaqueColor : [UIColor clearColor] 908 | ofNonOpaqueViewsInSubview:mainWindow]; 909 | 910 | NSString *string = [NSString stringWithFormat:@"Highlighting non-opaque views is %@", (self.highlightNonOpaqueViews) ? @"on" : @"off"]; 911 | if (self.showStatusBarOverlay) 912 | [self showTemporaryStringInStatusBar:string]; 913 | else 914 | NSLog(@"DCIntrospect: %@", string); 915 | } 916 | 917 | - (void)setBackgroundColor:(UIColor *)color ofNonOpaqueViewsInSubview:(UIView *)view 918 | { 919 | for (UIView *subview in view.subviews) 920 | { 921 | if ([self shouldIgnoreView:subview]) 922 | continue; 923 | 924 | if (!subview.opaque) 925 | subview.backgroundColor = color; 926 | 927 | [self setBackgroundColor:color ofNonOpaqueViewsInSubview:subview]; 928 | } 929 | } 930 | 931 | - (void)toggleRedrawFlashing 932 | { 933 | self.flashOnRedraw = !self.flashOnRedraw; 934 | NSString *string = [NSString stringWithFormat:@"Flashing on redraw is %@", (self.flashOnRedraw) ? @"on" : @"off"]; 935 | if (self.showStatusBarOverlay) 936 | [self showTemporaryStringInStatusBar:string]; 937 | else 938 | NSLog(@"DCIntrospect: %@", string); 939 | 940 | // flash all views to show what is working 941 | [self callDrawRectOnViewsInSubview:[self mainWindow]]; 942 | } 943 | 944 | - (void)callDrawRectOnViewsInSubview:(UIView *)subview 945 | { 946 | for (UIView *view in subview.subviews) 947 | { 948 | if (![self shouldIgnoreView:view]) 949 | { 950 | [view setNeedsDisplay]; 951 | [self callDrawRectOnViewsInSubview:view]; 952 | } 953 | } 954 | } 955 | 956 | - (void)flashRect:(CGRect)rect inView:(UIView *)view 957 | { 958 | if (self.flashOnRedraw) 959 | { 960 | CALayer *layer = [CALayer layer]; 961 | layer.frame = rect; 962 | layer.backgroundColor = kDCIntrospectFlashOnRedrawColor.CGColor; 963 | [view.layer addSublayer:layer]; 964 | [layer performSelector:@selector(removeFromSuperlayer) withObject:nil afterDelay:kDCIntrospectFlashOnRedrawFlashLength]; 965 | } 966 | } 967 | 968 | #pragma mark Description Methods 969 | 970 | - (NSString *)describeProperty:(NSString *)propertyName value:(id)value 971 | { 972 | if ([propertyName isEqualToString:@"contentMode"]) 973 | { 974 | switch ([value intValue]) 975 | { 976 | case 0: return @"UIViewContentModeScaleToFill"; 977 | case 1: return @"UIViewContentModeScaleAspectFit"; 978 | case 2: return @"UIViewContentModeScaleAspectFill"; 979 | case 3: return @"UIViewContentModeRedraw"; 980 | case 4: return @"UIViewContentModeCenter"; 981 | case 5: return @"UIViewContentModeTop"; 982 | case 6: return @"UIViewContentModeBottom"; 983 | case 7: return @"UIViewContentModeLeft"; 984 | case 8: return @"UIViewContentModeRight"; 985 | case 9: return @"UIViewContentModeTopLeft"; 986 | case 10: return @"UIViewContentModeTopRight"; 987 | case 11: return @"UIViewContentModeBottomLeft"; 988 | case 12: return @"UIViewContentModeBottomRight"; 989 | default: return nil; 990 | } 991 | } 992 | else if ([propertyName isEqualToString:@"textAlignment"]) 993 | { 994 | switch ([value intValue]) 995 | { 996 | case 0: return @"UITextAlignmentLeft"; 997 | case 1: return @"UITextAlignmentCenter"; 998 | case 2: return @"UITextAlignmentRight"; 999 | default: return nil; 1000 | } 1001 | } 1002 | else if ([propertyName isEqualToString:@"lineBreakMode"]) 1003 | { 1004 | switch ([value intValue]) 1005 | { 1006 | case 0: return @"UILineBreakModeWordWrap"; 1007 | case 1: return @"UILineBreakModeCharacterWrap"; 1008 | case 2: return @"UILineBreakModeClip"; 1009 | case 3: return @"UILineBreakModeHeadTruncation"; 1010 | case 4: return @"UILineBreakModeTailTruncation"; 1011 | case 5: return @"UILineBreakModeMiddleTruncation"; 1012 | default: return nil; 1013 | } 1014 | } 1015 | else if ([propertyName isEqualToString:@"activityIndicatorViewStyle"]) 1016 | { 1017 | switch ([value intValue]) 1018 | { 1019 | case 0: return @"UIActivityIndicatorViewStyleWhiteLarge"; 1020 | case 1: return @"UIActivityIndicatorViewStyleWhite"; 1021 | case 2: return @"UIActivityIndicatorViewStyleGray"; 1022 | default: return nil; 1023 | } 1024 | } 1025 | else if ([propertyName isEqualToString:@"returnKeyType"]) 1026 | { 1027 | switch ([value intValue]) 1028 | { 1029 | case 0: return @"UIReturnKeyDefault"; 1030 | case 1: return @"UIReturnKeyGo"; 1031 | case 2: return @"UIReturnKeyGoogle"; 1032 | case 3: return @"UIReturnKeyJoin"; 1033 | case 4: return @"UIReturnKeyNext"; 1034 | case 5: return @"UIReturnKeyRoute"; 1035 | case 6: return @"UIReturnKeySearch"; 1036 | case 7: return @"UIReturnKeySend"; 1037 | case 8: return @"UIReturnKeyYahoo"; 1038 | case 9: return @"UIReturnKeyDone"; 1039 | case 10: return @"UIReturnKeyEmergencyCall"; 1040 | default: return nil; 1041 | } 1042 | } 1043 | else if ([propertyName isEqualToString:@"keyboardAppearance"]) 1044 | { 1045 | switch ([value intValue]) 1046 | { 1047 | case 0: return @"UIKeyboardAppearanceDefault"; 1048 | case 1: return @"UIKeyboardAppearanceAlert"; 1049 | default: return nil; 1050 | } 1051 | } 1052 | else if ([propertyName isEqualToString:@"keyboardType"]) 1053 | { 1054 | switch ([value intValue]) 1055 | { 1056 | case 0: return @"UIKeyboardTypeDefault"; 1057 | case 1: return @"UIKeyboardTypeASCIICapable"; 1058 | case 2: return @"UIKeyboardTypeNumbersAndPunctuation"; 1059 | case 3: return @"UIKeyboardTypeURL"; 1060 | case 4: return @"UIKeyboardTypeNumberPad"; 1061 | case 5: return @"UIKeyboardTypePhonePad"; 1062 | case 6: return @"UIKeyboardTypeNamePhonePad"; 1063 | case 7: return @"UIKeyboardTypeEmailAddress"; 1064 | case 8: return @"UIKeyboardTypeDecimalPad"; 1065 | default: return nil; 1066 | } 1067 | } 1068 | else if ([propertyName isEqualToString:@"autocorrectionType"]) 1069 | { 1070 | switch ([value intValue]) 1071 | { 1072 | case 0: return @"UIKeyboardTypeDefault"; 1073 | case 1: return @"UITextAutocorrectionTypeDefault"; 1074 | case 2: return @"UITextAutocorrectionTypeNo"; 1075 | default: return nil; 1076 | } 1077 | } 1078 | else if ([propertyName isEqualToString:@"autocapitalizationType"]) 1079 | { 1080 | switch ([value intValue]) 1081 | { 1082 | case 0: return @"UITextAutocapitalizationTypeNone"; 1083 | case 1: return @"UITextAutocapitalizationTypeWords"; 1084 | case 2: return @"UITextAutocapitalizationTypeSentences"; 1085 | case 3: return @"UITextAutocapitalizationTypeAllCharacters"; 1086 | default: return nil; 1087 | } 1088 | } 1089 | else if ([propertyName isEqualToString:@"clearButtonMode"] || 1090 | [propertyName isEqualToString:@"leftViewMode"] || 1091 | [propertyName isEqualToString:@"rightViewMode"]) 1092 | { 1093 | switch ([value intValue]) 1094 | { 1095 | case 0: return @"UITextFieldViewModeNever"; 1096 | case 1: return @"UITextFieldViewModeWhileEditing"; 1097 | case 2: return @"UITextFieldViewModeUnlessEditing"; 1098 | case 3: return @"UITextFieldViewModeAlways"; 1099 | default: return nil; 1100 | } 1101 | } 1102 | else if ([propertyName isEqualToString:@"borderStyle"]) 1103 | { 1104 | switch ([value intValue]) 1105 | { 1106 | case 0: return @"UITextBorderStyleNone"; 1107 | case 1: return @"UITextBorderStyleLine"; 1108 | case 2: return @"UITextBorderStyleBezel"; 1109 | case 3: return @"UITextBorderStyleRoundedRect"; 1110 | default: return nil; 1111 | } 1112 | } 1113 | else if ([propertyName isEqualToString:@"progressViewStyle"]) 1114 | { 1115 | switch ([value intValue]) 1116 | { 1117 | case 0: return @"UIProgressViewStyleBar"; 1118 | case 1: return @"UIProgressViewStyleDefault"; 1119 | default: return nil; 1120 | } 1121 | } 1122 | else if ([propertyName isEqualToString:@"separatorStyle"]) 1123 | { 1124 | switch ([value intValue]) 1125 | { 1126 | case 0: return @"UITableViewCellSeparatorStyleNone"; 1127 | case 1: return @"UITableViewCellSeparatorStyleSingleLine"; 1128 | case 2: return @"UITableViewCellSeparatorStyleSingleLineEtched"; 1129 | default: return nil; 1130 | } 1131 | } 1132 | else if ([propertyName isEqualToString:@"selectionStyle"]) 1133 | { 1134 | switch ([value intValue]) 1135 | { 1136 | case 0: return @"UITableViewCellSelectionStyleNone"; 1137 | case 1: return @"UITableViewCellSelectionStyleBlue"; 1138 | case 2: return @"UITableViewCellSelectionStyleGray"; 1139 | default: return nil; 1140 | } 1141 | } 1142 | else if ([propertyName isEqualToString:@"editingStyle"]) 1143 | { 1144 | switch ([value intValue]) 1145 | { 1146 | case 0: return @"UITableViewCellEditingStyleNone"; 1147 | case 1: return @"UITableViewCellEditingStyleDelete"; 1148 | case 2: return @"UITableViewCellEditingStyleInsert"; 1149 | default: return nil; 1150 | } 1151 | } 1152 | else if ([propertyName isEqualToString:@"accessoryType"] || [propertyName isEqualToString:@"editingAccessoryType"]) 1153 | { 1154 | switch ([value intValue]) 1155 | { 1156 | case 0: return @"UITableViewCellAccessoryNone"; 1157 | case 1: return @"UITableViewCellAccessoryDisclosureIndicator"; 1158 | case 2: return @"UITableViewCellAccessoryDetailDisclosureButton"; 1159 | case 3: return @"UITableViewCellAccessoryCheckmark"; 1160 | default: return nil; 1161 | } 1162 | } 1163 | else if ([propertyName isEqualToString:@"style"]) 1164 | { 1165 | switch ([value intValue]) 1166 | { 1167 | case 0: return @"UITableViewStylePlain"; 1168 | case 1: return @"UITableViewStyleGrouped"; 1169 | default: return nil; 1170 | } 1171 | 1172 | } 1173 | else if ([propertyName isEqualToString:@"autoresizingMask"]) 1174 | { 1175 | UIViewAutoresizing mask = [value intValue]; 1176 | NSMutableString *string = [NSMutableString string]; 1177 | if (mask & UIViewAutoresizingFlexibleLeftMargin) 1178 | [string appendString:@"UIViewAutoresizingFlexibleLeftMargin"]; 1179 | if (mask & UIViewAutoresizingFlexibleRightMargin) 1180 | [string appendString:@" | UIViewAutoresizingFlexibleRightMargin"]; 1181 | if (mask & UIViewAutoresizingFlexibleTopMargin) 1182 | [string appendString:@" | UIViewAutoresizingFlexibleTopMargin"]; 1183 | if (mask & UIViewAutoresizingFlexibleBottomMargin) 1184 | [string appendString:@" | UIViewAutoresizingFlexibleBottomMargin"]; 1185 | if (mask & UIViewAutoresizingFlexibleWidth) 1186 | [string appendString:@" | UIViewAutoresizingFlexibleWidthMargin"]; 1187 | if (mask & UIViewAutoresizingFlexibleHeight) 1188 | [string appendString:@" | UIViewAutoresizingFlexibleHeightMargin"]; 1189 | 1190 | if ([string hasPrefix:@" | "]) 1191 | [string replaceCharactersInRange:NSMakeRange(0, 3) withString:@""]; 1192 | 1193 | return ([string length] > 0) ? string : @"UIViewAutoresizingNone"; 1194 | } 1195 | else if ([propertyName isEqualToString:@"accessibilityTraits"]) 1196 | { 1197 | UIAccessibilityTraits traits = [value intValue]; 1198 | NSMutableString *string = [NSMutableString string]; 1199 | if (traits & UIAccessibilityTraitButton) 1200 | [string appendString:@"UIAccessibilityTraitButton"]; 1201 | if (traits & UIAccessibilityTraitLink) 1202 | [string appendString:@" | UIAccessibilityTraitLink"]; 1203 | if (traits & UIAccessibilityTraitSearchField) 1204 | [string appendString:@" | UIAccessibilityTraitSearchField"]; 1205 | if (traits & UIAccessibilityTraitImage) 1206 | [string appendString:@" | UIAccessibilityTraitImage"]; 1207 | if (traits & UIAccessibilityTraitSelected) 1208 | [string appendString:@" | UIAccessibilityTraitSelected"]; 1209 | if (traits & UIAccessibilityTraitPlaysSound) 1210 | [string appendString:@" | UIAccessibilityTraitPlaysSound"]; 1211 | if (traits & UIAccessibilityTraitKeyboardKey) 1212 | [string appendString:@" | UIAccessibilityTraitKeyboardKey"]; 1213 | if (traits & UIAccessibilityTraitStaticText) 1214 | [string appendString:@" | UIAccessibilityTraitStaticText"]; 1215 | if (traits & UIAccessibilityTraitSummaryElement) 1216 | [string appendString:@" | UIAccessibilityTraitSummaryElement"]; 1217 | if (traits & UIAccessibilityTraitNotEnabled) 1218 | [string appendString:@" | UIAccessibilityTraitNotEnabled"]; 1219 | if (traits & UIAccessibilityTraitUpdatesFrequently) 1220 | [string appendString:@" | UIAccessibilityTraitUpdatesFrequently"]; 1221 | if (traits & UIAccessibilityTraitStartsMediaSession) 1222 | [string appendString:@" | UIAccessibilityTraitStartsMediaSession"]; 1223 | if (traits & UIAccessibilityTraitAdjustable) 1224 | [string appendFormat:@" | UIAccessibilityTraitAdjustable"]; 1225 | if ([string hasPrefix:@" | "]) 1226 | [string replaceCharactersInRange:NSMakeRange(0, 3) withString:@""]; 1227 | 1228 | return ([string length] > 0) ? string : @"UIAccessibilityTraitNone"; 1229 | } 1230 | 1231 | if ([value isKindOfClass:[NSValue class]]) 1232 | { 1233 | // print out the return for each value depending on type 1234 | NSString *type = [NSString stringWithUTF8String:[value objCType]]; 1235 | if ([type isEqualToString:@"c"]) 1236 | { 1237 | return ([value boolValue]) ? @"YES" : @"NO"; 1238 | } 1239 | else if ([type isEqualToString:@"{CGSize=ff}"]) 1240 | { 1241 | CGSize size = [value CGSizeValue]; 1242 | return CGSizeEqualToSize(size, CGSizeZero) ? @"CGSizeZero" : NSStringFromCGSize(size); 1243 | } 1244 | else if ([type isEqualToString:@"{UIEdgeInsets=ffff}"]) 1245 | { 1246 | UIEdgeInsets edgeInsets = [value UIEdgeInsetsValue]; 1247 | return UIEdgeInsetsEqualToEdgeInsets(edgeInsets, UIEdgeInsetsZero) ? @"UIEdgeInsetsZero" : NSStringFromUIEdgeInsets(edgeInsets); 1248 | } 1249 | } 1250 | else if ([value isKindOfClass:[UIColor class]]) 1251 | { 1252 | UIColor *color = (UIColor *)value; 1253 | return [self describeColor:color]; 1254 | } 1255 | else if ([value isKindOfClass:[UIFont class]]) 1256 | { 1257 | UIFont *font = (UIFont *)value; 1258 | return [NSString stringWithFormat:@"%.0fpx %@", font.pointSize, font.fontName]; 1259 | } 1260 | 1261 | return value ? [value description] : @"nil"; 1262 | } 1263 | 1264 | - (NSString *)describeColor:(UIColor *)color 1265 | { 1266 | if (!color) 1267 | return @"nil"; 1268 | 1269 | NSString *returnString = nil; 1270 | if (CGColorSpaceGetModel(CGColorGetColorSpace(color.CGColor)) == kCGColorSpaceModelRGB) 1271 | { 1272 | const CGFloat *components = CGColorGetComponents(color.CGColor); 1273 | returnString = [NSString stringWithFormat:@"R: %.0f G: %.0f B: %.0f A: %.2f", 1274 | components[0] * 256, 1275 | components[1] * 256, 1276 | components[2] * 256, 1277 | components[3]]; 1278 | } 1279 | else 1280 | { 1281 | returnString = [NSString stringWithFormat:@"%@ (incompatible color space)", color]; 1282 | } 1283 | return returnString; 1284 | } 1285 | 1286 | #pragma mark DCIntrospector Help 1287 | 1288 | - (void)toggleHelp 1289 | { 1290 | UIWindow *mainWindow = [self mainWindow]; 1291 | self.showingHelp = !self.showingHelp; 1292 | 1293 | if (self.showingHelp) 1294 | { 1295 | self.statusBarOverlay.leftLabel.text = @"Help"; 1296 | self.statusBarOverlay.rightLabel.text = @"Any key to close"; 1297 | UIView *backingView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, mainWindow.frame.size.width, mainWindow.frame.size.height)] autorelease]; 1298 | backingView.tag = 1548; 1299 | backingView.alpha = 0; 1300 | backingView.backgroundColor = [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.85f]; 1301 | [mainWindow addSubview:backingView]; 1302 | 1303 | UIWebView *webView = [[[UIWebView alloc] initWithFrame:backingView.frame] autorelease]; 1304 | webView.opaque = NO; 1305 | webView.backgroundColor = [UIColor clearColor]; 1306 | webView.delegate = self; 1307 | [backingView addSubview:webView]; 1308 | 1309 | NSMutableString *helpString = [NSMutableString stringWithString:@""]; 1310 | [helpString appendString:@"

DCIntrospect

"]; 1317 | [helpString appendString:@"

Created by Domestic Cat Software 2011.

"]; 1318 | [helpString appendString:@"

Twitter: @patr

"]; 1319 | [helpString appendString:@"

More info and full documentation: domesticcat.com.au/projects/introspect

"]; 1320 | [helpString appendString:@"

GitHub project: github.com/domesticcatsoftware/dcintrospect/

"]; 1321 | 1322 | [helpString appendString:@"

Key Bindings

"]; 1323 | [helpString appendString:@"

Edit DCIntrospectSettings.h to change key bindings.

"]; 1324 | 1325 | [helpString appendString:@"

General

"]; 1326 | 1327 | [helpString appendFormat:@"
Invoke Introspector
%@
", ([kDCIntrospectKeysInvoke isEqualToString:@" "]) ? @"spacebar" : kDCIntrospectKeysInvoke]; 1328 | [helpString appendFormat:@"
Toggle View Outlines
%@
", kDCIntrospectKeysToggleViewOutlines]; 1329 | [helpString appendFormat:@"
Toggle Highlighting Non-Opaque Views
%@
", kDCIntrospectKeysToggleNonOpaqueViews]; 1330 | [helpString appendFormat:@"
Toggle Help
%@
", kDCIntrospectKeysToggleHelp]; 1331 | [helpString appendFormat:@"
Toggle flash on drawRect: (see below)
%@
", kDCIntrospectKeysToggleFlashViewRedraws]; 1332 | [helpString appendFormat:@"
Toggle coordinates
%@
", kDCIntrospectKeysToggleShowCoordinates]; 1333 | [helpString appendString:@"
"]; 1334 | 1335 | [helpString appendString:@"

When a view is selected

"]; 1336 | [helpString appendFormat:@"
Log Properties
%@
", kDCIntrospectKeysLogProperties]; 1337 | [helpString appendFormat:@"
Log Accessibility Properties
%@
", kDCIntrospectKeysLogAccessibilityProperties]; 1338 | [helpString appendFormat:@"
Log Recursive Description for View
%@
", kDCIntrospectKeysLogViewRecursive]; 1339 | [helpString appendFormat:@"
Enter GDB
%@
", kDCIntrospectKeysEnterGDB]; 1340 | [helpString appendFormat:@"
Move up in view hierarchy
%@
", ([kDCIntrospectKeysMoveUpInViewHierarchy isEqualToString:@""]) ? @"page up" : kDCIntrospectKeysMoveUpInViewHierarchy]; 1341 | [helpString appendFormat:@"
Move back down in view hierarchy
%@
", ([kDCIntrospectKeysMoveBackInViewHierarchy isEqualToString:@""]) ? @"page down" : kDCIntrospectKeysMoveBackInViewHierarchy]; 1342 | [helpString appendString:@"
"]; 1343 | 1344 | [helpString appendFormat:@"
Nudge Left
\uE235 / %@
", kDCIntrospectKeysNudgeViewLeft]; 1345 | [helpString appendFormat:@"
Nudge Right
\uE234 / %@
", kDCIntrospectKeysNudgeViewRight]; 1346 | [helpString appendFormat:@"
Nudge Up
\uE232 / %@
", kDCIntrospectKeysNudgeViewUp]; 1347 | [helpString appendFormat:@"
Nudge Down
\uE233 / %@
", kDCIntrospectKeysNudgeViewDown]; 1348 | [helpString appendFormat:@"
Center in Superview
%@
", kDCIntrospectKeysCenterInSuperview]; 1349 | [helpString appendFormat:@"
Increase Width
alt + \uE234 / %@
", kDCIntrospectKeysIncreaseWidth]; 1350 | [helpString appendFormat:@"
Decrease Width
alt + \uE235 / %@
", kDCIntrospectKeysDecreaseWidth]; 1351 | [helpString appendFormat:@"
Increase Height
alt + \uE233 / %@
", kDCIntrospectKeysIncreaseHeight]; 1352 | [helpString appendFormat:@"
Decrease Height
alt + \uE232 / %@
", kDCIntrospectKeysDecreaseHeight]; 1353 | [helpString appendFormat:@"
Increase Alpha
%@
", kDCIntrospectKeysIncreaseViewAlpha]; 1354 | [helpString appendFormat:@"
Decrease Alpha
%@
", kDCIntrospectKeysDecreaseViewAlpha]; 1355 | [helpString appendFormat:@"
Log view code
%@
", kDCIntrospectKeysLogCodeForCurrentViewChanges]; 1356 | [helpString appendString:@"
"]; 1357 | 1358 | [helpString appendFormat:@"
Call setNeedsDisplay
%@
", kDCIntrospectKeysSetNeedsDisplay]; 1359 | [helpString appendFormat:@"
Call setNeedsLayout
%@
", kDCIntrospectKeysSetNeedsLayout]; 1360 | [helpString appendFormat:@"
Call reloadData (UITableView only)
%@
", kDCIntrospectKeysReloadData]; 1361 | [helpString appendString:@"
"]; 1362 | 1363 | [helpString appendFormat:@"

GDB

Push %@ (backtick) to jump into GDB. The currently selected view will be available as a variable named 'view'.

", kDCIntrospectKeysEnterGDB]; 1364 | 1365 | [helpString appendFormat:@"

Flash on drawRect: calls

To implement, call [[DCIntrospect sharedIntrospector] flashRect:inView:] inside the drawRect: method of any view you want to track.

When Flash on drawRect: is toggled on (binding: %@) the view will flash whenever drawRect: is called.

", kDCIntrospectKeysToggleFlashViewRedraws]; 1366 | 1367 | [helpString appendFormat:@"

Naming objects & logging code

By providing names for objects using setName:forObject:accessedWithSelf:, that name will be shown in the status bar instead of the class of the view.

This is also used when logging view code (binding: %@). Logging view code prints formatted code to the console for properties that have been changed.

For example, if you resize/move a view using the nudge keys, logging the view code will print view.frame = CGRectMake(50.0 ..etc); to the console. If a name is provided then view is replaced by the name.

", kDCIntrospectKeysLogCodeForCurrentViewChanges]; 1368 | 1369 | [helpString appendString:@"

License

DCIntrospect is made available under the MIT license.

"]; 1370 | 1371 | [helpString appendString:@"

Close Help

"]; 1372 | [helpString appendString:@"
"]; 1373 | 1374 | [UIView animateWithDuration:0.1 1375 | animations:^{ 1376 | backingView.alpha = 1.0f; 1377 | } completion:^(BOOL finished) { 1378 | [webView loadHTMLString:helpString baseURL:nil]; 1379 | }]; 1380 | } 1381 | else 1382 | { 1383 | UIView *backingView = (UIView *)[mainWindow viewWithTag:1548]; 1384 | [UIView animateWithDuration:0.1 1385 | animations:^{ 1386 | backingView.alpha = 0; 1387 | } completion:^(BOOL finished) { 1388 | [backingView removeFromSuperview]; 1389 | }]; 1390 | [self updateStatusBar]; 1391 | } 1392 | } 1393 | 1394 | - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType 1395 | { 1396 | NSString *requestString = [[request URL] absoluteString]; 1397 | if ([requestString isEqualToString:@"about:blank"]) 1398 | return YES; 1399 | else if ([requestString isEqualToString:@"http://close/"]) 1400 | [self toggleHelp]; 1401 | else 1402 | [[UIApplication sharedApplication] openURL:[request URL]]; 1403 | 1404 | return NO; 1405 | } 1406 | 1407 | #pragma mark Experimental 1408 | 1409 | - (void)logPropertiesForCurrentView 1410 | { 1411 | [self logPropertiesForObject:self.currentView]; 1412 | } 1413 | 1414 | - (void)logPropertiesForObject:(id)object 1415 | { 1416 | Class objectClass = [object class]; 1417 | NSString *className = [NSString stringWithFormat:@"%@", objectClass]; 1418 | 1419 | NSMutableString *outputString = [NSMutableString stringWithFormat:@"\n\n** %@", className]; 1420 | 1421 | // list the class heirachy 1422 | Class superClass = [objectClass superclass]; 1423 | while (superClass) 1424 | { 1425 | [outputString appendFormat:@" : %@", superClass]; 1426 | superClass = [superClass superclass]; 1427 | } 1428 | [outputString appendString:@" ** \n"]; 1429 | 1430 | // dump properties of class and super classes, up to UIView 1431 | NSMutableString *propertyString = [NSMutableString string]; 1432 | 1433 | Class inspectClass = objectClass; 1434 | while (inspectClass) 1435 | { 1436 | NSMutableString *objectString = [NSMutableString string]; 1437 | [objectString appendFormat:@"\n ** %@ properties **\n", inspectClass]; 1438 | 1439 | if (inspectClass == UIView.class) 1440 | { 1441 | UIView *view = (UIView *)object; 1442 | // print out generic uiview properties 1443 | [objectString appendFormat:@" tag: %i\n", view.tag]; 1444 | [objectString appendFormat:@" frame: %@ | ", NSStringFromCGRect(view.frame)]; 1445 | [objectString appendFormat:@"bounds: %@ | ", NSStringFromCGRect(view.bounds)]; 1446 | [objectString appendFormat:@"center: %@\n", NSStringFromCGPoint(view.center)]; 1447 | [objectString appendFormat:@" transform: %@\n", NSStringFromCGAffineTransform(view.transform)]; 1448 | [objectString appendFormat:@" autoresizingMask: %@\n", [self describeProperty:@"autoresizingMask" value:[NSNumber numberWithInt:view.autoresizingMask]]]; 1449 | [objectString appendFormat:@" autoresizesSubviews: %@\n", (view.autoresizesSubviews) ? @"YES" : @"NO"]; 1450 | [objectString appendFormat:@" contentMode: %@ | ", [self describeProperty:@"contentMode" value:[NSNumber numberWithInt:view.contentMode]]]; 1451 | [objectString appendFormat:@"contentStretch: %@\n", NSStringFromCGRect(view.contentStretch)]; 1452 | [objectString appendFormat:@" backgroundColor: %@\n", [self describeColor:view.backgroundColor]]; 1453 | [objectString appendFormat:@" alpha: %.2f | ", view.alpha]; 1454 | [objectString appendFormat:@"opaque: %@ | ", (view.opaque) ? @"YES" : @"NO"]; 1455 | [objectString appendFormat:@"hidden: %@ | ", (view.hidden) ? @"YES" : @"NO"]; 1456 | [objectString appendFormat:@"clips to bounds: %@ | ", (view.clipsToBounds) ? @"YES" : @"NO"]; 1457 | [objectString appendFormat:@"clearsContextBeforeDrawing: %@\n", (view.clearsContextBeforeDrawing) ? @"YES" : @"NO"]; 1458 | [objectString appendFormat:@" userInteractionEnabled: %@ | ", (view.userInteractionEnabled) ? @"YES" : @"NO"]; 1459 | [objectString appendFormat:@"multipleTouchEnabled: %@\n", (view.multipleTouchEnabled) ? @"YES" : @"NO"]; 1460 | [objectString appendFormat:@" gestureRecognizers: %@\n", (view.gestureRecognizers) ? [view.gestureRecognizers description] : @"nil"]; 1461 | } 1462 | else 1463 | { 1464 | // Dump all properties of the class 1465 | unsigned int count; 1466 | objc_property_t *properties = class_copyPropertyList(inspectClass, &count); 1467 | size_t buf_size = 1024; 1468 | char *buffer = malloc(buf_size); 1469 | 1470 | for (unsigned int i = 0; i < count; ++i) 1471 | { 1472 | // get the property name and selector name 1473 | NSString *propertyName = [NSString stringWithCString:property_getName(properties[i]) encoding:NSUTF8StringEncoding]; 1474 | 1475 | SEL sel = NSSelectorFromString(propertyName); 1476 | if ([object respondsToSelector:sel]) 1477 | { 1478 | NSString *propertyDescription; 1479 | @try 1480 | { 1481 | // get the return object and type for the selector 1482 | NSString *returnType = [NSString stringWithUTF8String:[[object methodSignatureForSelector:sel] methodReturnType]]; 1483 | id returnObject = [object valueForKey:propertyName]; 1484 | if ([returnType isEqualToString:@"c"]) 1485 | returnObject = [NSNumber numberWithBool:[returnObject intValue] != 0]; 1486 | 1487 | propertyDescription = [self describeProperty:propertyName value:returnObject]; 1488 | } 1489 | @catch (NSException *exception) 1490 | { 1491 | // Non KVC compliant properties, see also +workaroundUITextInputTraitsPropertiesBug 1492 | propertyDescription = @"N/A"; 1493 | } 1494 | [objectString appendFormat:@" %@: %@\n", propertyName, propertyDescription]; 1495 | } 1496 | } 1497 | 1498 | free(properties); 1499 | free(buffer); 1500 | } 1501 | 1502 | [propertyString insertString:objectString atIndex:0]; 1503 | 1504 | if (inspectClass == UIView.class) 1505 | { 1506 | break; 1507 | } 1508 | 1509 | inspectClass = [inspectClass superclass]; 1510 | } 1511 | 1512 | [outputString appendString:propertyString]; 1513 | 1514 | // list targets if there are any 1515 | if ([object respondsToSelector:@selector(allTargets)]) 1516 | { 1517 | [outputString appendString:@"\n ** Targets & Actions **\n"]; 1518 | UIControl *control = (UIControl *)object; 1519 | UIControlEvents controlEvents = [control allControlEvents]; 1520 | NSSet *allTargets = [control allTargets]; 1521 | [allTargets enumerateObjectsUsingBlock:^(id target, BOOL *stop) 1522 | { 1523 | NSArray *actions = [control actionsForTarget:target forControlEvent:controlEvents]; 1524 | [actions enumerateObjectsUsingBlock:^(id action, NSUInteger idx, BOOL *stop2) 1525 | { 1526 | [outputString appendFormat:@" target: %@ action: %@\n", target, action]; 1527 | }]; 1528 | }]; 1529 | } 1530 | 1531 | [outputString appendString:@"\n"]; 1532 | NSLog(@"DCIntrospect: %@", outputString); 1533 | } 1534 | 1535 | - (void)logAccessabilityPropertiesForObject:(id)object 1536 | { 1537 | Class objectClass = [object class]; 1538 | NSString *className = [NSString stringWithFormat:@"%@", objectClass]; 1539 | NSMutableString *outputString = [NSMutableString string]; 1540 | 1541 | // warn about accessibility inspector if the element count is zero 1542 | NSUInteger count = [object accessibilityElementCount]; 1543 | if (count == 0) 1544 | [outputString appendString:@"\n\n** Warning: Logging accessibility properties requires Accessibility Inspector: Settings.app -> General -> Accessibility\n"]; 1545 | 1546 | [outputString appendFormat:@"** %@ Accessibility Properties **\n", className]; 1547 | [outputString appendFormat:@" label: %@\n", [object accessibilityLabel]]; 1548 | [outputString appendFormat:@" hint: %@\n", [object accessibilityHint]]; 1549 | [outputString appendFormat:@" traits: %@\n", [self describeProperty:@"accessibilityTraits" value:[NSNumber numberWithUnsignedLongLong:[object accessibilityTraits]]]]; 1550 | [outputString appendFormat:@" value: %@\n", [object accessibilityValue]]; 1551 | [outputString appendFormat:@" frame: %@\n", NSStringFromCGRect([object accessibilityFrame])]; 1552 | [outputString appendString:@"\n"]; 1553 | 1554 | NSLog(@"DCIntrospect: %@", outputString); 1555 | } 1556 | 1557 | - (NSArray *)subclassesOfClass:(Class)parentClass 1558 | { 1559 | // thanks to Matt Gallagher: 1560 | int numClasses = objc_getClassList(NULL, 0); 1561 | Class *classes = NULL; 1562 | 1563 | classes = malloc(sizeof(Class) * numClasses); 1564 | numClasses = objc_getClassList(classes, numClasses); 1565 | 1566 | NSMutableArray *result = [NSMutableArray array]; 1567 | for (NSInteger i = 0; i < numClasses; i++) 1568 | { 1569 | Class superClass = classes[i]; 1570 | do 1571 | { 1572 | superClass = class_getSuperclass(superClass); 1573 | } while(superClass && superClass != parentClass); 1574 | 1575 | if (superClass == nil) 1576 | { 1577 | continue; 1578 | } 1579 | 1580 | [result addObject:classes[i]]; 1581 | } 1582 | 1583 | free(classes); 1584 | 1585 | return result; 1586 | } 1587 | 1588 | #pragma mark Helper Methods 1589 | 1590 | - (UIWindow *)mainWindow 1591 | { 1592 | NSArray *windows = [[UIApplication sharedApplication] windows]; 1593 | if (windows.count == 0) 1594 | return nil; 1595 | 1596 | return [windows objectAtIndex:0]; 1597 | } 1598 | 1599 | - (NSMutableArray *)viewsAtPoint:(CGPoint)touchPoint inView:(UIView *)view 1600 | { 1601 | NSMutableArray *views = [[NSMutableArray alloc] init]; 1602 | for (UIView *subview in view.subviews) 1603 | { 1604 | CGRect rect = subview.frame; 1605 | if ([self shouldIgnoreView:subview]) 1606 | continue; 1607 | 1608 | if (CGRectContainsPoint(rect, touchPoint)) 1609 | { 1610 | [views addObject:subview]; 1611 | 1612 | // convert the point to it's superview 1613 | CGPoint newTouchPoint = touchPoint; 1614 | newTouchPoint = [view convertPoint:newTouchPoint toView:subview]; 1615 | [views addObjectsFromArray:[self viewsAtPoint:newTouchPoint inView:subview]]; 1616 | } 1617 | } 1618 | 1619 | return [views autorelease]; 1620 | } 1621 | 1622 | - (void)fadeView:(UIView *)view toAlpha:(CGFloat)alpha 1623 | { 1624 | [UIView animateWithDuration:0.1 1625 | delay:0.0 1626 | options:UIViewAnimationOptionAllowUserInteraction 1627 | animations:^{ 1628 | view.alpha = alpha; 1629 | } 1630 | completion:nil]; 1631 | } 1632 | 1633 | - (BOOL)view:(UIView *)view containsSubview:(UIView *)subview 1634 | { 1635 | for (UIView *aView in view.subviews) 1636 | { 1637 | if (aView == subview) 1638 | return YES; 1639 | 1640 | if ([self view:aView containsSubview:subview]) 1641 | return YES; 1642 | } 1643 | 1644 | return NO; 1645 | } 1646 | 1647 | - (BOOL)shouldIgnoreView:(UIView *)view 1648 | { 1649 | if (view == self.frameView || view == self.inputTextView) 1650 | return YES; 1651 | return NO; 1652 | } 1653 | 1654 | @end 1655 | -------------------------------------------------------------------------------- /DCIntrospect/DCIntrospectSettings.h: -------------------------------------------------------------------------------- 1 | ////////////// 2 | // Settings // 3 | ////////////// 4 | 5 | #define kDCIntrospectFlashOnRedrawColor [UIColor colorWithRed:1.0f green:0.0f blue:0.0f alpha:0.4f] // UIColor 6 | #define kDCIntrospectFlashOnRedrawFlashLength 0.03f // NSTimeInterval 7 | #define kDCIntrospectOpaqueColor [UIColor redColor] // UIColor 8 | #define kDCIntrospectTemporaryDisableDuration 10. // Seconds 9 | 10 | ////////////////// 11 | // Key Bindings // 12 | ////////////////// 13 | 14 | // '' is equivalent to page up (copy and paste this character to use) 15 | // '' is equivalent to page down (copy and paste this character to use) 16 | 17 | // Global // 18 | #define kDCIntrospectKeysInvoke @" " // starts introspector 19 | #define kDCIntrospectKeysToggleViewOutlines @"o" // shows outlines for all views 20 | #define kDCIntrospectKeysToggleNonOpaqueViews @"O" // changes all non-opaque view background colours to red (destructive) 21 | #define kDCIntrospectKeysToggleHelp @"?" // shows help 22 | #define kDCIntrospectKeysToggleFlashViewRedraws @"f" // toggle flashing on redraw for all views that implement [[DCIntrospect sharedIntrospector] flashRect:inView:] in drawRect: 23 | #define kDCIntrospectKeysToggleShowCoordinates @"c" // toggles the coordinates display 24 | #define kDCIntrospectKeysEnterBlockMode @"b" // enters block action mode 25 | 26 | // When introspector is invoked and a view is selected // 27 | #define kDCIntrospectKeysNudgeViewLeft @"4" // nudges the selected view in given direction 28 | #define kDCIntrospectKeysNudgeViewRight @"6" // 29 | #define kDCIntrospectKeysNudgeViewUp @"8" // 30 | #define kDCIntrospectKeysNudgeViewDown @"2" // 31 | #define kDCIntrospectKeysCenterInSuperview @"5" // centers the selected view in it's superview 32 | #define kDCIntrospectKeysIncreaseWidth @"9" // increases/decreases the width/height of selected view 33 | #define kDCIntrospectKeysDecreaseWidth @"7" // 34 | #define kDCIntrospectKeysIncreaseHeight @"3" // 35 | #define kDCIntrospectKeysDecreaseHeight @"1" // 36 | #define kDCIntrospectKeysLogCodeForCurrentViewChanges @"0" // prints code to the console of the changes to the current view. If the view has not been changed nothing will be printed. For example, if you nudge a view or change it's rect with the nudge keys, this will log '<#view#>.frame = CGRectMake(50.0, ..etc);'. Or if you set it's name using setName:forObject:accessedWithSelf: it will use the name provided, for example 'myView.frame = CGRectMake(...);'. Setting accessedWithSelf to YES would output 'self.myView.frame = CGRectMake(...);'. 37 | 38 | #define kDCIntrospectKeysIncreaseViewAlpha @"+" // increases/decreases the selected views alpha value 39 | #define kDCIntrospectKeysDecreaseViewAlpha @"-" // 40 | 41 | #define kDCIntrospectKeysSetNeedsDisplay @"d" // calls setNeedsDisplay on selected view 42 | #define kDCIntrospectKeysSetNeedsLayout @"l" // calls setNeedsLayout on selected view 43 | #define kDCIntrospectKeysReloadData @"r" // calls reloadData on selected view if it's a UITableView 44 | #define kDCIntrospectKeysLogProperties @"p" // logs all properties of the selected view 45 | #define kDCIntrospectKeysLogAccessibilityProperties @"a" // logs accessibility info (useful for automated view tests - thanks to @samsoffes for the idea) 46 | #define kDCIntrospectKeysLogViewRecursive @"v" // calls private method recursiveDescription which logs selected view heirachy 47 | 48 | #define kDCIntrospectKeysMoveUpInViewHierarchy @"y" // changes the selected view to it's superview 49 | #define kDCIntrospectKeysMoveBackInViewHierarchy @"t" // changes the selected view back to the previous view selected (after using the above command) 50 | 51 | #define kDCIntrospectKeysMoveDownToFirstSubview @"h" 52 | #define kDCIntrospectKeysMoveToNextSiblingView @"j" 53 | #define kDCIntrospectKeysMoveToPrevSiblingView @"g" 54 | 55 | #define kDCIntrospectKeysEnterGDB @"`" // enters GDB 56 | #define kDCIntrospectKeysDisableForPeriod @"~" // disables DCIntrospect for a given period (see kDCIntrospectTemporaryDisableDuration) 57 | -------------------------------------------------------------------------------- /DCIntrospect/DCStatusBarOverlay.h: -------------------------------------------------------------------------------- 1 | // 2 | // DCStatusBarOverlay.h 3 | // 4 | // Copyright 2011 Domestic Cat. All rights reserved. 5 | // 6 | 7 | // Based mainly on @myellow's excellent MTStatusBarOverlay: https://github.com/myell0w/MTStatusBarOverlay 8 | 9 | 10 | #import "DCIntrospectSettings.h" 11 | 12 | #define kDCIntrospectNotificationStatusBarTapped @"kDCIntrospectNotificationStatusBarTapped" 13 | 14 | @interface DCStatusBarOverlay : UIWindow 15 | { 16 | } 17 | 18 | @property (nonatomic, retain) UILabel *leftLabel; 19 | @property (nonatomic, retain) UILabel *rightLabel; 20 | 21 | /////////// 22 | // Setup // 23 | /////////// 24 | 25 | - (id)init; 26 | - (void)updateBarFrame; 27 | 28 | ///////////// 29 | // Actions // 30 | ///////////// 31 | 32 | - (void)tapped; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /DCIntrospect/DCStatusBarOverlay.m: -------------------------------------------------------------------------------- 1 | // 2 | // DCStatusBarOverlay.m 3 | // 4 | // Copyright 2011 Domestic Cat. All rights reserved. 5 | // 6 | 7 | #import "DCStatusBarOverlay.h" 8 | 9 | @implementation DCStatusBarOverlay 10 | @synthesize leftLabel, rightLabel; 11 | 12 | - (void)dealloc 13 | { 14 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil]; 15 | 16 | [leftLabel release]; 17 | [rightLabel release]; 18 | 19 | [super dealloc]; 20 | } 21 | 22 | #pragma mark Setup 23 | 24 | - (id)init 25 | { 26 | if ((self = [super initWithFrame:CGRectZero])) 27 | { 28 | self.windowLevel = UIWindowLevelStatusBar + 1.0f; 29 | UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; 30 | CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width; 31 | CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height; 32 | const CGFloat bar_size = 20; 33 | if (UIInterfaceOrientationIsLandscape(orientation)) 34 | self.frame = CGRectMake(0, 0, screenHeight, bar_size); 35 | else 36 | self.frame = CGRectMake(0, 0, screenWidth, bar_size); 37 | self.backgroundColor = [UIColor blackColor]; 38 | 39 | UIImageView *backgroundImageView = [[UIImageView alloc] initWithFrame:self.frame]; 40 | backgroundImageView.image = [[UIImage imageNamed:@"statusBarBackground.png"] stretchableImageWithLeftCapWidth:2.0f topCapHeight:0.0f]; 41 | [self addSubview:backgroundImageView]; 42 | [backgroundImageView release]; 43 | 44 | self.leftLabel = [[[UILabel alloc] initWithFrame:CGRectOffset(self.frame, 2.0f, 0.0f)] autorelease]; 45 | self.leftLabel.backgroundColor = [UIColor clearColor]; 46 | self.leftLabel.textAlignment = UITextAlignmentLeft; 47 | self.leftLabel.font = [UIFont boldSystemFontOfSize:12.0f]; 48 | self.leftLabel.textColor = [UIColor colorWithWhite:0.97f alpha:1.0f]; 49 | self.leftLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; 50 | [self addSubview:self.leftLabel]; 51 | 52 | self.rightLabel = [[[UILabel alloc] initWithFrame:CGRectOffset(self.frame, -2.0f, 0.0f)] autorelease]; 53 | self.rightLabel.backgroundColor = [UIColor clearColor]; 54 | self.rightLabel.font = [UIFont boldSystemFontOfSize:12.0f]; 55 | self.rightLabel.textAlignment = UITextAlignmentRight; 56 | self.rightLabel.textColor = [UIColor colorWithWhite:0.9f alpha:1.0f]; 57 | self.rightLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; 58 | [self addSubview:self.rightLabel]; 59 | 60 | UITapGestureRecognizer *gestureRecognizer = [[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped)] autorelease]; 61 | [self addGestureRecognizer:gestureRecognizer]; 62 | 63 | [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; 64 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateBarFrame) name:UIDeviceOrientationDidChangeNotification object:nil]; 65 | } 66 | 67 | return self; 68 | } 69 | 70 | - (void)updateBarFrame 71 | { 72 | // current interface orientation 73 | UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; 74 | CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width; 75 | CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height; 76 | 77 | CGFloat pi = (CGFloat)M_PI; 78 | if (orientation == UIDeviceOrientationPortrait) 79 | { 80 | self.transform = CGAffineTransformIdentity; 81 | self.frame = CGRectMake(0, 0, screenWidth, self.frame.size.height); 82 | } 83 | else if (orientation == UIDeviceOrientationLandscapeLeft) 84 | { 85 | self.transform = CGAffineTransformMakeRotation(pi * (90) / 180.0f); 86 | self.frame = CGRectMake(screenWidth - self.frame.size.width, 0, self.frame.size.width, screenHeight); 87 | } 88 | else if (orientation == UIDeviceOrientationLandscapeRight) 89 | { 90 | self.transform = CGAffineTransformMakeRotation(pi * (-90) / 180.0f); 91 | self.frame = CGRectMake(0, 0, self.frame.size.width, screenHeight); 92 | } 93 | else if (orientation == UIDeviceOrientationPortraitUpsideDown) 94 | { 95 | self.transform = CGAffineTransformMakeRotation(pi); 96 | self.frame = CGRectMake(0, screenHeight - self.frame.size.height, screenWidth, self.frame.size.height); 97 | } 98 | } 99 | 100 | #pragma mark Actions 101 | 102 | - (void)tapped 103 | { 104 | [[NSNotificationCenter defaultCenter] postNotificationName:kDCIntrospectNotificationStatusBarTapped object:nil]; 105 | } 106 | 107 | @end 108 | -------------------------------------------------------------------------------- /DCIntrospectDemo/DCIntrospectDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 625780B0138A052F004DA77B /* circle.png in Resources */ = {isa = PBXBuildFile; fileRef = 625780AF138A052F004DA77B /* circle.png */; }; 11 | 625780B2138A0EBD004DA77B /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 625780B1138A0EBC004DA77B /* QuartzCore.framework */; }; 12 | AB0D01D5136A778000962171 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB0D01D4136A778000962171 /* UIKit.framework */; }; 13 | AB0D01D7136A778000962171 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB0D01D6136A778000962171 /* Foundation.framework */; }; 14 | AB0D01D9136A778000962171 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB0D01D8136A778000962171 /* CoreGraphics.framework */; }; 15 | AB0D01DF136A778000962171 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = AB0D01DD136A778000962171 /* InfoPlist.strings */; }; 16 | AB0D01E2136A778000962171 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AB0D01E1136A778000962171 /* main.m */; }; 17 | AB0D01E5136A778000962171 /* DCIntrospectDemoAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AB0D01E4136A778000962171 /* DCIntrospectDemoAppDelegate.m */; }; 18 | AB0D01E8136A778000962171 /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = AB0D01E6136A778000962171 /* MainWindow.xib */; }; 19 | AB0D01EB136A778000962171 /* DCIntrospectDemoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AB0D01EA136A778000962171 /* DCIntrospectDemoViewController.m */; }; 20 | AB0D0214136A9E8D00962171 /* DCIntrospect.m in Sources */ = {isa = PBXBuildFile; fileRef = AB0D0213136A9E8D00962171 /* DCIntrospect.m */; }; 21 | AB0D021F136A9FE000962171 /* DCFrameView.m in Sources */ = {isa = PBXBuildFile; fileRef = AB0D021E136A9FE000962171 /* DCFrameView.m */; }; 22 | ABDA505A136E0FDD00339835 /* DCStatusBarOverlay.m in Sources */ = {isa = PBXBuildFile; fileRef = ABDA5059136E0FDD00339835 /* DCStatusBarOverlay.m */; }; 23 | ABDA50A0136FDD9400339835 /* DCCrossHairView.m in Sources */ = {isa = PBXBuildFile; fileRef = ABDA509F136FDD9300339835 /* DCCrossHairView.m */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXFileReference section */ 27 | 625780AF138A052F004DA77B /* circle.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = circle.png; sourceTree = ""; }; 28 | 625780B1138A0EBC004DA77B /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 29 | AB0D01D0136A778000962171 /* DCIntrospectDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DCIntrospectDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 30 | AB0D01D4136A778000962171 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 31 | AB0D01D6136A778000962171 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 32 | AB0D01D8136A778000962171 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 33 | AB0D01DC136A778000962171 /* DCIntrospectDemo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "DCIntrospectDemo-Info.plist"; sourceTree = ""; }; 34 | AB0D01DE136A778000962171 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 35 | AB0D01E0136A778000962171 /* DCIntrospectDemo-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "DCIntrospectDemo-Prefix.pch"; sourceTree = ""; }; 36 | AB0D01E1136A778000962171 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 37 | AB0D01E3136A778000962171 /* DCIntrospectDemoAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DCIntrospectDemoAppDelegate.h; sourceTree = ""; }; 38 | AB0D01E4136A778000962171 /* DCIntrospectDemoAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DCIntrospectDemoAppDelegate.m; sourceTree = ""; }; 39 | AB0D01E7136A778000962171 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MainWindow.xib; sourceTree = ""; }; 40 | AB0D01E9136A778000962171 /* DCIntrospectDemoViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DCIntrospectDemoViewController.h; sourceTree = ""; }; 41 | AB0D01EA136A778000962171 /* DCIntrospectDemoViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DCIntrospectDemoViewController.m; sourceTree = ""; }; 42 | AB0D0212136A9E8D00962171 /* DCIntrospect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DCIntrospect.h; path = ../../DCIntrospect/DCIntrospect.h; sourceTree = ""; }; 43 | AB0D0213136A9E8D00962171 /* DCIntrospect.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DCIntrospect.m; path = ../../DCIntrospect/DCIntrospect.m; sourceTree = ""; }; 44 | AB0D021D136A9FE000962171 /* DCFrameView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DCFrameView.h; path = ../../DCIntrospect/DCFrameView.h; sourceTree = ""; }; 45 | AB0D021E136A9FE000962171 /* DCFrameView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DCFrameView.m; path = ../../DCIntrospect/DCFrameView.m; sourceTree = ""; }; 46 | ABDA5058136E0FDD00339835 /* DCStatusBarOverlay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DCStatusBarOverlay.h; path = ../../DCIntrospect/DCStatusBarOverlay.h; sourceTree = ""; }; 47 | ABDA5059136E0FDD00339835 /* DCStatusBarOverlay.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DCStatusBarOverlay.m; path = ../../DCIntrospect/DCStatusBarOverlay.m; sourceTree = ""; }; 48 | ABDA509E136FDD9300339835 /* DCCrossHairView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DCCrossHairView.h; path = ../../DCIntrospect/DCCrossHairView.h; sourceTree = ""; }; 49 | ABDA509F136FDD9300339835 /* DCCrossHairView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DCCrossHairView.m; path = ../../DCIntrospect/DCCrossHairView.m; sourceTree = ""; }; 50 | ABDA51091370E79100339835 /* DCIntrospectSettings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DCIntrospectSettings.h; path = ../../DCIntrospect/DCIntrospectSettings.h; sourceTree = ""; }; 51 | /* End PBXFileReference section */ 52 | 53 | /* Begin PBXFrameworksBuildPhase section */ 54 | AB0D01CD136A778000962171 /* Frameworks */ = { 55 | isa = PBXFrameworksBuildPhase; 56 | buildActionMask = 2147483647; 57 | files = ( 58 | 625780B2138A0EBD004DA77B /* QuartzCore.framework in Frameworks */, 59 | AB0D01D5136A778000962171 /* UIKit.framework in Frameworks */, 60 | AB0D01D7136A778000962171 /* Foundation.framework in Frameworks */, 61 | AB0D01D9136A778000962171 /* CoreGraphics.framework in Frameworks */, 62 | ); 63 | runOnlyForDeploymentPostprocessing = 0; 64 | }; 65 | /* End PBXFrameworksBuildPhase section */ 66 | 67 | /* Begin PBXGroup section */ 68 | AB0D01C5136A778000962171 = { 69 | isa = PBXGroup; 70 | children = ( 71 | AB0D01DA136A778000962171 /* DCIntrospectDemo */, 72 | AB0D01D3136A778000962171 /* Frameworks */, 73 | AB0D01D1136A778000962171 /* Products */, 74 | ); 75 | sourceTree = ""; 76 | }; 77 | AB0D01D1136A778000962171 /* Products */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | AB0D01D0136A778000962171 /* DCIntrospectDemo.app */, 81 | ); 82 | name = Products; 83 | sourceTree = ""; 84 | }; 85 | AB0D01D3136A778000962171 /* Frameworks */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | 625780B1138A0EBC004DA77B /* QuartzCore.framework */, 89 | AB0D01D4136A778000962171 /* UIKit.framework */, 90 | AB0D01D6136A778000962171 /* Foundation.framework */, 91 | AB0D01D8136A778000962171 /* CoreGraphics.framework */, 92 | ); 93 | name = Frameworks; 94 | sourceTree = ""; 95 | }; 96 | AB0D01DA136A778000962171 /* DCIntrospectDemo */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | AB0D01F7136A79A800962171 /* DCIntrospect */, 100 | AB0D01E3136A778000962171 /* DCIntrospectDemoAppDelegate.h */, 101 | AB0D01E4136A778000962171 /* DCIntrospectDemoAppDelegate.m */, 102 | AB0D01E6136A778000962171 /* MainWindow.xib */, 103 | 625780AF138A052F004DA77B /* circle.png */, 104 | AB0D01E9136A778000962171 /* DCIntrospectDemoViewController.h */, 105 | AB0D01EA136A778000962171 /* DCIntrospectDemoViewController.m */, 106 | AB0D01DB136A778000962171 /* Supporting Files */, 107 | ); 108 | path = DCIntrospectDemo; 109 | sourceTree = ""; 110 | }; 111 | AB0D01DB136A778000962171 /* Supporting Files */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | AB0D01DC136A778000962171 /* DCIntrospectDemo-Info.plist */, 115 | AB0D01DD136A778000962171 /* InfoPlist.strings */, 116 | AB0D01E0136A778000962171 /* DCIntrospectDemo-Prefix.pch */, 117 | AB0D01E1136A778000962171 /* main.m */, 118 | ); 119 | name = "Supporting Files"; 120 | sourceTree = ""; 121 | }; 122 | AB0D01F7136A79A800962171 /* DCIntrospect */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | ABDA51091370E79100339835 /* DCIntrospectSettings.h */, 126 | AB0D0212136A9E8D00962171 /* DCIntrospect.h */, 127 | AB0D0213136A9E8D00962171 /* DCIntrospect.m */, 128 | ABDA5058136E0FDD00339835 /* DCStatusBarOverlay.h */, 129 | ABDA5059136E0FDD00339835 /* DCStatusBarOverlay.m */, 130 | AB0D021D136A9FE000962171 /* DCFrameView.h */, 131 | AB0D021E136A9FE000962171 /* DCFrameView.m */, 132 | ABDA509E136FDD9300339835 /* DCCrossHairView.h */, 133 | ABDA509F136FDD9300339835 /* DCCrossHairView.m */, 134 | ); 135 | name = DCIntrospect; 136 | sourceTree = ""; 137 | }; 138 | /* End PBXGroup section */ 139 | 140 | /* Begin PBXNativeTarget section */ 141 | AB0D01CF136A778000962171 /* DCIntrospectDemo */ = { 142 | isa = PBXNativeTarget; 143 | buildConfigurationList = AB0D01F1136A778000962171 /* Build configuration list for PBXNativeTarget "DCIntrospectDemo" */; 144 | buildPhases = ( 145 | AB0D01CC136A778000962171 /* Sources */, 146 | AB0D01CD136A778000962171 /* Frameworks */, 147 | AB0D01CE136A778000962171 /* Resources */, 148 | ); 149 | buildRules = ( 150 | ); 151 | dependencies = ( 152 | ); 153 | name = DCIntrospectDemo; 154 | productName = DCIntrospectDemo; 155 | productReference = AB0D01D0136A778000962171 /* DCIntrospectDemo.app */; 156 | productType = "com.apple.product-type.application"; 157 | }; 158 | /* End PBXNativeTarget section */ 159 | 160 | /* Begin PBXProject section */ 161 | AB0D01C7136A778000962171 /* Project object */ = { 162 | isa = PBXProject; 163 | attributes = { 164 | LastUpgradeCheck = 0410; 165 | }; 166 | buildConfigurationList = AB0D01CA136A778000962171 /* Build configuration list for PBXProject "DCIntrospectDemo" */; 167 | compatibilityVersion = "Xcode 3.2"; 168 | developmentRegion = English; 169 | hasScannedForEncodings = 0; 170 | knownRegions = ( 171 | en, 172 | ); 173 | mainGroup = AB0D01C5136A778000962171; 174 | productRefGroup = AB0D01D1136A778000962171 /* Products */; 175 | projectDirPath = ""; 176 | projectRoot = ""; 177 | targets = ( 178 | AB0D01CF136A778000962171 /* DCIntrospectDemo */, 179 | ); 180 | }; 181 | /* End PBXProject section */ 182 | 183 | /* Begin PBXResourcesBuildPhase section */ 184 | AB0D01CE136A778000962171 /* Resources */ = { 185 | isa = PBXResourcesBuildPhase; 186 | buildActionMask = 2147483647; 187 | files = ( 188 | AB0D01DF136A778000962171 /* InfoPlist.strings in Resources */, 189 | AB0D01E8136A778000962171 /* MainWindow.xib in Resources */, 190 | 625780B0138A052F004DA77B /* circle.png in Resources */, 191 | ); 192 | runOnlyForDeploymentPostprocessing = 0; 193 | }; 194 | /* End PBXResourcesBuildPhase section */ 195 | 196 | /* Begin PBXSourcesBuildPhase section */ 197 | AB0D01CC136A778000962171 /* Sources */ = { 198 | isa = PBXSourcesBuildPhase; 199 | buildActionMask = 2147483647; 200 | files = ( 201 | AB0D01E2136A778000962171 /* main.m in Sources */, 202 | AB0D01E5136A778000962171 /* DCIntrospectDemoAppDelegate.m in Sources */, 203 | AB0D01EB136A778000962171 /* DCIntrospectDemoViewController.m in Sources */, 204 | AB0D0214136A9E8D00962171 /* DCIntrospect.m in Sources */, 205 | AB0D021F136A9FE000962171 /* DCFrameView.m in Sources */, 206 | ABDA505A136E0FDD00339835 /* DCStatusBarOverlay.m in Sources */, 207 | ABDA50A0136FDD9400339835 /* DCCrossHairView.m in Sources */, 208 | ); 209 | runOnlyForDeploymentPostprocessing = 0; 210 | }; 211 | /* End PBXSourcesBuildPhase section */ 212 | 213 | /* Begin PBXVariantGroup section */ 214 | AB0D01DD136A778000962171 /* InfoPlist.strings */ = { 215 | isa = PBXVariantGroup; 216 | children = ( 217 | AB0D01DE136A778000962171 /* en */, 218 | ); 219 | name = InfoPlist.strings; 220 | sourceTree = ""; 221 | }; 222 | AB0D01E6136A778000962171 /* MainWindow.xib */ = { 223 | isa = PBXVariantGroup; 224 | children = ( 225 | AB0D01E7136A778000962171 /* en */, 226 | ); 227 | name = MainWindow.xib; 228 | sourceTree = ""; 229 | }; 230 | /* End PBXVariantGroup section */ 231 | 232 | /* Begin XCBuildConfiguration section */ 233 | AB0D01EF136A778000962171 /* Debug */ = { 234 | isa = XCBuildConfiguration; 235 | buildSettings = { 236 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 237 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 238 | GCC_C_LANGUAGE_STANDARD = gnu99; 239 | GCC_OPTIMIZATION_LEVEL = 0; 240 | GCC_PREPROCESSOR_DEFINITIONS = DEBUG; 241 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 242 | GCC_VERSION = com.apple.compilers.llvmgcc42; 243 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 244 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 245 | GCC_WARN_UNUSED_VARIABLE = YES; 246 | IPHONEOS_DEPLOYMENT_TARGET = 4.3; 247 | OTHER_CFLAGS = ""; 248 | RUN_CLANG_STATIC_ANALYZER = YES; 249 | SDKROOT = iphoneos; 250 | TARGETED_DEVICE_FAMILY = 1; 251 | }; 252 | name = Debug; 253 | }; 254 | AB0D01F0136A778000962171 /* Release */ = { 255 | isa = XCBuildConfiguration; 256 | buildSettings = { 257 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 258 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 259 | GCC_C_LANGUAGE_STANDARD = gnu99; 260 | GCC_VERSION = com.apple.compilers.llvmgcc42; 261 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 262 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 263 | GCC_WARN_UNUSED_VARIABLE = YES; 264 | IPHONEOS_DEPLOYMENT_TARGET = 4.3; 265 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 266 | RUN_CLANG_STATIC_ANALYZER = YES; 267 | SDKROOT = iphoneos; 268 | TARGETED_DEVICE_FAMILY = 1; 269 | }; 270 | name = Release; 271 | }; 272 | AB0D01F2136A778000962171 /* Debug */ = { 273 | isa = XCBuildConfiguration; 274 | buildSettings = { 275 | ALWAYS_SEARCH_USER_PATHS = NO; 276 | COPY_PHASE_STRIP = NO; 277 | GCC_DYNAMIC_NO_PIC = NO; 278 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 279 | GCC_PREFIX_HEADER = "DCIntrospectDemo/DCIntrospectDemo-Prefix.pch"; 280 | GCC_WARN_SHADOW = YES; 281 | GCC_WARN_UNDECLARED_SELECTOR = YES; 282 | INFOPLIST_FILE = "DCIntrospectDemo/DCIntrospectDemo-Info.plist"; 283 | PRODUCT_NAME = "$(TARGET_NAME)"; 284 | WRAPPER_EXTENSION = app; 285 | }; 286 | name = Debug; 287 | }; 288 | AB0D01F3136A778000962171 /* Release */ = { 289 | isa = XCBuildConfiguration; 290 | buildSettings = { 291 | ALWAYS_SEARCH_USER_PATHS = NO; 292 | COPY_PHASE_STRIP = YES; 293 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 294 | GCC_PREFIX_HEADER = "DCIntrospectDemo/DCIntrospectDemo-Prefix.pch"; 295 | GCC_WARN_SHADOW = YES; 296 | GCC_WARN_UNDECLARED_SELECTOR = YES; 297 | INFOPLIST_FILE = "DCIntrospectDemo/DCIntrospectDemo-Info.plist"; 298 | PRODUCT_NAME = "$(TARGET_NAME)"; 299 | VALIDATE_PRODUCT = YES; 300 | WRAPPER_EXTENSION = app; 301 | }; 302 | name = Release; 303 | }; 304 | /* End XCBuildConfiguration section */ 305 | 306 | /* Begin XCConfigurationList section */ 307 | AB0D01CA136A778000962171 /* Build configuration list for PBXProject "DCIntrospectDemo" */ = { 308 | isa = XCConfigurationList; 309 | buildConfigurations = ( 310 | AB0D01EF136A778000962171 /* Debug */, 311 | AB0D01F0136A778000962171 /* Release */, 312 | ); 313 | defaultConfigurationIsVisible = 0; 314 | defaultConfigurationName = Release; 315 | }; 316 | AB0D01F1136A778000962171 /* Build configuration list for PBXNativeTarget "DCIntrospectDemo" */ = { 317 | isa = XCConfigurationList; 318 | buildConfigurations = ( 319 | AB0D01F2136A778000962171 /* Debug */, 320 | AB0D01F3136A778000962171 /* Release */, 321 | ); 322 | defaultConfigurationIsVisible = 0; 323 | defaultConfigurationName = Release; 324 | }; 325 | /* End XCConfigurationList section */ 326 | }; 327 | rootObject = AB0D01C7136A778000962171 /* Project object */; 328 | } 329 | -------------------------------------------------------------------------------- /DCIntrospectDemo/DCIntrospectDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DCIntrospectDemo/DCIntrospectDemo/DCIntrospectDemo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIconFile 12 | 13 | CFBundleIdentifier 14 | com.domesticcat.${PRODUCT_NAME:rfc1034identifier} 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | ${PRODUCT_NAME} 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | 1.0 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | 1.0 27 | LSRequiresIPhoneOS 28 | 29 | NSMainNibFile 30 | MainWindow 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | UIInterfaceOrientationPortraitUpsideDown 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /DCIntrospectDemo/DCIntrospectDemo/DCIntrospectDemo-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'DCIntrospectDemo' target in the 'DCIntrospectDemo' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_3_0 8 | #warning "This project uses features only available in iPhone SDK 3.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #endif 15 | -------------------------------------------------------------------------------- /DCIntrospectDemo/DCIntrospectDemo/DCIntrospectDemoAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // DCIntrospectDemoAppDelegate.h 3 | // 4 | // Created by Domestic Cat on 29/04/11. 5 | // 6 | 7 | #import 8 | 9 | @class DCIntrospectDemoViewController; 10 | 11 | @interface DCIntrospectDemoAppDelegate : NSObject 12 | { 13 | } 14 | 15 | @property (nonatomic, retain) IBOutlet UIWindow *window; 16 | @property (nonatomic, retain) IBOutlet DCIntrospectDemoViewController *viewController; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /DCIntrospectDemo/DCIntrospectDemo/DCIntrospectDemoAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // DCIntrospectDemo 3 | // 4 | // Created by Domestic Cat on 29/04/11. 5 | // 6 | 7 | #import "DCIntrospectDemoAppDelegate.h" 8 | #import "DCIntrospectDemoViewController.h" 9 | #import "DCIntrospect.h" 10 | 11 | @implementation DCIntrospectDemoAppDelegate 12 | 13 | @synthesize window; 14 | @synthesize viewController; 15 | 16 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 17 | { 18 | // create a custom tap gesture recognizer so introspection can be invoked from a device 19 | // this one is a two finger triple tap 20 | UITapGestureRecognizer *defaultGestureRecognizer = [[[UITapGestureRecognizer alloc] init] autorelease]; 21 | defaultGestureRecognizer.cancelsTouchesInView = NO; 22 | defaultGestureRecognizer.delaysTouchesBegan = NO; 23 | defaultGestureRecognizer.delaysTouchesEnded = NO; 24 | defaultGestureRecognizer.numberOfTapsRequired = 3; 25 | defaultGestureRecognizer.numberOfTouchesRequired = 2; 26 | [DCIntrospect sharedIntrospector].invokeGestureRecognizer = defaultGestureRecognizer; 27 | 28 | self.window.rootViewController = self.viewController; 29 | [self.window makeKeyAndVisible]; 30 | 31 | // always insert this AFTER makeKeyAndVisible so statusBarOrientation is reported correctly. 32 | [[DCIntrospect sharedIntrospector] start]; 33 | 34 | return YES; 35 | } 36 | 37 | - (void)applicationWillResignActive:(UIApplication *)application 38 | { 39 | } 40 | 41 | - (void)applicationDidEnterBackground:(UIApplication *)application 42 | { 43 | } 44 | 45 | - (void)applicationWillEnterForeground:(UIApplication *)application 46 | { 47 | } 48 | 49 | - (void)applicationDidBecomeActive:(UIApplication *)application 50 | { 51 | } 52 | 53 | - (void)applicationWillTerminate:(UIApplication *)application 54 | { 55 | } 56 | 57 | - (void)dealloc 58 | { 59 | [window release]; 60 | [viewController release]; 61 | 62 | [super dealloc]; 63 | } 64 | 65 | @end 66 | -------------------------------------------------------------------------------- /DCIntrospectDemo/DCIntrospectDemo/DCIntrospectDemoViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DCIntrospectDemoViewController.h 3 | // DCIntrospectDemo 4 | // 5 | // Created by Domestic Cat on 29/04/11. 6 | // Copyright 2011 Domestic Cat Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DCIntrospectDemoViewController : UIViewController 12 | { 13 | } 14 | 15 | @property (nonatomic, retain) IBOutlet UIActivityIndicatorView *activityIndicator; 16 | @property (nonatomic, retain) IBOutlet UILabel *label; 17 | 18 | - (IBAction)buttonTapped:(id)sender; 19 | - (IBAction)switchChanged:(id)sender; 20 | - (IBAction)sliderChanged:(id)sender; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /DCIntrospectDemo/DCIntrospectDemo/DCIntrospectDemoViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DCIntrospectDemoViewController.m 3 | // DCIntrospectDemo 4 | // 5 | // Created by Domestic Cat on 29/04/11. 6 | // Copyright 2011 Domestic Cat Software. All rights reserved. 7 | // 8 | 9 | #import "DCIntrospectDemoViewController.h" 10 | #import "DCIntrospect.h" 11 | 12 | @implementation DCIntrospectDemoViewController 13 | @synthesize activityIndicator; 14 | @synthesize label; 15 | 16 | - (void)dealloc 17 | { 18 | [[DCIntrospect sharedIntrospector] removeNamesForViewsInView:self.view]; 19 | 20 | [activityIndicator release]; 21 | [label release]; 22 | [super dealloc]; 23 | } 24 | 25 | - (void)didReceiveMemoryWarning 26 | { 27 | [super didReceiveMemoryWarning]; 28 | } 29 | 30 | #pragma mark - View lifecycle 31 | 32 | - (void)viewDidLoad 33 | { 34 | [super viewDidLoad]; 35 | 36 | // set the activity indicator to a non-integer frame for demonstration 37 | self.activityIndicator.frame = CGRectOffset(self.activityIndicator.frame, 0.5, 0.0); 38 | [[DCIntrospect sharedIntrospector] setName:@"activityIndicator" forObject:self.activityIndicator accessedWithSelf:YES]; 39 | } 40 | 41 | - (void)viewDidUnload 42 | { 43 | [self setActivityIndicator:nil]; 44 | [self setLabel:nil]; 45 | 46 | [super viewDidUnload]; 47 | } 48 | 49 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation 50 | { 51 | return YES; 52 | } 53 | 54 | - (BOOL)textFieldShouldReturn:(UITextField *)textField 55 | { 56 | [textField resignFirstResponder]; 57 | return YES; 58 | } 59 | 60 | #pragma mark Table View Methods 61 | 62 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 63 | { 64 | static NSString *CellIdentifier = @"Cell"; 65 | 66 | UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 67 | if (cell == nil) 68 | { 69 | cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease]; 70 | } 71 | 72 | cell.textLabel.text = [NSString stringWithFormat:@"Row %i", indexPath.row]; 73 | cell.detailTextLabel.text = @"Detailed Text"; 74 | cell.accessoryType = UITableViewCellAccessoryCheckmark; 75 | 76 | return cell; 77 | } 78 | 79 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 80 | { 81 | return 2; 82 | } 83 | 84 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 85 | { 86 | return 3; 87 | } 88 | 89 | - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section 90 | { 91 | return [NSString stringWithFormat:@"Section %i", section]; 92 | } 93 | 94 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 95 | { 96 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 97 | } 98 | 99 | - (IBAction)buttonTapped:(id)sender 100 | { 101 | } 102 | 103 | - (IBAction)switchChanged:(id)sender 104 | { 105 | } 106 | 107 | - (IBAction)sliderChanged:(id)sender 108 | { 109 | } 110 | 111 | @end 112 | -------------------------------------------------------------------------------- /DCIntrospectDemo/DCIntrospectDemo/circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domesticcatsoftware/DCIntrospect/9555122e94e0f61375e5b375b906b81d6d5b1e6e/DCIntrospectDemo/DCIntrospectDemo/circle.png -------------------------------------------------------------------------------- /DCIntrospectDemo/DCIntrospectDemo/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /DCIntrospectDemo/DCIntrospectDemo/en.lproj/MainWindow.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1024 5 | 11A430e 6 | 1565 7 | 1117 8 | 552.00 9 | 10 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 11 | 521 12 | 13 | 14 | YES 15 | IBUITableView 16 | IBUIButton 17 | IBUIPageControl 18 | IBUISlider 19 | IBUISegmentedControl 20 | IBUISwitch 21 | IBUILabel 22 | IBUITextField 23 | IBProxyObject 24 | IBUICustomObject 25 | IBUIWindow 26 | IBUIView 27 | IBUIProgressView 28 | IBUIImageView 29 | IBUIViewController 30 | IBUIActivityIndicatorView 31 | 32 | 33 | YES 34 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 35 | 36 | 37 | YES 38 | 39 | YES 40 | 41 | 42 | 43 | 44 | YES 45 | 46 | IBFilesOwner 47 | IBCocoaTouchFramework 48 | 49 | 50 | IBFirstResponder 51 | IBCocoaTouchFramework 52 | 53 | 54 | IBCocoaTouchFramework 55 | 56 | 57 | 58 | 59 | 274 60 | 61 | YES 62 | 63 | 64 | 268 65 | {{20, 113}, {279, 209}} 66 | 67 | 68 | 69 | 70 | 3 71 | MQA 72 | 73 | YES 74 | IBCocoaTouchFramework 75 | YES 76 | 1 77 | 0 78 | YES 79 | 42 80 | 22 81 | 22 82 | 83 | 84 | 85 | 292 86 | {{235, 20}, {64, 64}} 87 | 88 | 89 | 90 | 91 | 3 92 | MCAwAA 93 | 94 | NO 95 | NO 96 | 97 | ImageAccessibilityHint 98 | ImageAccessibilityLabel 99 | 100 | 101 | IBCocoaTouchFramework 102 | 103 | NSImage 104 | circle.png 105 | 106 | 107 | 108 | 109 | 268 110 | {{28, 376}, {150, 9}} 111 | 112 | 113 | 114 | NO 115 | IBCocoaTouchFramework 116 | 0.5 117 | 118 | 119 | 120 | 292 121 | {{26, 394}, {118, 23}} 122 | 123 | 124 | 125 | NO 126 | IBCocoaTouchFramework 127 | 0 128 | 0 129 | 0.5 130 | 131 | 132 | 133 | 292 134 | {{158, 395}, {20, 20}} 135 | 136 | 137 | 138 | NO 139 | IBCocoaTouchFramework 140 | NO 141 | YES 142 | 2 143 | 144 | 145 | 146 | 268 147 | {{21, 331}, {278, 37}} 148 | 149 | 150 | 151 | NO 152 | 1 153 | 154 | Hint 155 | Button 156 | 157 | IBCocoaTouchFramework 158 | 0 159 | 0 160 | 161 | Helvetica-Bold 162 | 15 163 | 16 164 | 165 | 1 166 | Button w/ Actions 167 | 168 | 169 | 1 170 | MC4xOTYwNzg0MzQ2IDAuMzA5ODAzOTMyOSAwLjUyMTU2ODY1NgA 171 | 172 | 173 | 3 174 | MC41AA 175 | 176 | 177 | 178 | 179 | 268 180 | {{20, 424}, {145, 23}} 181 | 182 | 183 | 184 | NO 185 | YES 186 | 7 187 | NO 188 | IBCocoaTouchFramework 189 | Non-opaque label 190 | 191 | Helvetica 192 | 17 193 | 16 194 | 195 | 196 | 1 197 | MCAwIDAAA 198 | 199 | 200 | 1 201 | 10 202 | 1 203 | 204 | 205 | 206 | 268 207 | {{195, 424}, {108, 23}} 208 | 209 | 210 | 211 | 212 | 1 213 | MC45NzI4MjYwODcgMC45NzI4MjYwODcgMC45NzI4MjYwODcAA 214 | 215 | YES 216 | 7 217 | NO 218 | IBCocoaTouchFramework 219 | Opaque label 220 | 221 | 222 | 223 | 1 224 | 10 225 | 1 226 | 227 | 228 | 229 | 268 230 | {{197, 386}, {94, 27}} 231 | 232 | 233 | 234 | NO 235 | IBCocoaTouchFramework 236 | 0 237 | 0 238 | YES 239 | 240 | 241 | 242 | 292 243 | {{20, 20}, {97, 31}} 244 | 245 | 246 | 247 | NO 248 | YES 249 | 250 | Accessibility Hint 251 | Accessibility Label 252 | 253 | 254 | IBCocoaTouchFramework 255 | 0 256 | Text Field 257 | 3 258 | Placeholder 259 | 260 | 3 261 | MAA 262 | 263 | 2 264 | 265 | 266 | 267 | Helvetica 268 | 12 269 | 16 270 | 271 | YES 272 | 17 273 | 274 | IBCocoaTouchFramework 275 | 276 | 277 | 278 | 279 | 292 280 | {{20, 66}, {207, 30}} 281 | 282 | 283 | 284 | NO 285 | IBCocoaTouchFramework 286 | 2 287 | 2 288 | 0 289 | 290 | YES 291 | First 292 | Second 293 | 294 | 295 | YES 296 | 297 | 298 | 299 | 300 | YES 301 | 302 | 303 | 304 | 305 | YES 306 | {0, 0} 307 | {0, 0} 308 | 309 | 310 | YES 311 | 312 | 313 | 314 | 315 | 316 | 317 | 1316 318 | 319 | {{149, 10}, {38, 36}} 320 | 321 | 322 | 323 | NO 324 | IBCocoaTouchFramework 325 | 0 326 | 0 327 | 3 328 | 329 | 330 | {{0, 20}, {320, 460}} 331 | 332 | 333 | 334 | 335 | 3 336 | MQA 337 | 338 | 339 | IBCocoaTouchFramework 340 | 341 | 342 | 343 | 1 344 | 1 345 | 346 | IBCocoaTouchFramework 347 | NO 348 | 349 | 350 | 351 | 292 352 | {320, 480} 353 | 354 | 1 355 | MSAxIDEAA 356 | 357 | NO 358 | NO 359 | 360 | IBCocoaTouchFramework 361 | YES 362 | 363 | 364 | 365 | 366 | YES 367 | 368 | 369 | delegate 370 | 371 | 372 | 373 | 4 374 | 375 | 376 | 377 | viewController 378 | 379 | 380 | 381 | 11 382 | 383 | 384 | 385 | window 386 | 387 | 388 | 389 | 14 390 | 391 | 392 | 393 | activityIndicator 394 | 395 | 396 | 397 | 27 398 | 399 | 400 | 401 | label 402 | 403 | 404 | 405 | 29 406 | 407 | 408 | 409 | sliderChanged: 410 | 411 | 412 | 13 413 | 414 | 31 415 | 416 | 417 | 418 | dataSource 419 | 420 | 421 | 422 | 35 423 | 424 | 425 | 426 | delegate 427 | 428 | 429 | 430 | 36 431 | 432 | 433 | 434 | buttonTapped: 435 | 436 | 437 | 7 438 | 439 | 39 440 | 441 | 442 | 443 | switchChanged: 444 | 445 | 446 | 13 447 | 448 | 40 449 | 450 | 451 | 452 | delegate 453 | 454 | 455 | 456 | 50 457 | 458 | 459 | 460 | 461 | YES 462 | 463 | 0 464 | 465 | 466 | 467 | 468 | 469 | -1 470 | 471 | 472 | File's Owner 473 | 474 | 475 | 3 476 | 477 | 478 | DCIntrospectDemo App Delegate 479 | 480 | 481 | -2 482 | 483 | 484 | 485 | 486 | 10 487 | 488 | 489 | YES 490 | 491 | 492 | 493 | 494 | 495 | 16 496 | 497 | 498 | YES 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 17 516 | 517 | 518 | YES 519 | 520 | 521 | 522 | 523 | 18 524 | 525 | 526 | 527 | 528 | 19 529 | 530 | 531 | 532 | 533 | 20 534 | 535 | 536 | 537 | 538 | 21 539 | 540 | 541 | 542 | 543 | 22 544 | 545 | 546 | 547 | 548 | 23 549 | 550 | 551 | 552 | 553 | 24 554 | 555 | 556 | 557 | 558 | 25 559 | 560 | 561 | 562 | 563 | 41 564 | 565 | 566 | 567 | 568 | 42 569 | 570 | 571 | 572 | 573 | 43 574 | 575 | 576 | 577 | 578 | 12 579 | 580 | 581 | 582 | 583 | 584 | 585 | YES 586 | 587 | YES 588 | -1.CustomClassName 589 | -2.CustomClassName 590 | 10.CustomClassName 591 | 10.IBEditorWindowLastContentRect 592 | 10.IBPluginDependency 593 | 12.IBEditorWindowLastContentRect 594 | 12.IBPluginDependency 595 | 16.IBPluginDependency 596 | 17.IBPluginDependency 597 | 18.IBPluginDependency 598 | 19.IBPluginDependency 599 | 20.IBPluginDependency 600 | 21.IBPluginDependency 601 | 22.IBPluginDependency 602 | 23.IBPluginDependency 603 | 24.IBPluginDependency 604 | 25.IBPluginDependency 605 | 3.CustomClassName 606 | 3.IBPluginDependency 607 | 41.IBPluginDependency 608 | 42.IBPluginDependency 609 | 43.IBPluginDependency 610 | 611 | 612 | YES 613 | UIApplication 614 | UIResponder 615 | DCIntrospectDemoViewController 616 | {{234, 376}, {320, 480}} 617 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 618 | {{525, 346}, {320, 480}} 619 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 620 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 621 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 622 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 623 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 624 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 625 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 626 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 627 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 628 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 629 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 630 | DCIntrospectDemoAppDelegate 631 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 632 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 633 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 634 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 635 | 636 | 637 | 638 | YES 639 | 640 | 641 | 642 | 643 | 644 | YES 645 | 646 | 647 | 648 | 649 | 49 650 | 651 | 652 | 653 | YES 654 | 655 | DCIntrospectDemoAppDelegate 656 | NSObject 657 | 658 | YES 659 | 660 | YES 661 | viewController 662 | window 663 | 664 | 665 | YES 666 | DCIntrospectDemoViewController 667 | UIWindow 668 | 669 | 670 | 671 | YES 672 | 673 | YES 674 | viewController 675 | window 676 | 677 | 678 | YES 679 | 680 | viewController 681 | DCIntrospectDemoViewController 682 | 683 | 684 | window 685 | UIWindow 686 | 687 | 688 | 689 | 690 | IBProjectSource 691 | ./Classes/DCIntrospectDemoAppDelegate.h 692 | 693 | 694 | 695 | DCIntrospectDemoViewController 696 | UIViewController 697 | 698 | YES 699 | 700 | YES 701 | buttonTapped: 702 | switchChanged: 703 | 704 | 705 | YES 706 | id 707 | id 708 | 709 | 710 | 711 | YES 712 | 713 | YES 714 | buttonTapped: 715 | switchChanged: 716 | 717 | 718 | YES 719 | 720 | buttonTapped: 721 | id 722 | 723 | 724 | switchChanged: 725 | id 726 | 727 | 728 | 729 | 730 | YES 731 | 732 | YES 733 | activityIndicator 734 | label 735 | 736 | 737 | YES 738 | UIActivityIndicatorView 739 | UILabel 740 | 741 | 742 | 743 | YES 744 | 745 | YES 746 | activityIndicator 747 | label 748 | 749 | 750 | YES 751 | 752 | activityIndicator 753 | UIActivityIndicatorView 754 | 755 | 756 | label 757 | UILabel 758 | 759 | 760 | 761 | 762 | IBProjectSource 763 | ./Classes/DCIntrospectDemoViewController.h 764 | 765 | 766 | 767 | 768 | 0 769 | IBCocoaTouchFramework 770 | 771 | com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS 772 | 773 | 774 | 775 | com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 776 | 777 | 778 | YES 779 | 3 780 | 781 | circle.png 782 | {70, 70} 783 | 784 | 521 785 | 786 | 787 | -------------------------------------------------------------------------------- /DCIntrospectDemo/DCIntrospectDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // DCIntrospectDemo 4 | // 5 | // Created by Domestic Cat on 29/04/11. 6 | // Copyright 2011 Domestic Cat Software. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, char *argv[]) 12 | { 13 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 14 | int retVal = UIApplicationMain(argc, argv, nil, nil); 15 | [pool release]; 16 | return retVal; 17 | } 18 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2011 by Patrick Richards domesticcatsoftware.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | DCIntrospect 2 | ============ 3 | 4 | Twitter: [@patr](http://twitter.com/patr) 5 | 6 | Our commercial apps: [domesticcat.com.au](http://domesticcat.com.au/apps) 7 | 8 | Introspect is small set of tools for iOS that aid in debugging user interfaces built with UIKit. It's especially useful for UI layouts that are dynamically created or can change during runtime, or for tuning performance by finding non-opaque views or views that are re-drawing unnecessarily. It's designed for use in the iPhone simulator, but can also be used on a device. 9 | 10 | 11 | ![Introspect Demo Image](http://domesticcat.com.au/projects/introspect/introspectdemo.png) 12 | 13 | 14 | It uses keyboard shortcuts to handle starting, ending and other commands. It can also be invoked via an app-wide `UIGestureRecognizer` if it is to be used on the device. 15 | 16 | Features: 17 | -------------- 18 | * Simple to setup and use 19 | * Controlled via app-wide keyboard commands 20 | * Highlighting of view frames 21 | * Displays a views origin & size, including distances to edges of main window 22 | * Move and resize view frames during runtime using shortcut keys 23 | * Logging of properties of a view, including subclass properties, actions and targets (see below for an example) 24 | * Logging of accessibility properties — useful for UI automation scripts 25 | * Manually call setNeedsDisplay, setNeedsLayout and reloadData (for UITableView) 26 | * Highlight all view outlines 27 | * Highlight all views that are non-opaque 28 | * Shows warning for views that are positioned on non-integer origins (will cause blurriness when drawn) 29 | * Print a views hierarchy to console (via private method `recursiveDescription`) to console 30 | 31 | Usage 32 | ----- 33 | 34 | Before you start make sure the `DEBUG` environment variable is set. DCIntrospect will not run without that set to prevent it being left in for production use. 35 | 36 | Add the `DCIntrospect` class files to your project, add the QuartzCore framework if needed. To start: 37 | 38 | [window makeKeyAndDisplay] 39 | 40 | // always call after makeKeyAndDisplay. 41 | #if TARGET_IPHONE_SIMULATOR 42 | [[DCIntrospect sharedIntrospector] start]; 43 | #endif 44 | 45 | The `#if` to target the simulator is not required but is a good idea to further prevent leaving it on in production code. 46 | 47 | Once setup, simply push the space bar to invoke the introspect or then start clicking on views to get info. You can also tap and drag around the interface. 48 | 49 | A a small demo app is included to test it out. 50 | 51 | Selected keyboard shortcuts 52 | ----------------------------------------- 53 | 54 | * Start/Stop: `spacebar` 55 | * Help: `?` 56 | * Print properties and actions of selected view to console: `p` 57 | * Print accessibility properties and actions of selected view to console: `a` 58 | * Toggle all view outlines: `o` 59 | * Toggle highlighting non-opaque views: `O` 60 | * Nudge view left, right, up & down: `4 6 8 2` (use the numeric pad) or `← → ↑ ↓` 61 | * Print out the selected views' new frame to console after nudge/resize: `0` 62 | * Print selected views recursive description to console: `v` 63 | 64 | Logging selected views properties 65 | ------------------------------------------------- 66 | 67 | Pushing `p` will log out the available properties about the selected view. DCIntrospect will try to make sense of the values it can and show more useful info. An example from a `UIButton`: 68 | 69 | ** UIRoundedRectButton : UIButton : UIControl : UIView : UIResponder : NSObject ** 70 | 71 | ** UIView properties ** 72 | tag: 1 73 | frame: {{21, 331}, {278, 37}} | bounds: {{0, 0}, {278, 37}} | center: {160, 349.5} 74 | transform: [1, 0, 0, 1, 0, 0] 75 | autoresizingMask: UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin 76 | autoresizesSubviews: YES 77 | contentMode: UIViewContentModeScaleToFill | contentStretch: {{0, 0}, {1, 1}} backgroundColor: nil 78 | alpha: 1.00 | opaque: NO | hidden: NO | clips to bounds: NO | 79 | clearsContextBeforeDrawing: YES 80 | userInteractionEnabled: YES | multipleTouchEnabled: NO 81 | gestureRecognizers: nil 82 | 83 | ** UIRoundedRectButton properties ** 84 | 85 | ** Targets & Actions ** 86 | target: action: buttonTapped: 87 | 88 | Customizing Key Bindings 89 | -------------------------------------- 90 | 91 | Edit the file `DCIntrospectSettings.h` to change key bindings. You might want to change the key bindings if your using a laptop/wireless keyboard for development. 92 | 93 | License 94 | ----------- 95 | 96 | Made available under the MIT License. 97 | 98 | Collaboration 99 | ------------- 100 | 101 | If you have any feature requests/bugfixes etc. feel free to help out and send a pull request, or create a new issue. 102 | --------------------------------------------------------------------------------