背景介绍
iOS系统从9.0之后就加入了悬浮窗调试小工具来帮助开发者调试UI,很遗憾的是,这个是一个非公开的功能,苹果没有公开它的头文件。(私有API传送门)当然私有API没有阻挡住我们使用这么酷炫的小工具。如何使用可以看看前段时间笔者写过一片文章《iOS自带悬浮窗调试工具使用详解》。可是好景不长,在iOS11中这个小工具没法用了。最近想用这个系统自带的悬浮窗工具来调试UI,毕竟是接入成本最小UI调试工具,于是看到了国外大神的这篇文章 《Swizzling in iOS 11 with UIDebuggingInformationOverlay》。
原因
国外大神的文章很长,详细介绍了他是如何让悬浮窗调试工具重现在iOS11上的。文章具体内容这里就不展开了,感兴趣的可以去看看他的文章。文章主要内容:
iOS9 & 10 上 -[UIDebuggingInformationOverlay init] 和 [UIDebuggingInformationOverlay prepareDebuggingOverlay] 是能正常工作的。在iOS11上,上面这两个方法被苹果做了限制,只有苹果内部设备才可以正常使用。对这两个方法逆向后的代码如下:
@implementation UIDebuggingInformationOverlay
- (instancetype)init {
static BOOL overlayEnabled = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
overlayEnabled = UIDebuggingOverlayIsEnabled();
});
if (!overlayEnabled) {
return nil;
}
if (self = [super init]) {
[self _setWindowControlsStatusBarOrientation:NO];
}
return self;
}
+ (void)prepareDebuggingOverlay {
if (_UIGetDebuggingOverlayEnabled()) {
id handler = [UIDebuggingInformationOverlayInvokeGestureHandler mainHandler];
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:handler action:@selector(_handleActivationGesture:)];
[tapGesture setNumberOfTouchesRequired:2];
[tapGesture setNumberOfTapsRequired:1];
[tapGesture setDelegate:handler];
UIView *statusBarWindow = [UIApp statusBarWindow];
[statusBarWindow addGestureRecognizer:tapGesture];
}
}
@end
可以很清晰的看到,苹果用UIDebuggingOverlayIsEnabled() 对UIDebuggingInformationOverlay的初始化方法做了检测,如果不是内部设备就返回nil,同时对prepareDebuggingOverlay方法也做了检测。
既然我们都知道了方法内容,我们绕过这两个检查方法不就OK了?对的,使用Methond Swizzling 替换这两个OC的方法就好了。
国外大神也给出了一个解决方案,替换上面的两个OC方法,但是其中prepareDebuggingOverlay中添加了汇编代码,并且给出的汇编代码只支持x86_64的cpu。笔者在这个基础上重写了prepareDebuggingOverlay,发现也可以work。代码如下:
@interface UIWindow (PrivateMethods)
- (void)_setWindowControlsStatusBarOrientation:(BOOL)orientation;
@end
@interface FakeWindowClass : UIWindow
@end
@implementation FakeWindowClass
- (instancetype)initSwizzled {
self = [super init];
if (self) {
[self _setWindowControlsStatusBarOrientation:NO];
}
return self;
}
@end
@implementation NSObject (UIDebuggingInformationOverlayEnable)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class cls = NSClassFromString(@"UIDebuggingInformationOverlay");
[FakeWindowClass swizzleSelector:@selector(init) newSelector:@selector(initSwizzled) forClass:cls isClassMethod:NO];
[self swizzleSelector:@selector(prepareDebuggingOverlay) newSelector:@selector(prepareDebuggingOverlaySwizzled) forClass:cls isClassMethod:YES];
});
}
+ (void)swizzleSelector:(SEL)originalSelector newSelector:(SEL)swizzledSelector forClass:(Class)class isClassMethod:(BOOL)isClassMethod {
Method originalMethod = NULL;
Method swizzledMethod = NULL;
if (isClassMethod) {
originalMethod = class_getClassMethod(class, originalSelector);
swizzledMethod = class_getClassMethod([self class], swizzledSelector);
} else {
originalMethod = class_getInstanceMethod(class, originalSelector);
swizzledMethod = class_getInstanceMethod([self class], swizzledSelector);
}
method_exchangeImplementations(originalMethod, swizzledMethod);
}
+ (void)prepareDebuggingOverlaySwizzled {
id overlayClass = NSClassFromString(@"UIDebuggingInformationOverlayInvokeGestureHandler");
id handler = [overlayClass performSelector:NSSelectorFromString(@"mainHandler")];
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:handler action:@selector(_handleActivationGesture:)];
tapGesture.numberOfTouchesRequired = 2;
tapGesture.numberOfTapsRequired = 1;
tapGesture.delegate = handler;
UIView *statusBarWindow = [[UIApplication sharedApplication] valueForKey:@"statusBarWindow"];
[statusBarWindow addGestureRecognizer:tapGesture];
}
@end
结尾
将上面的代码放在一个文件里,引入到我们的项目中就可以在iOS11上使用苹果自带的悬浮窗UI调试工具了。这里上传了这个文件UIDebuggingTool,方便大家。笔者只测试了iOS11.0.1,欢迎大家帮忙测试下其他系统的情况并修改这个小工具。
Previous git命令行工作环境配置
Next