Last active
May 27, 2024 03:59
-
-
Save bfahey/c4eb72f264808f5e5dcb39e47b06def2 to your computer and use it in GitHub Desktop.
Debug your app with Apple's private UIDebuggingInformationOverlay. Supports iOS 13-17+.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| @import ObjectiveC.runtime; | |
| @import UIKit; | |
| @interface DebuggingOverlay: NSObject | |
| + (void)show API_AVAILABLE(ios(13)); | |
| @end | |
| @implementation DebuggingOverlay | |
| /// Shows the `UIDebuggingInformationOverlay`. | |
| + (void)show { | |
| id debugOverlayClass = NSClassFromString(@"UIDebuggingInformationOverlay"); | |
| // Replace the overlay's init method with UIWindow's. | |
| static dispatch_once_t onceToken; | |
| dispatch_once(&onceToken, ^{ | |
| Method newInit = class_getInstanceMethod([UIWindow class], @selector(init)); | |
| IMP newInitImp = method_getImplementation(newInit); | |
| class_replaceMethod(debugOverlayClass, @selector(init), newInitImp, nil); | |
| }); | |
| // [UIDebuggingInformationOverlay prepareDebuggingOverlay] calls the _UIGetDebuggingOverlayEnabled | |
| // which checks if the device is internal to Apple. Bypass the check by invoking a tap | |
| // gesture. | |
| UIGestureRecognizer *tapGesture = [[UIGestureRecognizer alloc] init]; | |
| tapGesture.state = UIGestureRecognizerStateEnded; | |
| #pragma clang diagnostic push | |
| #pragma clang diagnostic ignored "-Warc-performSelector-leaks" | |
| id handlerClass = NSClassFromString(@"UIDebuggingInformationOverlayInvokeGestureHandler"); | |
| id handler = [handlerClass performSelector:NSSelectorFromString(@"mainHandler")]; | |
| [handler performSelector:NSSelectorFromString(@"_handleActivationGesture:") withObject:tapGesture]; | |
| // If the app supports multiple scenes the debug menu needs to be added as a subview. | |
| if ([NSBundle.mainBundle objectForInfoDictionaryKey:@"UIApplicationSceneManifest"]) { | |
| UIView *debugOverlayView = [debugOverlayClass performSelector:NSSelectorFromString(@"overlay")]; | |
| debugOverlayView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; | |
| [[self getKeyWindow] addSubview:debugOverlayView]; | |
| } | |
| #pragma clang diagnostic pop | |
| } | |
| /// Returns the app's key window. | |
| + (UIWindow * _Nullable)getKeyWindow { | |
| for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) { | |
| if (![scene isKindOfClass:[UIWindowScene class]]) { continue; } | |
| for (UIWindow *window in ((UIWindowScene *)scene).windows) { | |
| if (window.isKeyWindow) { | |
| return window; | |
| } | |
| } | |
| } | |
| return nil; | |
| } | |
| @end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment