appjam 0.1.8.9 → 0.1.8.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. data/lib/appjam/generators/templates/blank/EiffelApplication.xcodeproj/project.pbxproj +114 -0
  2. data/lib/appjam/generators/templates/blank/EiffelApplication.xcodeproj/project.xcworkspace/xcuserdata/eiffel.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  3. data/lib/appjam/generators/templates/blank/EiffelApplication/AppDelegate.m.tt +13 -1
  4. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/components/MyLauncher/CustomBadge.h +52 -0
  5. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/components/MyLauncher/CustomBadge.m +241 -0
  6. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/components/MyLauncher/MyLauncherItem.h +58 -0
  7. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/components/MyLauncher/MyLauncherItem.m +227 -0
  8. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/components/MyLauncher/MyLauncherPageControl.h +37 -0
  9. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/components/MyLauncher/MyLauncherPageControl.m +117 -0
  10. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/components/MyLauncher/MyLauncherScrollView.h +23 -0
  11. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/components/MyLauncher/MyLauncherScrollView.m +42 -0
  12. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/components/MyLauncher/MyLauncherView.h +70 -0
  13. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/components/MyLauncher/MyLauncherView.m +830 -0
  14. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/components/MyLauncher/MyLauncherViewController.h +38 -0
  15. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/components/MyLauncher/MyLauncherViewController.m +302 -0
  16. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/AESCrypt-ObjC/AESCrypt.h +37 -0
  17. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/AESCrypt-ObjC/AESCrypt.m +50 -0
  18. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/AESCrypt-ObjC/NSData+Base64.h +17 -0
  19. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/AESCrypt-ObjC/NSData+Base64.m +110 -0
  20. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/AESCrypt-ObjC/NSData+CommonCrypto.h +112 -0
  21. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/AESCrypt-ObjC/NSData+CommonCrypto.m +546 -0
  22. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/AESCrypt-ObjC/NSString+Base64.h +15 -0
  23. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/AESCrypt-ObjC/NSString+Base64.m +82 -0
  24. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/DCIntrospect/DCCrossHairView.h +16 -0
  25. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/DCIntrospect/DCCrossHairView.m +43 -0
  26. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/DCIntrospect/DCFrameView.h +55 -0
  27. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/DCIntrospect/DCFrameView.m +234 -0
  28. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/DCIntrospect/DCIntrospect.h +155 -0
  29. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/DCIntrospect/DCIntrospect.m +1654 -0
  30. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/DCIntrospect/DCIntrospectSettings.h +56 -0
  31. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/DCIntrospect/DCStatusBarOverlay.h +34 -0
  32. data/lib/appjam/generators/templates/blank/EiffelApplication/libs/toolkit/DCIntrospect/DCStatusBarOverlay.m +107 -0
  33. data/lib/appjam/version.rb +1 -1
  34. metadata +32 -3
@@ -0,0 +1,155 @@
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 <objc/runtime.h>
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 <DCFrameViewDelegate, UITextViewDelegate, UIWebViewDelegate>
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
@@ -0,0 +1,1654 @@
1
+ //
2
+ // DCIntrospect.m
3
+ //
4
+ // Created by Domestic Cat on 29/04/11.
5
+ //
6
+
7
+ #import "DCIntrospect.h"
8
+ #import <dlfcn.h>
9
+
10
+ #include <assert.h>
11
+ #include <stdbool.h>
12
+ #include <sys/types.h>
13
+ #include <unistd.h>
14
+ #include <sys/sysctl.h>
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:@"<html>"];
1310
+ [helpString appendString:@"<head><style>"];
1311
+ [helpString appendString:@"body { background-color:rgba(0, 0, 0, 0.0); font:10pt helvetica; line-height: 15px margin-left:5px; margin-right:5px; margin-top:20px; color:rgb(240, 240, 240); } a { color:#45e0fe; font-weight:bold; } h1 { width:100%; font-size:14pt; border-bottom: 1px solid white; margin-top:22px; } h2 { font-size:11pt; margin-left:3px; margin-bottom:2px; } .name { margin-left:7px; } .key { float:right; margin-right:7px; } .key, .code { font-family:Courier; font-weight:bold; color:#CE8B39; } .spacer { height:10px; } p { margin-left: 7px; margin-right: 7px; }"];
1312
+
1313
+ if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
1314
+ [helpString appendString:@"body { font-size:11pt; width:500px; margin:0 auto; }"];
1315
+
1316
+ [helpString appendString:@"</style></head><body><h1>DCIntrospect</h1>"];
1317
+ [helpString appendString:@"<p>Created by <a href='http://domesticcat.com.au'>Domestic Cat Software</a> 2011.</p>"];
1318
+ [helpString appendString:@"<p>Twitter: <a href='http://twitter.com/patr'>@patr</a></p>"];
1319
+ [helpString appendString:@"<p>More info and full documentation: <a href='http://domesticcat.com.au/projects/introspect'>domesticcat.com.au/projects/introspect</a></p>"];
1320
+ [helpString appendString:@"<p>GitHub project: <a href='https://github.com/domesticcatsoftware/dcintrospect'>github.com/domesticcatsoftware/dcintrospect/</a></p>"];
1321
+
1322
+ [helpString appendString:@"<div class='bindings'><h1>Key Bindings</h1>"];
1323
+ [helpString appendString:@"<p>Edit DCIntrospectSettings.h to change key bindings.</p>"];
1324
+
1325
+ [helpString appendString:@"<h2>General</h2>"];
1326
+
1327
+ [helpString appendFormat:@"<div><span class='name'>Invoke Introspector</span><div class='key'>%@</div></div>", ([kDCIntrospectKeysInvoke isEqualToString:@" "]) ? @"spacebar" : kDCIntrospectKeysInvoke];
1328
+ [helpString appendFormat:@"<div><span class='name'>Toggle View Outlines</span><div class='key'>%@</div></div>", kDCIntrospectKeysToggleViewOutlines];
1329
+ [helpString appendFormat:@"<div><span class='name'>Toggle Highlighting Non-Opaque Views</span><div class='key'>%@</div></div>", kDCIntrospectKeysToggleNonOpaqueViews];
1330
+ [helpString appendFormat:@"<div><span class='name'>Toggle Help</span><div class='key'>%@</div></div>", kDCIntrospectKeysToggleHelp];
1331
+ [helpString appendFormat:@"<div><span class='name'>Toggle flash on <span class='code'>drawRect:</span> (see below)</span><div class='key'>%@</div></div>", kDCIntrospectKeysToggleFlashViewRedraws];
1332
+ [helpString appendFormat:@"<div><span class='name'>Toggle coordinates</span><div class='key'>%@</div></div>", kDCIntrospectKeysToggleShowCoordinates];
1333
+ [helpString appendString:@"<div class='spacer'></div>"];
1334
+
1335
+ [helpString appendString:@"<h2>When a view is selected</h2>"];
1336
+ [helpString appendFormat:@"<div><span class='name'>Log Properties</span><div class='key'>%@</div></div>", kDCIntrospectKeysLogProperties];
1337
+ [helpString appendFormat:@"<div><span class='name'>Log Accessibility Properties</span><div class='key'>%@</div></div>", kDCIntrospectKeysLogAccessibilityProperties];
1338
+ [helpString appendFormat:@"<div><span class='name'>Log Recursive Description for View</span><div class='key'>%@</div></div>", kDCIntrospectKeysLogViewRecursive];
1339
+ [helpString appendFormat:@"<div><span class='name'>Enter GDB</span><div class='key'>%@</div></div>", kDCIntrospectKeysEnterGDB];
1340
+ [helpString appendFormat:@"<div><span class='name'>Move up in view hierarchy</span><div class='key'>%@</div></div>", ([kDCIntrospectKeysMoveUpInViewHierarchy isEqualToString:@""]) ? @"page up" : kDCIntrospectKeysMoveUpInViewHierarchy];
1341
+ [helpString appendFormat:@"<div><span class='name'>Move back down in view hierarchy</span><div class='key'>%@</div></div>", ([kDCIntrospectKeysMoveBackInViewHierarchy isEqualToString:@""]) ? @"page down" : kDCIntrospectKeysMoveBackInViewHierarchy];
1342
+ [helpString appendString:@"<div class='spacer'></div>"];
1343
+
1344
+ [helpString appendFormat:@"<div><span class='name'>Nudge Left</span><div class='key'>\uE235 / %@</div></div>", kDCIntrospectKeysNudgeViewLeft];
1345
+ [helpString appendFormat:@"<div><span class='name'>Nudge Right</span><div class='key'>\uE234 / %@</div></div>", kDCIntrospectKeysNudgeViewRight];
1346
+ [helpString appendFormat:@"<div><span class='name'>Nudge Up</span><div class='key'>\uE232 / %@</div></div>", kDCIntrospectKeysNudgeViewUp];
1347
+ [helpString appendFormat:@"<div><span class='name'>Nudge Down</span><div class='key'>\uE233 / %@</div></div>", kDCIntrospectKeysNudgeViewDown];
1348
+ [helpString appendFormat:@"<div><span class='name'>Center in Superview</span><div class='key'>%@</div></div>", kDCIntrospectKeysCenterInSuperview];
1349
+ [helpString appendFormat:@"<div><span class='name'>Increase Width</span><div class='key'>alt + \uE234 / %@</div></div>", kDCIntrospectKeysIncreaseWidth];
1350
+ [helpString appendFormat:@"<div><span class='name'>Decrease Width</span><div class='key'>alt + \uE235 / %@</div></div>", kDCIntrospectKeysDecreaseWidth];
1351
+ [helpString appendFormat:@"<div><span class='name'>Increase Height</span><div class='key'>alt + \uE233 / %@</div></div>", kDCIntrospectKeysIncreaseHeight];
1352
+ [helpString appendFormat:@"<div><span class='name'>Decrease Height</span><div class='key'>alt + \uE232 / %@</div></div>", kDCIntrospectKeysDecreaseHeight];
1353
+ [helpString appendFormat:@"<div><span class='name'>Increase Alpha</span><div class='key'>%@</div></div>", kDCIntrospectKeysIncreaseViewAlpha];
1354
+ [helpString appendFormat:@"<div><span class='name'>Decrease Alpha</span><div class='key'>%@</div></div>", kDCIntrospectKeysDecreaseViewAlpha];
1355
+ [helpString appendFormat:@"<div><span class='name'>Log view code</span><div class='key'>%@</div></div>", kDCIntrospectKeysLogCodeForCurrentViewChanges];
1356
+ [helpString appendString:@"<div class='spacer'></div>"];
1357
+
1358
+ [helpString appendFormat:@"<div><span class='name'>Call setNeedsDisplay</span><div class='key'>%@</div></div>", kDCIntrospectKeysSetNeedsDisplay];
1359
+ [helpString appendFormat:@"<div><span class='name'>Call setNeedsLayout</span><div class='key'>%@</div></div>", kDCIntrospectKeysSetNeedsLayout];
1360
+ [helpString appendFormat:@"<div><span class='name'>Call reloadData (UITableView only)</span><div class='key'>%@</div></div>", kDCIntrospectKeysReloadData];
1361
+ [helpString appendString:@"</div>"];
1362
+
1363
+ [helpString appendFormat:@"<h1>GDB</h1><p>Push <span class='code'>%@</span> (backtick) to jump into GDB. The currently selected view will be available as a variable named 'view'.</p>", kDCIntrospectKeysEnterGDB];
1364
+
1365
+ [helpString appendFormat:@"<h1>Flash on <span class='code'>drawRect:</span> calls</h1><p>To implement, call <span class='code'>[[DCIntrospect sharedIntrospector] flashRect:inView:]</span> inside the <span class='code'>drawRect:</span> method of any view you want to track.</p><p>When Flash on <span class='code'>drawRect:</span> is toggled on (binding: <span class='code'>%@</span>) the view will flash whenever <span class='code'>drawRect:</span> is called.</p>", kDCIntrospectKeysToggleFlashViewRedraws];
1366
+
1367
+ [helpString appendFormat:@"<h1>Naming objects & logging code</h1><p>By providing names for objects using <span class='code'>setName:forObject:accessedWithSelf:</span>, that name will be shown in the status bar instead of the class of the view.</p><p>This is also used when logging view code (binding: <span class='code'>%@</span>). Logging view code prints formatted code to the console for properties that have been changed.</p><p>For example, if you resize/move a view using the nudge keys, logging the view code will print <span class='code'>view.frame = CGRectMake(50.0 ..etc);</span> to the console. If a name is provided then <span class='code'>view</span> is replaced by the name.</p>", kDCIntrospectKeysLogCodeForCurrentViewChanges];
1368
+
1369
+ [helpString appendString:@"<h1>License</h1><p>DCIntrospect is made available under the <a href='http://en.wikipedia.org/wiki/MIT_License'>MIT license</a>.</p>"];
1370
+
1371
+ [helpString appendString:@"<h2 style='text-align:center;'><a href='http://close'>Close Help</h2>"];
1372
+ [helpString appendString:@"<div class='spacer'></div>"];
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