├── README.md ├── NSLayoutConstraint+Debugging.h └── NSLayoutConstraint+Debugging.m /README.md: -------------------------------------------------------------------------------- 1 | ## Auto Layout debugging helper 2 | 3 | This category is sample code for [objc.io issue #3](http://www.objc.io/issue-3). 4 | 5 | It modifies the description of constraints to include information about where they were created. For further information, please refer to the article [Advanced Auto Layout Toolbox](http://www.objc.io/issue-3/advanced-auto-layout-toolbox.html). -------------------------------------------------------------------------------- /NSLayoutConstraint+Debugging.h: -------------------------------------------------------------------------------- 1 | // 2 | // objc.io #3 sample code (http://www.objc.io/issue-3) 3 | // Advanced Auto Layout Toolbox 4 | // 5 | 6 | /// 7 | /// This will add the call stack symbols to any NSLayoutConstraint that's added to a view. 8 | /// 9 | /// This is disabled by default. In Debug builds, set the environment variable @c ObjcioLayoutConstraintsDebugging 10 | /// 11 | 12 | @interface NSLayoutConstraint (ObjcioLayoutConstraintsDebugging) 13 | 14 | @property (readonly, nonatomic, copy) NSArray *creationCallStackSymbols; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /NSLayoutConstraint+Debugging.m: -------------------------------------------------------------------------------- 1 | // 2 | // objc.io #3 sample code (http://www.objc.io/issue-3) 3 | // Advanced Auto Layout Toolbox 4 | // 5 | 6 | #import "NSLayoutConstraint+Debugging.h" 7 | 8 | #if DEBUG 9 | 10 | #import 11 | 12 | static BOOL ObjcioLayoutConstraintsDebuggingEnabled(void) 13 | { 14 | static BOOL enabled; 15 | static dispatch_once_t onceToken; 16 | dispatch_once(&onceToken, ^{ 17 | enabled = ([[NSProcessInfo processInfo] environment][@"ObjcioLayoutConstraintsDebugging"] != nil); 18 | }); 19 | return enabled; 20 | } 21 | 22 | 23 | // C.f. 24 | static void MethodSwizzle(Class c, SEL origSEL, SEL overrideSEL) 25 | { 26 | Method origMethod = class_getInstanceMethod(c, origSEL); 27 | Method overrideMethod = class_getInstanceMethod(c, overrideSEL); 28 | if(class_addMethod(c, origSEL, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod))) { 29 | class_replaceMethod(c, overrideSEL, method_getImplementation(origMethod), method_getTypeEncoding(origMethod)); 30 | } else { 31 | method_exchangeImplementations(origMethod, overrideMethod); 32 | } 33 | } 34 | 35 | static int const ObjcioLayoutConstraintDebuggingShort; 36 | static int const ObjcioLayoutConstraintDebuggingCallStackSymbols; 37 | 38 | @implementation NSView (ObjcioLayoutConstraintsDebugging) 39 | 40 | 41 | // We're doing some nasty method swizzling here to make debugging of constraints easier. 42 | + (void)load; 43 | { 44 | if (ObjcioLayoutConstraintsDebuggingEnabled()) { 45 | MethodSwizzle(self, @selector(addConstraint:), @selector(objcioOverride_addConstraint:)); 46 | MethodSwizzle(self, @selector(addConstraints:), @selector(objcioOverride_addConstraints:)); 47 | } 48 | } 49 | 50 | - (void)objcioOverride_addConstraint:(NSLayoutConstraint *)constraint 51 | { 52 | AddTracebackToConstraints(@[constraint]); 53 | [self objcioOverride_addConstraint:constraint]; 54 | } 55 | 56 | - (void)objcioOverride_addConstraints:(NSArray *)constraints 57 | { 58 | AddTracebackToConstraints(constraints); 59 | [self objcioOverride_addConstraints:constraints]; 60 | } 61 | 62 | static void AddTracebackToConstraints(NSArray *constraints) 63 | { 64 | NSArray *a = [NSThread callStackSymbols]; 65 | NSString *symbol = nil; 66 | if (2 < [a count]) { 67 | NSString *line = a[2]; 68 | // Format is 69 | // 1 2 3 4 5 70 | // 012345678901234567890123456789012345678901234567890123456789 71 | // 8 MyCoolApp 0x0000000100029809 -[MyViewController loadView] + 99 72 | // 73 | // Don't add if this wasn't called from "MyCoolApp": 74 | if (59 <= [line length]) { 75 | line = [line substringFromIndex:4]; 76 | if ([line hasPrefix:@"My"]) { 77 | symbol = [line substringFromIndex:59 - 4]; 78 | } 79 | } 80 | } 81 | for (NSLayoutConstraint *c in constraints) { 82 | if (symbol != nil) { 83 | objc_setAssociatedObject(c, &ObjcioLayoutConstraintDebuggingShort, symbol, OBJC_ASSOCIATION_COPY_NONATOMIC); 84 | } 85 | objc_setAssociatedObject(c, &ObjcioLayoutConstraintDebuggingCallStackSymbols, a, OBJC_ASSOCIATION_COPY_NONATOMIC); 86 | } 87 | } 88 | 89 | @end 90 | 91 | 92 | 93 | @implementation NSLayoutConstraint (ObjcioLayoutConstraintsDebugging) 94 | 95 | // We're doing some nasty method swizzling here to make debugging of constraints easier. 96 | + (void)load; 97 | { 98 | if (ObjcioLayoutConstraintsDebuggingEnabled()) { 99 | MethodSwizzle(self, @selector(description), @selector(objcioOverride_description)); 100 | } 101 | } 102 | 103 | - (NSString *)objcioOverride_description 104 | { 105 | // call through to the original, really 106 | NSString *description = [self objcioOverride_description]; 107 | NSString *objcioTag = objc_getAssociatedObject(self, &ObjcioLayoutConstraintDebuggingShort); 108 | if (objcioTag == nil) { 109 | return description; 110 | } 111 | return [description stringByAppendingFormat:@" %@", objcioTag]; 112 | } 113 | 114 | - (NSArray *)creationCallStackSymbols 115 | { 116 | return objc_getAssociatedObject(self, &ObjcioLayoutConstraintDebuggingCallStackSymbols); 117 | } 118 | 119 | @end 120 | 121 | #else 122 | 123 | @implementation NSLayoutConstraint (ObjcioLayoutConstraintsDebugging) 124 | 125 | - (NSArray *)creationCallStackSymbols 126 | { 127 | return nil; 128 | } 129 | 130 | @end 131 | 132 | #endif 133 | --------------------------------------------------------------------------------