Is it possible to catch autolayout constraint ambiguities in production – the equivalent of a UIViewAlertForUnsatisfiableConstraints
breakpoint but for production apps?
My goal would be to add a global handler that would report such errors to a logging system.
The symbol UIViewAlertForUnsatisfiableConstraints
actually is a function :
_UIViewAlertForUnsatisfiableConstraints(NSLayoutConstraint* unsatisfiableConstraint, NSArray<NSLayoutConstraint*>* allConstraints)
.
It is private, so you can't replace it.
But it is called from private method -[UIView engine:willBreakConstraint:dueToMutuallyExclusiveConstraints:]
, which can be swizzled. This method has approximately this content:
void -[UIView engine:willBreakConstraint:dueToMutuallyExclusiveConstraints:] {
if ([self _isUnsatisfiableConstraintsLoggingSuspended]) {
[self _recordConstraintBrokenWhileUnsatisfiableConstraintsLoggingSuspended:$arg4]; // add constraint to some pool
}
else {
if (__UIConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints) {
// print something in os_log
}
else {
_UIViewAlertForUnsatisfiableConstraints($arg4, $arg5);
}
}
}
If I understand correctly from this article, __UIConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints
will always return NO on iOS, so all you need to do is to check private bool property called _isUnsatisfiableConstraintsLoggingSuspended
and call original method then.
This is result code example:
#import <objc/runtime.h>
void SwizzleInstanceMethod(Class classToSwizzle, SEL origSEL, Class myClass, SEL newSEL) {
Method methodToSwizzle = class_getInstanceMethod(classToSwizzle, origSEL);
Method myMethod = class_getInstanceMethod(myClass, newSEL);
class_replaceMethod(classToSwizzle, newSEL, method_getImplementation(methodToSwizzle), method_getTypeEncoding(methodToSwizzle));
class_replaceMethod(classToSwizzle, origSEL, method_getImplementation(myMethod), method_getTypeEncoding(myMethod));
}
@interface InterceptUnsatisfiableConstraints : NSObject
@end
@implementation InterceptUnsatisfiableConstraints
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL willBreakConstantSel = NSSelectorFromString(@"engine:willBreakConstraint:dueToMutuallyExclusiveConstraints:");
SwizzleInstanceMethod([UIView class], willBreakConstantSel, [self class], @selector(pr_engine:willBreakConstraint:dueToMutuallyExclusiveConstraints:));
});
}
- (void)pr_engine:(id)engine willBreakConstraint:(NSLayoutConstraint*)constraint dueToMutuallyExclusiveConstraints:(NSArray<NSLayoutConstraint*>*)layoutConstraints {
BOOL constrainsLoggingSuspended = [[self valueForKey:@"_isUnsatisfiableConstraintsLoggingSuspended"] boolValue];
if (!constrainsLoggingSuspended) {
NSLog(@"_UIViewAlertForUnsatisfiableConstraints would be called on next line, log this event");
}
[self pr_engine:engine willBreakConstraint:constraint dueToMutuallyExclusiveConstraints:layoutConstraints];
}
@end
It works on iOS 8.2/9/10 (it doesn't work in iOS 8.1, so be careful), but I can't give any guarantee. Also, it catches constraint problems in system components, such as keyboard/video player/etc. This code is fragile (it can lead to crash on any system version update, parameters change, etc) and I will not recommend to use it in production (guess that it will not pass even automated review process). You have the last word, but you are warned.
However I think that you can use it in builds for internal/external testers to fix bugs in autolayout before production.
Noticed that you are using swift: you can add this code to your swift project with bridging header file.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With