appjam 0.1.8.9 → 0.1.8.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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