├── .gitignore ├── Example-ObjC ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── ViewController.h ├── ViewController.m └── main.m ├── Example-Swift ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist └── ViewController.swift ├── InAppViewDebugger.podspec ├── InAppViewDebugger.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── xcshareddata │ └── xcschemes │ │ ├── Example-ObjC.xcscheme │ │ ├── Example-Swift.xcscheme │ │ └── InAppViewDebugger.xcscheme └── xcuserdata │ └── indragie.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── InAppViewDebugger ├── Assets.xcassets │ ├── Contents.json │ ├── DisclosureIndicator.imageset │ │ ├── Contents.json │ │ ├── Disclosure Indicator.png │ │ ├── Disclosure Indicator@2x.png │ │ └── Disclosure Indicator@3x.png │ ├── RangeSliderFill.imageset │ │ ├── Contents.json │ │ ├── fill.png │ │ ├── fill@2x.png │ │ └── fill@3x.png │ ├── RangeSliderLeftHandle.imageset │ │ ├── Contents.json │ │ ├── left_handle.png │ │ ├── left_handle@2x.png │ │ └── left_handle@3x.png │ ├── RangeSliderRightHandle.imageset │ │ ├── Contents.json │ │ ├── right_handle.png │ │ ├── right_handle@2x.png │ │ └── right_handle@3x.png │ └── RangeSliderTrack.imageset │ │ ├── Contents.json │ │ ├── track.png │ │ ├── track@2x.png │ │ └── track@3x.png ├── BUILD ├── Configuration.swift ├── Element.swift ├── HierarchyTableViewCell.swift ├── HierarchyTableViewController.swift ├── HierarchyViewConfiguration.swift ├── InAppViewDebugger.h ├── InAppViewDebugger.swift ├── Info.plist ├── ParallelLineView.swift ├── RangeSlider.swift ├── Snapshot.swift ├── SnapshotActionSheetUtils.swift ├── SnapshotView.swift ├── SnapshotViewConfiguration.swift ├── SnapshotViewController.swift ├── TreeTableViewDataSource.swift ├── VIewDebuggerViewController.swift ├── ViewControllerUtils.swift └── ViewElement.swift ├── LICENSE ├── Package.swift ├── README.md ├── WORKSPACE └── docs ├── Classes.html ├── Classes ├── Configuration.html ├── ElementLabel.html ├── ElementLabel │ └── Classification.html ├── HierarchyViewConfiguration.html ├── InAppViewDebugger.html ├── Snapshot.html ├── SnapshotViewConfiguration.html ├── SnapshotViewConfiguration │ └── HeaderAttributes.html └── ViewElement.html ├── Protocols.html ├── Protocols └── Element.html ├── badge.svg ├── css ├── highlight.css └── jazzy.css ├── docsets ├── InAppViewDebugger.docset │ └── Contents │ │ ├── Info.plist │ │ └── Resources │ │ ├── Documents │ │ ├── Classes.html │ │ ├── Classes │ │ │ ├── Configuration.html │ │ │ ├── ElementLabel.html │ │ │ ├── ElementLabel │ │ │ │ └── Classification.html │ │ │ ├── HierarchyViewConfiguration.html │ │ │ ├── InAppViewDebugger.html │ │ │ ├── Snapshot.html │ │ │ ├── SnapshotViewConfiguration.html │ │ │ ├── SnapshotViewConfiguration │ │ │ │ └── HeaderAttributes.html │ │ │ └── ViewElement.html │ │ ├── Protocols.html │ │ ├── Protocols │ │ │ └── Element.html │ │ ├── css │ │ │ ├── highlight.css │ │ │ └── jazzy.css │ │ ├── img │ │ │ ├── carat.png │ │ │ ├── dash.png │ │ │ ├── gh.png │ │ │ └── spinner.gif │ │ ├── index.html │ │ ├── js │ │ │ ├── jazzy.js │ │ │ ├── jazzy.search.js │ │ │ ├── jquery.min.js │ │ │ ├── lunr.min.js │ │ │ └── typeahead.jquery.js │ │ └── search.json │ │ └── docSet.dsidx └── InAppViewDebugger.tgz ├── img ├── borders.gif ├── carat.png ├── dash.png ├── distance.gif ├── focus.gif ├── gh.png ├── headers.gif ├── iphone1.png ├── iphone2.png ├── main.png ├── slicing.gif └── spinner.gif ├── index.html ├── js ├── jazzy.js ├── jazzy.search.js ├── jquery.min.js ├── lunr.min.js └── typeahead.jquery.js ├── search.json └── undocumented.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | # 51 | # Add this line if you want to avoid checking in source code from the Xcode workspace 52 | # *.xcworkspace 53 | 54 | # Carthage 55 | # 56 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 57 | # Carthage/Checkouts 58 | 59 | Carthage/Build 60 | 61 | # fastlane 62 | # 63 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 64 | # screenshots whenever they are needed. 65 | # For more information about the recommended setup visit: 66 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 67 | 68 | fastlane/report.xml 69 | fastlane/Preview.html 70 | fastlane/screenshots/**/*.png 71 | fastlane/test_output 72 | 73 | # Code Injection 74 | # 75 | # After new code Injection tools there's a generated folder /iOSInjectionProject 76 | # https://github.com/johnno1962/injectionforxcode 77 | 78 | iOSInjectionProject/ -------------------------------------------------------------------------------- /Example-ObjC/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // Example-ObjC 4 | // 5 | // Created by Indragie Karunaratne on 4/20/19. 6 | // Copyright © 2019 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /Example-ObjC/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // Example-ObjC 4 | // 5 | // Created by Indragie Karunaratne on 4/20/19. 6 | // Copyright © 2019 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @implementation AppDelegate 12 | 13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 14 | return YES; 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Example-ObjC/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Example-ObjC/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example-ObjC/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Example-ObjC/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Example-ObjC/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Example-ObjC/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // Example-ObjC 4 | // 5 | // Created by Indragie Karunaratne on 4/20/19. 6 | // Copyright © 2019 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /Example-ObjC/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // Example-ObjC 4 | // 5 | // Created by Indragie Karunaratne on 4/20/19. 6 | // Copyright © 2019 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | @import InAppViewDebugger; 12 | 13 | @implementation ViewController 14 | 15 | - (void)loadView 16 | { 17 | UIView *view = [[UIView alloc] init]; 18 | view.backgroundColor = UIColor.cyanColor; 19 | view.frame = UIScreen.mainScreen.bounds; 20 | 21 | CGRect slice, remainder; 22 | CGRectDivide(view.bounds, &slice, &remainder, CGRectGetWidth(view.bounds) / 2.0, CGRectMinXEdge); 23 | UIView *view1 = [[UIView alloc] init]; 24 | view1.backgroundColor = UIColor.yellowColor; 25 | view1.frame = slice; 26 | 27 | UIView *view2 = [[UIView alloc] init]; 28 | view2.backgroundColor = UIColor.purpleColor; 29 | view2.frame = remainder; 30 | 31 | [view addSubview:view1]; 32 | [view addSubview:view2]; 33 | 34 | CGRect slice1, remainder1; 35 | CGRectDivide(view1.bounds, &slice1, &remainder1, CGRectGetHeight(view1.bounds) / 2.0, CGRectMinYEdge); 36 | UIView *view3 = [[UIView alloc] init]; 37 | view3.backgroundColor = UIColor.blueColor; 38 | view3.frame = slice1; 39 | 40 | UIView *overlay = [[UIView alloc] init]; 41 | overlay.backgroundColor = UIColor.brownColor; 42 | overlay.frame = UIEdgeInsetsInsetRect(view3.bounds, UIEdgeInsetsMake(20.0, 20.0, 20.0, 20.0)); 43 | [view3 addSubview:overlay]; 44 | 45 | UIView *view4 = [[UIView alloc] init]; 46 | view4.backgroundColor = UIColor.orangeColor; 47 | view4.frame = remainder1; 48 | 49 | [view1 addSubview:view3]; 50 | [view1 addSubview:view4]; 51 | 52 | CGRect slice2, remainder2; 53 | CGRectDivide(view2.bounds, &slice2, &remainder2, CGRectGetHeight(view2.bounds) / 2.0, CGRectMinYEdge); 54 | UIView *view5 = [[UIView alloc] init]; 55 | view5.backgroundColor = UIColor.redColor; 56 | view5.frame = slice2; 57 | 58 | UIView *view6 = [[UIView alloc] init]; 59 | view6.backgroundColor = UIColor.greenColor; 60 | view6.frame = remainder2; 61 | 62 | [view2 addSubview:view5]; 63 | [view2 addSubview:view6]; 64 | 65 | self.view = view; 66 | } 67 | 68 | - (IBAction)snapshot:(UIBarButtonItem *)sender 69 | { 70 | [InAppViewDebugger present]; 71 | } 72 | 73 | @end 74 | -------------------------------------------------------------------------------- /Example-ObjC/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Example-ObjC 4 | // 5 | // Created by Indragie Karunaratne on 4/20/19. 6 | // Copyright © 2019 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Example-Swift/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // LiveSnapshot 4 | // 5 | // Created by Indragie Karunaratne on 3/30/19. 6 | // Copyright © 2019 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | return true 18 | } 19 | 20 | } 21 | 22 | -------------------------------------------------------------------------------- /Example-Swift/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Example-Swift/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example-Swift/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Example-Swift/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Example-Swift/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Example-Swift/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // LiveSnapshot 4 | // 5 | // Created by Indragie Karunaratne on 3/30/19. 6 | // Copyright © 2019 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SceneKit 11 | import InAppViewDebugger 12 | 13 | class ViewController: UIViewController { 14 | 15 | override func loadView() { 16 | let view = UIView() 17 | view.backgroundColor = UIColor.cyan 18 | view.frame = UIScreen.main.bounds 19 | 20 | let frames = view.bounds.divided(atDistance: view.bounds.width / 2, from: .minXEdge) 21 | let view1 = UIView() 22 | view1.backgroundColor = .yellow 23 | view1.frame = frames.slice 24 | 25 | let view2 = UIView() 26 | view2.backgroundColor = .purple 27 | view2.frame = frames.remainder 28 | 29 | view.addSubview(view1) 30 | view.addSubview(view2) 31 | 32 | let frames1 = view1.bounds.divided(atDistance: view1.bounds.height / 2, from: .minYEdge) 33 | let view3 = UIView() 34 | view3.backgroundColor = .blue 35 | view3.frame = frames1.slice 36 | 37 | let overlay = UIView() 38 | overlay.backgroundColor = .brown 39 | overlay.frame = view3.bounds.insetBy(dx: 20, dy: 20) 40 | view3.addSubview(overlay) 41 | 42 | let view4 = UIView() 43 | view4.backgroundColor = .orange 44 | view4.frame = frames1.remainder 45 | 46 | view1.addSubview(view3) 47 | view1.addSubview(view4) 48 | 49 | let frames2 = view2.bounds.divided(atDistance: view2.bounds.height / 2, from: .minYEdge) 50 | let view5 = UIView() 51 | view5.backgroundColor = .red 52 | view5.frame = frames2.slice 53 | 54 | let view6 = UIView() 55 | view6.backgroundColor = .green 56 | view6.frame = frames2.remainder 57 | 58 | view2.addSubview(view5) 59 | view2.addSubview(view6) 60 | 61 | self.view = view 62 | } 63 | 64 | @IBAction func snapshot(sender: UIBarButtonItem) { 65 | InAppViewDebugger.present() 66 | } 67 | } 68 | 69 | -------------------------------------------------------------------------------- /InAppViewDebugger.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = "InAppViewDebugger" 3 | spec.version = "1.0.3" 4 | spec.summary = "A UIView debugger (like Reveal or Xcode) that can be embedded in an app for on-device view debugging." 5 | spec.homepage = "https://github.com/indragiek/InAppViewDebugger" 6 | spec.screenshots = "https://raw.githubusercontent.com/indragiek/InAppViewDebugger/master/docs/img/main.png" 7 | spec.license = { :type => "MIT", :file => "LICENSE" } 8 | spec.author = { "Indragie Karunaratne" => "i@indragie.com" } 9 | spec.social_media_url = "https://twitter.com/indragie" 10 | spec.platform = :ios, "11.0" 11 | spec.swift_version = '4.2' 12 | spec.source = { :git => "https://github.com/indragiek/InAppViewDebugger.git", :tag => "#{spec.version}" } 13 | spec.source_files = "InAppViewDebugger/**/*.{h,m,swift}" 14 | spec.resources = "InAppViewDebugger/**/*.xcassets" 15 | end 16 | -------------------------------------------------------------------------------- /InAppViewDebugger.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /InAppViewDebugger.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /InAppViewDebugger.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /InAppViewDebugger.xcodeproj/xcshareddata/xcschemes/Example-ObjC.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /InAppViewDebugger.xcodeproj/xcshareddata/xcschemes/Example-Swift.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /InAppViewDebugger.xcodeproj/xcshareddata/xcschemes/InAppViewDebugger.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /InAppViewDebugger.xcodeproj/xcuserdata/indragie.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Example-ObjC.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 2 11 | 12 | Example-Swift.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | InAppViewDebugger.xcscheme_^#shared#^_ 18 | 19 | orderHint 20 | 1 21 | 22 | 23 | SuppressBuildableAutocreation 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /InAppViewDebugger/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /InAppViewDebugger/Assets.xcassets/DisclosureIndicator.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Disclosure Indicator.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Disclosure Indicator@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Disclosure Indicator@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /InAppViewDebugger/Assets.xcassets/DisclosureIndicator.imageset/Disclosure Indicator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/InAppViewDebugger/Assets.xcassets/DisclosureIndicator.imageset/Disclosure Indicator.png -------------------------------------------------------------------------------- /InAppViewDebugger/Assets.xcassets/DisclosureIndicator.imageset/Disclosure Indicator@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/InAppViewDebugger/Assets.xcassets/DisclosureIndicator.imageset/Disclosure Indicator@2x.png -------------------------------------------------------------------------------- /InAppViewDebugger/Assets.xcassets/DisclosureIndicator.imageset/Disclosure Indicator@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/InAppViewDebugger/Assets.xcassets/DisclosureIndicator.imageset/Disclosure Indicator@3x.png -------------------------------------------------------------------------------- /InAppViewDebugger/Assets.xcassets/RangeSliderFill.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "fill.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "fill@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "fill@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /InAppViewDebugger/Assets.xcassets/RangeSliderFill.imageset/fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/InAppViewDebugger/Assets.xcassets/RangeSliderFill.imageset/fill.png -------------------------------------------------------------------------------- /InAppViewDebugger/Assets.xcassets/RangeSliderFill.imageset/fill@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/InAppViewDebugger/Assets.xcassets/RangeSliderFill.imageset/fill@2x.png -------------------------------------------------------------------------------- /InAppViewDebugger/Assets.xcassets/RangeSliderFill.imageset/fill@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/InAppViewDebugger/Assets.xcassets/RangeSliderFill.imageset/fill@3x.png -------------------------------------------------------------------------------- /InAppViewDebugger/Assets.xcassets/RangeSliderLeftHandle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "left_handle.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "left_handle@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "left_handle@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /InAppViewDebugger/Assets.xcassets/RangeSliderLeftHandle.imageset/left_handle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/InAppViewDebugger/Assets.xcassets/RangeSliderLeftHandle.imageset/left_handle.png -------------------------------------------------------------------------------- /InAppViewDebugger/Assets.xcassets/RangeSliderLeftHandle.imageset/left_handle@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/InAppViewDebugger/Assets.xcassets/RangeSliderLeftHandle.imageset/left_handle@2x.png -------------------------------------------------------------------------------- /InAppViewDebugger/Assets.xcassets/RangeSliderLeftHandle.imageset/left_handle@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/InAppViewDebugger/Assets.xcassets/RangeSliderLeftHandle.imageset/left_handle@3x.png -------------------------------------------------------------------------------- /InAppViewDebugger/Assets.xcassets/RangeSliderRightHandle.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "right_handle.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "right_handle@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "right_handle@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /InAppViewDebugger/Assets.xcassets/RangeSliderRightHandle.imageset/right_handle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/InAppViewDebugger/Assets.xcassets/RangeSliderRightHandle.imageset/right_handle.png -------------------------------------------------------------------------------- /InAppViewDebugger/Assets.xcassets/RangeSliderRightHandle.imageset/right_handle@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/InAppViewDebugger/Assets.xcassets/RangeSliderRightHandle.imageset/right_handle@2x.png -------------------------------------------------------------------------------- /InAppViewDebugger/Assets.xcassets/RangeSliderRightHandle.imageset/right_handle@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/InAppViewDebugger/Assets.xcassets/RangeSliderRightHandle.imageset/right_handle@3x.png -------------------------------------------------------------------------------- /InAppViewDebugger/Assets.xcassets/RangeSliderTrack.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "track.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "track@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "track@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /InAppViewDebugger/Assets.xcassets/RangeSliderTrack.imageset/track.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/InAppViewDebugger/Assets.xcassets/RangeSliderTrack.imageset/track.png -------------------------------------------------------------------------------- /InAppViewDebugger/Assets.xcassets/RangeSliderTrack.imageset/track@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/InAppViewDebugger/Assets.xcassets/RangeSliderTrack.imageset/track@2x.png -------------------------------------------------------------------------------- /InAppViewDebugger/Assets.xcassets/RangeSliderTrack.imageset/track@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/InAppViewDebugger/Assets.xcassets/RangeSliderTrack.imageset/track@3x.png -------------------------------------------------------------------------------- /InAppViewDebugger/BUILD: -------------------------------------------------------------------------------- 1 | load( 2 | "@build_bazel_rules_apple//apple:ios.bzl", 3 | "ios_static_framework", 4 | ) 5 | load( 6 | "@build_bazel_rules_apple//apple:resources.bzl", 7 | "apple_resource_bundle", 8 | ) 9 | load( 10 | "@build_bazel_rules_swift//swift:swift.bzl", 11 | "swift_library", 12 | ) 13 | 14 | apple_resource_bundle( 15 | name = "Assets", 16 | resources = glob([ 17 | "Assets.xcassets/**", 18 | ]), 19 | ) 20 | 21 | swift_library( 22 | name = "InAppViewDebugger", 23 | srcs = glob([ 24 | "*.swift", 25 | ]), 26 | data = [ 27 | ":Assets", 28 | ], 29 | module_name = "InAppViewDebugger", 30 | ) 31 | 32 | ios_static_framework( 33 | name = "InAppViewDebuggerFramework", 34 | bundle_name = "InAppViewDebugger", 35 | minimum_os_version = "11.0", 36 | deps = [ 37 | ":InAppViewDebugger", 38 | ], 39 | ) 40 | -------------------------------------------------------------------------------- /InAppViewDebugger/Configuration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Configuration.swift 3 | // InAppViewDebugger 4 | // 5 | // Created by Indragie Karunaratne on 4/4/19. 6 | // Copyright © 2019 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Configuration options for the in app view debugger. 12 | @objc(IAVDConfiguration) public final class Configuration: NSObject { 13 | /// Configuration for the 3D snapshot view. 14 | @objc public var snapshotViewConfiguration = SnapshotViewConfiguration() 15 | 16 | /// Configuration for the hierarchy (tree) view. 17 | @objc public var hierarchyViewConfiguration = HierarchyViewConfiguration() 18 | } 19 | -------------------------------------------------------------------------------- /InAppViewDebugger/Element.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Element.swift 3 | // LiveSnapshot 4 | // 5 | // Created by Indragie Karunaratne on 3/30/19. 6 | // Copyright © 2019 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreGraphics 11 | 12 | /// Provides identifying information for an element that is displayed in the 13 | /// view debugger. 14 | @objc(IAVDElementLabel) public final class ElementLabel: NSObject { 15 | /// Classification for an element that determines how it is represented 16 | /// in the view debugger. 17 | @objc(IAVDElementClassification) public enum Classification: Int { 18 | /// An element of normal importance. 19 | case normal 20 | 21 | /// An element of higher importance that is highlighted 22 | case important 23 | } 24 | 25 | /// A human readable name for the element. 26 | @objc public let name: String? 27 | 28 | /// Classification for an element that determines how it is represented 29 | /// in the view debugger. 30 | @objc public let classification: Classification 31 | 32 | /// Constructs a new `Element` 33 | /// 34 | /// - Parameters: 35 | /// - name: A human readable name for the element 36 | /// - classification: Classification for an element that determines how it 37 | /// is represented in the view debugger. 38 | @objc public init(name: String?, classification: Classification = .normal) { 39 | self.name = name 40 | self.classification = classification 41 | } 42 | } 43 | 44 | /// A UI element that can be snapshotted. 45 | @objc(IAVDElement) public protocol Element { 46 | /// Identifying information for the element, like its name and classification. 47 | var label: ElementLabel { get } 48 | 49 | /// A shortened description of the element. 50 | var shortDescription: String { get } 51 | 52 | /// The full length description of the element. 53 | var description: String { get } 54 | 55 | /// The frame of the element in its parent's coordinate space. 56 | var frame: CGRect { get } 57 | 58 | /// Whether the element is hidden from view or not. 59 | var isHidden: Bool { get } 60 | 61 | /// A snapshot image of the element in its current state. 62 | var snapshotImage: CGImage? { get } 63 | 64 | /// The child elements of the element. 65 | var children: [Element] { get } 66 | } 67 | -------------------------------------------------------------------------------- /InAppViewDebugger/HierarchyTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HierarchyTableViewCell.swift 3 | // InAppViewDebugger 4 | // 5 | // Created by Indragie Karunaratne on 4/6/19. 6 | // Copyright © 2019 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol HierarchyTableViewCellDelegate: AnyObject { 12 | func hierarchyTableViewCellDidTapSubtree(cell: HierarchyTableViewCell) 13 | func hierarchyTableViewCellDidLongPress(cell: HierarchyTableViewCell, point: CGPoint) 14 | } 15 | 16 | final class HierarchyTableViewCell: UITableViewCell { 17 | private lazy var labelStackView: UIStackView = { [unowned self] in 18 | let stackView = UIStackView() 19 | stackView.spacing = 3.0 20 | stackView.axis = .vertical 21 | stackView.setContentHuggingPriority(.defaultLow, for: .horizontal) 22 | stackView.translatesAutoresizingMaskIntoConstraints = false 23 | stackView.addArrangedSubview(nameLabel) 24 | stackView.addArrangedSubview(frameLabel) 25 | return stackView 26 | }() 27 | 28 | let lineView: ParallelLineView = { 29 | let lineView = ParallelLineView() 30 | lineView.setContentHuggingPriority(.defaultHigh, for: .horizontal) 31 | lineView.translatesAutoresizingMaskIntoConstraints = false 32 | return lineView 33 | }() 34 | 35 | let nameLabel: UILabel = { 36 | let label = UILabel() 37 | label.font = .preferredFont(forTextStyle: .body) 38 | label.translatesAutoresizingMaskIntoConstraints = false 39 | return label 40 | }() 41 | 42 | let frameLabel: UILabel = { 43 | let label = UILabel() 44 | label.font = .preferredFont(forTextStyle: .caption1) 45 | label.translatesAutoresizingMaskIntoConstraints = false 46 | return label 47 | }() 48 | 49 | private lazy var subtreeButton: UIButton = { [unowned self] in 50 | let button = UIButton(type: .custom) 51 | let color = UIColor(white: 0.2, alpha: 1.0) 52 | button.setBackgroundImage(colorImage(color: UIColor(white: 0.0, alpha: 0.1)), for: .highlighted) 53 | button.setTitle(NSLocalizedString("Subtree", comment: "Show the subtree starting at this element"), for: .normal) 54 | button.setTitleColor(color, for: .normal) 55 | button.titleLabel?.font = UIFont.systemFont(ofSize: 15.0) 56 | let disclosureImage = UIImage(named: "DisclosureIndicator", in: Bundle(for: HierarchyTableViewCell.self), compatibleWith: nil) 57 | button.setImage(disclosureImage, for: .normal) 58 | button.layer.cornerRadius = 4.0 59 | button.layer.borderWidth = 1.0 60 | button.layer.borderColor = color.cgColor 61 | button.layer.masksToBounds = true 62 | 63 | let imageTextSpacing: CGFloat = 4.0 64 | let imageTextInset = imageTextSpacing / 2.0 65 | button.imageEdgeInsets = UIEdgeInsets(top: 1.0, left: imageTextInset, bottom: 0, right: -imageTextInset) 66 | button.titleEdgeInsets = UIEdgeInsets(top: 0.0, left: -imageTextInset, bottom: 0.0, right: imageTextInset) 67 | button.contentEdgeInsets = UIEdgeInsets(top: 4.0, left: 68 | 4.0 + imageTextInset, bottom: 4.0, right: 4.0 + imageTextInset) 69 | button.semanticContentAttribute = .forceRightToLeft 70 | 71 | button.translatesAutoresizingMaskIntoConstraints = false 72 | button.setContentHuggingPriority(.defaultHigh, for: .horizontal) 73 | 74 | button.addTarget(self, action: #selector(didTapSubtree(sender:)), for: .touchUpInside) 75 | 76 | return button 77 | }() 78 | 79 | // Used to hide/unhide the subtree button. 80 | private var subtreeLabelWidthConstraint: NSLayoutConstraint? 81 | 82 | var showSubtreeButton = false { 83 | didSet { 84 | subtreeLabelWidthConstraint?.isActive = !showSubtreeButton 85 | } 86 | } 87 | 88 | var indexPath: IndexPath? 89 | 90 | weak var delegate: HierarchyTableViewCellDelegate? 91 | 92 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 93 | super.init(style: style, reuseIdentifier: reuseIdentifier) 94 | 95 | selectedBackgroundView = { 96 | let backgroundView = UIView() 97 | backgroundView.backgroundColor = UIColor(white: 0.9, alpha: 1.0) 98 | return backgroundView 99 | }() 100 | 101 | contentView.addSubview(lineView) 102 | contentView.addSubview(labelStackView) 103 | contentView.addSubview(subtreeButton) 104 | 105 | let marginsGuide = contentView.layoutMarginsGuide 106 | NSLayoutConstraint.activate([ 107 | lineView.leadingAnchor.constraint(equalTo: marginsGuide.leadingAnchor), 108 | lineView.topAnchor.constraint(equalTo: topAnchor), 109 | lineView.bottomAnchor.constraint(equalTo: bottomAnchor), 110 | labelStackView.leadingAnchor.constraint(equalTo: lineView.trailingAnchor, constant: 5.0), 111 | labelStackView.centerYAnchor.constraint(equalTo: marginsGuide.centerYAnchor), 112 | subtreeButton.leadingAnchor.constraint(equalTo: labelStackView.trailingAnchor, constant: 5.0), 113 | subtreeButton.centerYAnchor.constraint(equalTo: marginsGuide.centerYAnchor), 114 | subtreeButton.trailingAnchor.constraint(equalTo: marginsGuide.trailingAnchor), 115 | ]) 116 | subtreeLabelWidthConstraint = subtreeButton.widthAnchor.constraint(equalToConstant: 0.0) 117 | 118 | let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(sender:))) 119 | contentView.addGestureRecognizer(longPressGestureRecognizer) 120 | } 121 | 122 | required init?(coder aDecoder: NSCoder) { 123 | fatalError("init(coder:) has not been implemented") 124 | } 125 | 126 | // MARK: Actions 127 | 128 | @objc private func didTapSubtree(sender: UIButton) { 129 | delegate?.hierarchyTableViewCellDidTapSubtree(cell: self) 130 | } 131 | 132 | @objc private func handleLongPress(sender: UILongPressGestureRecognizer) { 133 | guard sender.state == .began else { 134 | return 135 | } 136 | let point = sender.location(ofTouch: 0, in: self) 137 | delegate?.hierarchyTableViewCellDidLongPress(cell: self, point: point) 138 | } 139 | } 140 | 141 | private func colorImage(color: UIColor) -> UIImage? { 142 | UIGraphicsBeginImageContext(CGSize(width: 1.0, height: 1.0)) 143 | color.setFill() 144 | UIRectFill(CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0)) 145 | let image = UIGraphicsGetImageFromCurrentImageContext() 146 | UIGraphicsEndImageContext() 147 | return image 148 | } 149 | -------------------------------------------------------------------------------- /InAppViewDebugger/HierarchyTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HierarchyTableViewController.swift 3 | // InAppViewDebugger 4 | // 5 | // Created by Indragie Karunaratne on 4/6/19. 6 | // Copyright © 2019 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol HierarchyTableViewControllerDelegate: AnyObject { 12 | func hierarchyTableViewController(_ viewController: HierarchyTableViewController, didSelectSnapshot snapshot: Snapshot) 13 | func hierarchyTableViewController(_ viewController: HierarchyTableViewController, didDeselectSnapshot snapshot: Snapshot) 14 | func hierarchyTableViewController(_ viewController: HierarchyTableViewController, didFocusOnSnapshot snapshot: Snapshot) 15 | func hierarchyTableViewControllerWillNavigateBackToPreviousSnapshot(_ viewController: HierarchyTableViewController) 16 | } 17 | 18 | final class HierarchyTableViewController: UITableViewController, HierarchyTableViewCellDelegate, HierarchyTableViewControllerDelegate { 19 | private static let ReuseIdentifier = "HierarchyTableViewCell" 20 | 21 | private let snapshot: Snapshot 22 | private let configuration: HierarchyViewConfiguration 23 | 24 | private var dataSource: TreeTableViewDataSource? { 25 | didSet { 26 | if isViewLoaded { 27 | tableView?.dataSource = dataSource 28 | tableView?.reloadData() 29 | } 30 | } 31 | } 32 | 33 | private var shouldIgnoreMaxDepth = false { 34 | didSet { 35 | if shouldIgnoreMaxDepth != oldValue { 36 | dataSource = TreeTableViewDataSource( 37 | tree: snapshot, 38 | maxDepth: shouldIgnoreMaxDepth ? nil : configuration.maxDepth?.intValue, 39 | cellFactory: cellFactory(shouldIgnoreMaxDepth: shouldIgnoreMaxDepth) 40 | ) 41 | } 42 | } 43 | } 44 | 45 | weak var delegate: HierarchyTableViewControllerDelegate? 46 | 47 | init(snapshot: Snapshot, configuration: HierarchyViewConfiguration) { 48 | self.snapshot = snapshot 49 | self.configuration = configuration 50 | 51 | super.init(nibName: nil, bundle: nil) 52 | 53 | navigationItem.title = snapshot.element.label.name 54 | clearsSelectionOnViewWillAppear = false 55 | 56 | self.dataSource = TreeTableViewDataSource( 57 | tree: snapshot, 58 | maxDepth: configuration.maxDepth?.intValue, 59 | cellFactory: cellFactory(shouldIgnoreMaxDepth: false) 60 | ) 61 | } 62 | 63 | required init?(coder aDecoder: NSCoder) { 64 | fatalError("init(coder:) has not been implemented") 65 | } 66 | 67 | override func viewDidLoad() { 68 | super.viewDidLoad() 69 | tableView.register(HierarchyTableViewCell.self, forCellReuseIdentifier: HierarchyTableViewController.ReuseIdentifier) 70 | tableView.dataSource = dataSource 71 | tableView.separatorStyle = .none 72 | } 73 | 74 | override func viewWillDisappear(_ animated: Bool) { 75 | super.viewWillDisappear(animated) 76 | if isMovingFromParent { 77 | deselectAll() 78 | delegate?.hierarchyTableViewControllerWillNavigateBackToPreviousSnapshot(self) 79 | } 80 | } 81 | 82 | // MARK: API 83 | 84 | func selectRow(forSnapshot snapshot: Snapshot) { 85 | let topViewController = topHierarchyViewController() 86 | if topViewController == self { 87 | shouldIgnoreMaxDepth = true 88 | let indexPath = dataSource?.indexPath(forValue: snapshot) 89 | tableView.selectRow(at: indexPath, animated: true, scrollPosition: .middle) 90 | } else { 91 | topViewController.selectRow(forSnapshot: snapshot) 92 | } 93 | } 94 | 95 | func deselectRow(forSnapshot snapshot: Snapshot) { 96 | let topViewController = topHierarchyViewController() 97 | if topViewController == self { 98 | shouldIgnoreMaxDepth = false 99 | guard let indexPath = dataSource?.indexPath(forValue: snapshot) else { 100 | return 101 | } 102 | tableView.deselectRow(at: indexPath, animated: true) 103 | } else { 104 | topViewController.deselectRow(forSnapshot: snapshot) 105 | } 106 | } 107 | 108 | func focus(snapshot: Snapshot) { 109 | focus(snapshot: snapshot, callDelegate: false) 110 | } 111 | 112 | private func focus(snapshot: Snapshot, callDelegate: Bool) { 113 | let topViewController = topHierarchyViewController() 114 | if topViewController == self { 115 | pushSubtreeViewController(snapshot: snapshot, callDelegate: callDelegate) 116 | } else { 117 | topViewController.focus(snapshot: snapshot) 118 | } 119 | 120 | } 121 | 122 | // MARK: UITableViewDelegate 123 | 124 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 125 | guard let snapshot = dataSource?.value(atIndexPath: indexPath) else { 126 | return 127 | } 128 | delegate?.hierarchyTableViewController(self, didSelectSnapshot: snapshot) 129 | } 130 | 131 | override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) { 132 | guard let snapshot = dataSource?.value(atIndexPath: indexPath) else { 133 | return 134 | } 135 | delegate?.hierarchyTableViewController(self, didDeselectSnapshot: snapshot) 136 | } 137 | 138 | // MARK: HierarchyTableViewCellDelegate 139 | 140 | func hierarchyTableViewCellDidTapSubtree(cell: HierarchyTableViewCell) { 141 | guard let indexPath = cell.indexPath, let snapshot = dataSource?.value(atIndexPath: indexPath) else { 142 | return 143 | } 144 | pushSubtreeViewController(snapshot: snapshot, callDelegate: true) 145 | } 146 | 147 | func hierarchyTableViewCellDidLongPress(cell: HierarchyTableViewCell, point: CGPoint) { 148 | guard let indexPath = cell.indexPath, let snapshot = dataSource?.value(atIndexPath: indexPath) else { 149 | return 150 | } 151 | let actionSheet = makeActionSheet(snapshot: snapshot, sourceView: cell, sourcePoint: point) { snapshot in 152 | self.focus(snapshot: snapshot, callDelegate: true) 153 | } 154 | present(actionSheet, animated: true, completion: nil) 155 | } 156 | 157 | // MARK: HierarchyTableViewControllerDelegate 158 | 159 | func hierarchyTableViewController(_ viewController: HierarchyTableViewController, didSelectSnapshot snapshot: Snapshot) { 160 | delegate?.hierarchyTableViewController(self, didSelectSnapshot: snapshot) 161 | } 162 | 163 | func hierarchyTableViewController(_ viewController: HierarchyTableViewController, didDeselectSnapshot snapshot: Snapshot) { 164 | delegate?.hierarchyTableViewController(self, didDeselectSnapshot: snapshot) 165 | } 166 | 167 | func hierarchyTableViewController(_ viewController: HierarchyTableViewController, didFocusOnSnapshot snapshot: Snapshot) { 168 | delegate?.hierarchyTableViewController(self, didFocusOnSnapshot: snapshot) 169 | } 170 | 171 | func hierarchyTableViewControllerWillNavigateBackToPreviousSnapshot(_ viewController: HierarchyTableViewController) { 172 | delegate?.hierarchyTableViewControllerWillNavigateBackToPreviousSnapshot(self) 173 | } 174 | 175 | // MARK: Private 176 | 177 | private func pushSubtreeViewController(snapshot: Snapshot, callDelegate: Bool) { 178 | deselectAll() 179 | let subtreeViewController = HierarchyTableViewController(snapshot: snapshot, configuration: configuration) 180 | subtreeViewController.delegate = self 181 | navigationController?.pushViewController(subtreeViewController, animated: true) 182 | if callDelegate { 183 | delegate?.hierarchyTableViewController(self, didFocusOnSnapshot: snapshot) 184 | } 185 | } 186 | 187 | private func deselectAll() { 188 | guard let indexPaths = tableView?.indexPathsForSelectedRows else { 189 | return 190 | } 191 | for indexPath in indexPaths { 192 | tableView.deselectRow(at: indexPath, animated: true) 193 | } 194 | } 195 | 196 | private func topHierarchyViewController() -> HierarchyTableViewController { 197 | if let hierarchyViewController = navigationController?.topViewController as? HierarchyTableViewController { 198 | return hierarchyViewController 199 | } 200 | return self 201 | } 202 | 203 | private func cellFactory(shouldIgnoreMaxDepth: Bool) -> TreeTableViewDataSource.CellFactory { 204 | return { [unowned self] (tableView, value, depth, indexPath, isCollapsed) in 205 | let reuseIdentifier = HierarchyTableViewController.ReuseIdentifier 206 | let cell = (tableView.dequeueReusableCell(withIdentifier: reuseIdentifier) as? HierarchyTableViewCell) ?? HierarchyTableViewCell(style: .default, reuseIdentifier: reuseIdentifier) 207 | 208 | let baseFont = self.configuration.nameFont 209 | switch value.label.classification { 210 | case .normal: 211 | cell.nameLabel.font = baseFont 212 | case .important: 213 | if let descriptor = baseFont.fontDescriptor.withSymbolicTraits(.traitBold) { 214 | cell.nameLabel.font = UIFont(descriptor: descriptor, size: baseFont.pointSize) 215 | } else { 216 | cell.nameLabel.font = baseFont 217 | } 218 | } 219 | cell.nameLabel.text = value.label.name 220 | 221 | let frame = value.frame 222 | cell.frameLabel.font = self.configuration.frameFont 223 | cell.frameLabel.text = String(format: "(%.1f, %.1f, %.1f, %.1f)", frame.origin.x, frame.origin.y, frame.size.width, frame.size.height) 224 | cell.lineView.lineCount = depth 225 | cell.lineView.lineColors = self.configuration.lineColors 226 | cell.lineView.lineWidth = self.configuration.lineWidth 227 | cell.lineView.lineSpacing = self.configuration.lineSpacing 228 | cell.showSubtreeButton = !shouldIgnoreMaxDepth && !value.children.isEmpty && depth >= (self.configuration.maxDepth?.intValue ?? Int.max) 229 | cell.indexPath = indexPath 230 | cell.delegate = self 231 | return cell 232 | } 233 | } 234 | } 235 | 236 | extension Snapshot: Tree {} 237 | -------------------------------------------------------------------------------- /InAppViewDebugger/HierarchyViewConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HierarchyViewConfiguration.swift 3 | // InAppViewDebugger 4 | // 5 | // Created by Indragie Karunaratne on 4/8/19. 6 | // Copyright © 2019 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Configuration options for the hierarchy (tree) view. 12 | @objc(IAVDHierarchyViewConfiguration) public final class HierarchyViewConfiguration: NSObject { 13 | /// The maximum depth that is rendered in the tree view. If the 14 | /// depth of the item exceeds this value, it will be hidden from 15 | /// view and can only be accessed by tapping the "Subtree" button 16 | /// of its parent node. 17 | /// 18 | /// Setting this to `nil` means there's no maximum. 19 | @objc public var maxDepth: NSNumber? = 5 20 | 21 | /// The font used to render the names of the elements. 22 | @objc public var nameFont = UIFont.preferredFont(forTextStyle: .body) 23 | 24 | /// The font used to render the frames of the elements. 25 | @objc public var frameFont = UIFont.preferredFont(forTextStyle: .caption1) 26 | 27 | /// The colors of the lines displayed to show the depth of the tree. 28 | /// 29 | /// If the depth of a node exceeds the number of colors in this array, 30 | /// the colors will repeat from the beginning. 31 | @objc public var lineColors = [ 32 | UIColor(white: 0.2, alpha: 1.0), 33 | UIColor(white: 0.4, alpha: 1.0), 34 | UIColor(white: 0.6, alpha: 1.0), 35 | UIColor(white: 0.8, alpha: 1.0), 36 | UIColor(white: 0.9, alpha: 1.0), 37 | UIColor(white: 0.95, alpha: 1.0) 38 | ] 39 | 40 | /// The width of the lines drawn to show the depth of the tree. 41 | @objc public var lineWidth: CGFloat = 1.0 42 | 43 | /// The spacing between the lines drawn to show the depth of the three. 44 | @objc public var lineSpacing: CGFloat = 12.0 45 | } 46 | -------------------------------------------------------------------------------- /InAppViewDebugger/InAppViewDebugger.h: -------------------------------------------------------------------------------- 1 | // 2 | // InAppViewDebugger.h 3 | // InAppViewDebugger 4 | // 5 | // Created by Indragie Karunaratne on 3/31/19. 6 | // Copyright © 2019 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for InAppViewDebugger. 12 | FOUNDATION_EXPORT double InAppViewDebuggerVersionNumber; 13 | 14 | //! Project version string for InAppViewDebugger. 15 | FOUNDATION_EXPORT const unsigned char InAppViewDebuggerVersionString[]; 16 | -------------------------------------------------------------------------------- /InAppViewDebugger/InAppViewDebugger.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InAppViewDebugger.swift 3 | // InAppViewDebugger 4 | // 5 | // Created by Indragie Karunaratne on 4/4/19. 6 | // Copyright © 2019 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Exposes APIs for presenting the view debugger. 12 | @objc public final class InAppViewDebugger: NSObject { 13 | /// Takes a snapshot of the application's key window and presents the debugger 14 | /// view controller from the root view controller. 15 | @objc public class func present() { 16 | presentForWindow(UIApplication.shared.keyWindow) 17 | } 18 | 19 | /// Takes a snapshot of the specified window and presents the debugger view controller 20 | /// from the root view controller. 21 | /// 22 | /// - Parameters: 23 | /// - window: The view controller whose view should be snapshotted. 24 | /// - configuration: Optional configuration for the view debugger. 25 | /// - completion: Completion block to be called once the view debugger has 26 | /// been presented. 27 | @objc public class func presentForWindow(_ window: UIWindow?, configuration: Configuration? = nil, completion: (() -> Void)? = nil) { 28 | guard let window = window else { 29 | return 30 | } 31 | let snapshot = Snapshot(element: ViewElement(view: window)) 32 | presentWithSnapshot(snapshot, rootViewController: window.rootViewController, configuration: configuration, completion: completion) 33 | } 34 | 35 | /// Takes a snapshot of the specified view and presents the debugger view controller 36 | /// from the nearest ancestor view controller. 37 | /// 38 | /// - Parameters: 39 | /// - view: The view controller whose view should be snapshotted. 40 | /// - configuration: Optional configuration for the view debugger. 41 | /// - completion: Completion block to be called once the view debugger has 42 | /// been presented. 43 | @objc public class func presentForView(_ view: UIView?, configuration: Configuration? = nil, completion: (() -> Void)? = nil) { 44 | guard let view = view else { 45 | return 46 | } 47 | let snapshot = Snapshot(element: ViewElement(view: view)) 48 | presentWithSnapshot(snapshot, rootViewController: getNearestAncestorViewController(responder: view), configuration: configuration, completion: completion) 49 | } 50 | 51 | /// Takes a snapshot of the view of the specified view controller and presents 52 | /// the debugger view controller. 53 | /// 54 | /// - Parameters: 55 | /// - viewController: The view controller whose view should be snapshotted. 56 | /// - configuration: Optional configuration for the view debugger. 57 | /// - completion: Completion block to be called once the view debugger has 58 | /// been presented. 59 | @objc public class func presentForViewController(_ viewController: UIViewController?, configuration: Configuration? = nil, completion: (() -> Void)? = nil) { 60 | guard let view = viewController?.view else { 61 | return 62 | } 63 | let snapshot = Snapshot(element: ViewElement(view: view)) 64 | presentWithSnapshot(snapshot, rootViewController: viewController, configuration: configuration, completion: completion) 65 | } 66 | 67 | /// Presents a view debugger for the a snapshot as a modal view controller on 68 | /// top of the specified root view controller. 69 | /// 70 | /// - Parameters: 71 | /// - snapshot: The snapshot to render. 72 | /// - rootViewController: The root view controller to present the debugger view 73 | /// controller from. 74 | /// - configuration: Optional configuration for the view debugger. 75 | /// - completion: Completion block to be called once the view debugger has 76 | /// been presented. 77 | @objc public class func presentWithSnapshot(_ snapshot: Snapshot, rootViewController: UIViewController?, configuration: Configuration? = nil, completion: (() -> Void)? = nil) { 78 | guard let rootViewController = rootViewController else { 79 | return 80 | } 81 | let debuggerViewController = ViewDebuggerViewController(snapshot: snapshot, configuration: configuration ?? Configuration()) 82 | let navigationController = UINavigationController(rootViewController: debuggerViewController) 83 | topViewController(rootViewController: rootViewController)?.present(navigationController, animated: true, completion: completion) 84 | } 85 | 86 | @available(*, unavailable) 87 | override init() { 88 | fatalError() 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /InAppViewDebugger/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /InAppViewDebugger/ParallelLineView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParallelLineView.swift 3 | // InAppViewDebugger 4 | // 5 | // Created by Indragie Karunaratne on 4/7/19. 6 | // Copyright © 2019 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreGraphics 11 | 12 | /// A view that draws one or more parallel vertical lines. 13 | final class ParallelLineView: UIView { 14 | public var lineColors = [UIColor.black] 15 | 16 | public var lineWidth: CGFloat = 1.0 { 17 | didSet { 18 | setNeedsDisplay() 19 | invalidateIntrinsicContentSize() 20 | } 21 | } 22 | 23 | public var lineSpacing: CGFloat = 12.0 { 24 | didSet { 25 | setNeedsDisplay() 26 | invalidateIntrinsicContentSize() 27 | } 28 | } 29 | 30 | public var lineCount: Int = 0 { 31 | didSet { 32 | setNeedsDisplay() 33 | invalidateIntrinsicContentSize() 34 | } 35 | } 36 | 37 | override init(frame: CGRect) { 38 | super.init(frame: frame) 39 | backgroundColor = .white 40 | } 41 | 42 | required init?(coder aDecoder: NSCoder) { 43 | fatalError("init(coder:) has not been implemented") 44 | } 45 | 46 | override func draw(_ rect: CGRect) { 47 | guard lineCount > 0 && !lineColors.isEmpty else { 48 | return 49 | } 50 | var x: CGFloat = lineSpacing 51 | (0.. 0 else { 61 | return CGSize(width: 0.0, height: UIView.noIntrinsicMetric) 62 | } 63 | return CGSize(width: CGFloat(lineCount) * (lineWidth + lineSpacing), height: UIView.noIntrinsicMetric) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /InAppViewDebugger/RangeSlider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RangeSlider.swift 3 | // InAppViewDebugger 4 | // 5 | // Created by Indragie Karunaratne on 4/2/19. 6 | // Copyright © 2019 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// A slider with two handles that allows for defining a range of values rather 12 | /// than UISlider, which only allows for a single value. 13 | final class RangeSlider: UIControl { 14 | private let trackImageView = UIImageView() 15 | private let fillImageView = UIImageView() 16 | private let leftHandleImageView = UIImageView() 17 | private let rightHandleImageView = UIImageView() 18 | 19 | private var isTrackingLeftHandle = false 20 | private var isTrackingRightHandle = false 21 | 22 | public var allowableMinimumValue: Float = 0.0 { 23 | didSet { 24 | if minimumValue < allowableMaximumValue { 25 | minimumValue = allowableMaximumValue 26 | } 27 | setNeedsLayout() 28 | } 29 | } 30 | 31 | public var allowableMaximumValue: Float = 1.0 { 32 | didSet { 33 | if maximumValue > allowableMaximumValue { 34 | maximumValue = allowableMaximumValue 35 | } 36 | setNeedsLayout() 37 | } 38 | } 39 | 40 | public var minimumValue: Float = 0.0 { 41 | didSet { 42 | sendActions(for: .valueChanged) 43 | setNeedsLayout() 44 | } 45 | } 46 | 47 | public var maximumValue: Float = 1.0 { 48 | didSet { 49 | sendActions(for: .valueChanged) 50 | setNeedsLayout() 51 | } 52 | } 53 | 54 | override init(frame: CGRect) { 55 | super.init(frame: frame) 56 | isUserInteractionEnabled = true 57 | 58 | addSubview(fillImageView) 59 | addSubview(leftHandleImageView) 60 | addSubview(rightHandleImageView) 61 | 62 | configureTrackImageView() 63 | configureFillImageView() 64 | configureLeftHandleImageView() 65 | configureRightHandleImageView() 66 | } 67 | 68 | private func configureTrackImageView() { 69 | trackImageView.image = trackImage() 70 | trackImageView.isUserInteractionEnabled = false 71 | trackImageView.autoresizingMask = [] 72 | addSubview(trackImageView) 73 | } 74 | 75 | private func configureFillImageView() { 76 | fillImageView.image = fillImage() 77 | fillImageView.isUserInteractionEnabled = false 78 | addSubview(fillImageView) 79 | } 80 | 81 | private func configureLeftHandleImageView() { 82 | leftHandleImageView.image = leftHandleImage() 83 | leftHandleImageView.isUserInteractionEnabled = false 84 | addSubview(leftHandleImageView) 85 | } 86 | 87 | private func configureRightHandleImageView() { 88 | rightHandleImageView.image = rightHandleImage() 89 | rightHandleImageView.isUserInteractionEnabled = false 90 | addSubview(rightHandleImageView) 91 | } 92 | 93 | required init?(coder aDecoder: NSCoder) { 94 | fatalError("init(coder:) has not been implemented") 95 | } 96 | 97 | // MARK: UIView 98 | 99 | override var intrinsicContentSize: CGSize { 100 | return CGSize(width: UIView.noIntrinsicMetric, height: leftHandleImageView.image?.size.height ?? 0.0) 101 | } 102 | 103 | override func layoutSubviews() { 104 | super.layoutSubviews() 105 | 106 | let leftHandleSize = leftHandleImageSize() 107 | let rightHandleSize = rightHandleImageSize() 108 | let trackSize = trackImageView.image?.size ?? .zero 109 | 110 | trackImageView.frame = CGRect( 111 | x: leftHandleSize.width / 2.0, 112 | y: bounds.midY - (trackSize.height / 2.0), 113 | width: bounds.width - (leftHandleSize.width / 2.0) - (rightHandleSize.width / 2.0), 114 | height: trackSize.height 115 | ) 116 | 117 | let delta = allowableMaximumValue - allowableMinimumValue 118 | let minPercentage: Float 119 | let maxPercentage: Float 120 | if delta <= 0.0 { 121 | minPercentage = 0.0 122 | maxPercentage = 0.0 123 | } else { 124 | minPercentage = max(0.0, (minimumValue - allowableMinimumValue) / delta) 125 | maxPercentage = max(minPercentage, (maximumValue - allowableMinimumValue) / delta) 126 | } 127 | 128 | let sliderRangeWidth = bounds.width - leftHandleSize.width - rightHandleSize.width 129 | 130 | leftHandleImageView.frame = CGRect( 131 | x: roundToNearestPixel(sliderRangeWidth * CGFloat(minPercentage)), 132 | y: bounds.midY - (leftHandleSize.height / 2.0), 133 | width: leftHandleSize.width, 134 | height: leftHandleSize.height) 135 | 136 | rightHandleImageView.frame = CGRect( 137 | x: roundToNearestPixel(leftHandleSize.width + sliderRangeWidth * CGFloat(maxPercentage)), 138 | y: bounds.midY - (rightHandleSize.height / 2.0), 139 | width: rightHandleSize.width, 140 | height: rightHandleSize.height) 141 | 142 | fillImageView.frame = CGRect( 143 | x: leftHandleImageView.frame.midX, 144 | y: trackImageView.frame.minY, 145 | width: rightHandleImageView.frame.midX - leftHandleImageView.frame.midX, 146 | height: trackImageView.frame.height 147 | ) 148 | } 149 | 150 | // MARK: UIControl 151 | 152 | override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { 153 | let location = touch.location(in: self) 154 | if leftHandleImageView.frame.contains(location) { 155 | isTrackingLeftHandle = true 156 | isTrackingRightHandle = false 157 | return true 158 | } else if rightHandleImageView.frame.contains(location) { 159 | isTrackingRightHandle = true 160 | isTrackingLeftHandle = false 161 | return true 162 | } 163 | return false 164 | } 165 | 166 | override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { 167 | let location = touch.location(in: self) 168 | if isTrackingLeftHandle { 169 | minimumValue = min(max(allowableMinimumValue, valueAtX(location.x)), maximumValue) 170 | setNeedsLayout() 171 | layoutIfNeeded() 172 | return true 173 | } else if isTrackingRightHandle { 174 | maximumValue = max(min(allowableMaximumValue, valueAtX(location.x)), minimumValue) 175 | setNeedsLayout() 176 | layoutIfNeeded() 177 | return true 178 | } 179 | return false 180 | } 181 | 182 | override func endTracking(_ touch: UITouch?, with event: UIEvent?) { 183 | isTrackingLeftHandle = false 184 | isTrackingRightHandle = false 185 | } 186 | 187 | // MARK: UIGestureRecognizerDelegate 188 | 189 | override func gestureRecognizerShouldBegin(_ gesture: UIGestureRecognizer) -> Bool { 190 | return false 191 | } 192 | 193 | // MARK: Private 194 | 195 | private func valueAtX(_ x: CGFloat) -> Float { 196 | let minX = leftHandleImageSize().width 197 | let maxX = bounds.width - rightHandleImageSize().width 198 | let cappedX = min(max(x, minX), maxX) 199 | let delta = maxX - minX 200 | return Float((delta > 0.0) ? (cappedX - minX) / delta : 0.0) * (allowableMaximumValue - allowableMinimumValue) + allowableMinimumValue 201 | } 202 | 203 | private func leftHandleImageSize() -> CGSize { 204 | return leftHandleImageView.image?.size ?? .zero 205 | } 206 | 207 | private func rightHandleImageSize() -> CGSize { 208 | return rightHandleImageView.image?.size ?? .zero 209 | } 210 | 211 | private func roundToNearestPixel(_ value: CGFloat) -> CGFloat { 212 | let scale = window?.contentScaleFactor ?? UIScreen.main.scale 213 | return (scale > 0.0) ? (round(value * scale) / scale) : 0.0 214 | } 215 | } 216 | 217 | private func imageNamed(_ name: String) -> UIImage? { 218 | return UIImage(named: name, in: Bundle(for: RangeSlider.self), compatibleWith: nil) 219 | } 220 | 221 | private func trackImage() -> UIImage? { 222 | let insets = UIEdgeInsets(top: 0.0, left: 5.0, bottom: 0.0, right: 4.0) 223 | return imageNamed("RangeSliderTrack")?.resizableImage(withCapInsets: insets) 224 | } 225 | 226 | private func fillImage() -> UIImage? { 227 | let insets = UIEdgeInsets(top: 0.0, left: 5.0, bottom: 0.0, right: 4.0) 228 | return imageNamed("RangeSliderFill")?.resizableImage(withCapInsets: insets) 229 | } 230 | 231 | private func leftHandleImage() -> UIImage? { 232 | return imageNamed("RangeSliderLeftHandle") 233 | } 234 | 235 | private func rightHandleImage() -> UIImage? { 236 | return imageNamed("RangeSliderRightHandle") 237 | } 238 | -------------------------------------------------------------------------------- /InAppViewDebugger/Snapshot.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Snapshot.swift 3 | // InAppViewDebugger 4 | // 5 | // Created by Indragie Karunaratne on 3/31/19. 6 | // Copyright © 2019 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreGraphics 11 | 12 | /// A snapshot of the UI element tree in its current state. 13 | @objc(IAVDSnapshot) public final class Snapshot: NSObject { 14 | /// Unique identifier for the snapshot. 15 | @objc public let identifier = UUID().uuidString 16 | 17 | /// Identifying information for the element, like its name and classification. 18 | @objc public let label: ElementLabel 19 | 20 | /// The frame of the element in its parent's coordinate space. 21 | @objc public let frame: CGRect 22 | 23 | /// Whether the element is hidden from view or not. 24 | @objc public let isHidden: Bool 25 | 26 | /// A snapshot image of the element in its current state. 27 | @objc public let snapshotImage: CGImage? 28 | 29 | /// The child snapshots of the snapshot (one per child element). 30 | @objc public let children: [Snapshot] 31 | 32 | /// The element used to create the snapshot. 33 | @objc public let element: Element 34 | 35 | /// Constructs a new `Snapshot` 36 | /// 37 | /// - Parameter element: The element to construct the snapshot from. The 38 | /// data stored in the snapshot will be the data provided by the element 39 | /// at the time that this constructor is called. 40 | @objc public init(element: Element) { 41 | self.label = element.label 42 | self.frame = element.frame 43 | self.isHidden = element.isHidden 44 | self.snapshotImage = element.snapshotImage 45 | self.children = element.children.map { Snapshot(element: $0) } 46 | self.element = element 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /InAppViewDebugger/SnapshotActionSheetUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SnapshotActionSheetUtils.swift 3 | // InAppViewDebugger 4 | // 5 | // Created by Indragie Karunaratne on 4/9/19. 6 | // Copyright © 2019 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | func makeActionSheet(snapshot: Snapshot, sourceView: UIView, sourcePoint: CGPoint, focusAction: @escaping (Snapshot) -> Void) -> UIAlertController { 12 | let actionSheet = UIAlertController(title: nil, message: snapshot.element.description, preferredStyle: .actionSheet) 13 | actionSheet.addAction(UIAlertAction(title: NSLocalizedString("Focus", comment: "Focus on the hierarchy associated with this element"), style: .default) { _ in 14 | focusAction(snapshot) 15 | }) 16 | actionSheet.addAction(UIAlertAction(title: NSLocalizedString("Log Description", comment: "Log the description of this element"), style: .default) { _ in 17 | print(snapshot.element) 18 | }) 19 | let cancel = UIAlertAction(title: NSLocalizedString("Cancel", comment: "Cancel the action"), style: .cancel, handler: nil) 20 | actionSheet.addAction(cancel) 21 | actionSheet.preferredAction = cancel 22 | actionSheet.popoverPresentationController?.sourceView = sourceView 23 | actionSheet.popoverPresentationController?.sourceRect = CGRect(origin: sourcePoint, size: .zero) 24 | return actionSheet 25 | } 26 | -------------------------------------------------------------------------------- /InAppViewDebugger/SnapshotViewConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SnapshotViewConfiguration.swift 3 | // InAppViewDebugger 4 | // 5 | // Created by Indragie Karunaratne on 3/31/19. 6 | // Copyright © 2019 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Configuration options for the 3D snapshot view. 12 | @objc(IAVDSnapshotViewConfiguration) public final class SnapshotViewConfiguration: NSObject { 13 | /// Attributes used to customize the header rendered above the UI element. 14 | @objc(IAVDSnapshotViewHeaderAttributes) public final class HeaderAttributes: NSObject { 15 | /// The background color of the header rendered above each view 16 | /// that has name text. 17 | @objc public var color = UIColor.darkGray 18 | 19 | /// The corner radius of the header background. 20 | @objc public var cornerRadius: CGFloat = 8.0 21 | 22 | /// The top and bottom inset between the header and the name text. 23 | @objc public var verticalInset: CGFloat = 8.0 24 | 25 | /// The font used to render the name text in the header. 26 | @objc public var font = UIFont.boldSystemFont(ofSize: 13) 27 | } 28 | 29 | /// The spacing between layers along the z-axis. 30 | @objc public var zSpacing: Float = 50.0 31 | 32 | /// The minimum spacing between layers along the z-axis. 33 | @objc public var minimumZSpacing: Float = 0.0 34 | 35 | /// The maximum spacing between layers on the z-axis. 36 | @objc public var maximumZSpacing: Float = 100.0 37 | 38 | /// The scene's background color, which gets rendered behind 39 | /// all content. 40 | @objc public var backgroundColor = UIColor.white 41 | 42 | /// The color of the highlight overlaid on top of a UI element when it 43 | /// is selected. 44 | @objc public var highlightColor = UIColor(red: 0.0, green: 0.0, blue: 1.0, alpha: 0.5) 45 | 46 | /// The attributes for a header of normal importance. 47 | @objc public var normalHeaderAttributes: HeaderAttributes = { 48 | var attributes = HeaderAttributes() 49 | attributes.color = UIColor(red: 0.000, green: 0.533, blue: 1.000, alpha: 0.900) 50 | return attributes 51 | }() 52 | 53 | /// The attributes for a header of higher importance. 54 | @objc public var importantHeaderAttributes: HeaderAttributes = { 55 | var attributes = HeaderAttributes() 56 | attributes.color = UIColor(red: 0.961, green: 0.651, blue: 0.137, alpha: 0.900) 57 | return attributes 58 | }() 59 | 60 | /// The font used to render the description for a selected element. 61 | @objc public var descriptionFont = UIFont.systemFont(ofSize: 14) 62 | } 63 | -------------------------------------------------------------------------------- /InAppViewDebugger/SnapshotViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SnapshotViewController.swift 3 | // LiveSnapshot 4 | // 5 | // Created by Indragie Karunaratne on 3/30/19. 6 | // Copyright © 2019 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol SnapshotViewControllerDelegate: AnyObject { 12 | func snapshotViewController(_ viewController: SnapshotViewController, didSelectSnapshot snapshot: Snapshot) 13 | func snapshotViewController(_ viewController: SnapshotViewController, didDeselectSnapshot snapshot: Snapshot) 14 | func snapshotViewController(_ viewController: SnapshotViewController, didFocusOnSnapshot snapshot: Snapshot) 15 | func snapshotViewControllerWillNavigateBackToPreviousSnapshot(_ viewController: SnapshotViewController) 16 | } 17 | 18 | /// View controller that renders a 3D snapshot view using SceneKit. 19 | final class SnapshotViewController: UIViewController, SnapshotViewDelegate, SnapshotViewControllerDelegate { 20 | private let snapshot: Snapshot 21 | private let configuration: SnapshotViewConfiguration 22 | 23 | private var snapshotView: SnapshotView? 24 | weak var delegate: SnapshotViewControllerDelegate? 25 | 26 | init(snapshot: Snapshot, configuration: SnapshotViewConfiguration = SnapshotViewConfiguration()) { 27 | self.snapshot = snapshot 28 | self.configuration = configuration 29 | 30 | super.init(nibName: nil, bundle: nil) 31 | 32 | navigationItem.title = snapshot.element.label.name 33 | } 34 | 35 | required init?(coder aDecoder: NSCoder) { 36 | return nil 37 | } 38 | 39 | override func loadView() { 40 | let snapshotView = SnapshotView(snapshot: snapshot, configuration: configuration) 41 | snapshotView.delegate = self 42 | self.snapshotView = snapshotView 43 | self.view = snapshotView 44 | } 45 | 46 | override func viewWillDisappear(_ animated: Bool) { 47 | super.viewWillDisappear(animated) 48 | if isMovingFromParent { 49 | snapshotView?.deselectAll() 50 | delegate?.snapshotViewControllerWillNavigateBackToPreviousSnapshot(self) 51 | } 52 | } 53 | 54 | // MARK: API 55 | 56 | func select(snapshot: Snapshot) { 57 | let topViewController = topSnapshotViewController() 58 | if topViewController == self { 59 | snapshotView?.select(snapshot: snapshot) 60 | } else { 61 | topViewController.select(snapshot: snapshot) 62 | } 63 | } 64 | 65 | func deselect(snapshot: Snapshot) { 66 | let topViewController = topSnapshotViewController() 67 | if topViewController == self { 68 | snapshotView?.deselect(snapshot: snapshot) 69 | } else { 70 | topViewController.deselect(snapshot: snapshot) 71 | } 72 | } 73 | 74 | func focus(snapshot: Snapshot) { 75 | focus(snapshot: snapshot, callDelegate: false) 76 | } 77 | 78 | // MARK: SnapshotViewDelegate 79 | 80 | func snapshotView(_ snapshotView: SnapshotView, didSelectSnapshot snapshot: Snapshot) { 81 | delegate?.snapshotViewController(self, didSelectSnapshot: snapshot) 82 | } 83 | 84 | func snapshotView(_ snapshotView: SnapshotView, didDeselectSnapshot snapshot: Snapshot) { 85 | delegate?.snapshotViewController(self, didDeselectSnapshot: snapshot) 86 | } 87 | 88 | func snapshotView(_ snapshotView: SnapshotView, didLongPressSnapshot snapshot: Snapshot, point: CGPoint) { 89 | let actionSheet = makeActionSheet(snapshot: snapshot, sourceView: snapshotView, sourcePoint: point) { snapshot in 90 | self.focus(snapshot: snapshot, callDelegate: true) 91 | } 92 | present(actionSheet, animated: true, completion: nil) 93 | } 94 | 95 | func snapshotView(_ snapshotView: SnapshotView, showAlertController alertController: UIAlertController) { 96 | present(alertController, animated: true, completion: nil) 97 | } 98 | 99 | // MARK: SnapshotViewControllerDelegate 100 | 101 | func snapshotViewController(_ viewController: SnapshotViewController, didSelectSnapshot snapshot: Snapshot) { 102 | delegate?.snapshotViewController(self, didSelectSnapshot: snapshot) 103 | } 104 | 105 | func snapshotViewController(_ viewController: SnapshotViewController, didDeselectSnapshot snapshot: Snapshot) { 106 | delegate?.snapshotViewController(self, didDeselectSnapshot: snapshot) 107 | } 108 | 109 | func snapshotViewController(_ viewController: SnapshotViewController, didFocusOnSnapshot snapshot: Snapshot) { 110 | delegate?.snapshotViewController(self, didFocusOnSnapshot: snapshot) 111 | } 112 | 113 | func snapshotViewControllerWillNavigateBackToPreviousSnapshot(_ viewController: SnapshotViewController) { 114 | delegate?.snapshotViewControllerWillNavigateBackToPreviousSnapshot(self) 115 | } 116 | 117 | // MARK: Private 118 | 119 | private func focus(snapshot: Snapshot, callDelegate: Bool) { 120 | let topViewController = topSnapshotViewController() 121 | if topViewController == self { 122 | snapshotView?.deselectAll() 123 | let subtreeViewController = SnapshotViewController(snapshot: snapshot, configuration: configuration) 124 | subtreeViewController.delegate = self 125 | navigationController?.pushViewController(subtreeViewController, animated: true) 126 | if callDelegate { 127 | delegate?.snapshotViewController(self, didFocusOnSnapshot: snapshot) 128 | } 129 | } else { 130 | topViewController.focus(snapshot: snapshot) 131 | } 132 | } 133 | 134 | private func topSnapshotViewController() -> SnapshotViewController { 135 | if let snapshotViewController = navigationController?.topViewController as? SnapshotViewController { 136 | return snapshotViewController 137 | } 138 | return self 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /InAppViewDebugger/TreeTableViewDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TreeTableViewDataSource.swift 3 | // InAppViewDebugger 4 | // 5 | // Created by Indragie Karunaratne on 4/6/19. 6 | // Copyright © 2019 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol Tree { 12 | var children: [Self] { get } 13 | } 14 | 15 | final class TreeTableViewDataSource: NSObject, UITableViewDataSource { 16 | typealias CellFactory = (UITableView /* tableView */, TreeType /* value */, Int /* depth */, IndexPath /* indexPath */, Bool /* isCollapsed */) -> UITableViewCell 17 | 18 | private let tree: TreeType 19 | private let cellFactory: CellFactory 20 | private let flattenedTree: [FlattenedTree] 21 | 22 | init(tree: TreeType, maxDepth: Int?, cellFactory: @escaping CellFactory) { 23 | self.tree = tree 24 | self.cellFactory = cellFactory 25 | self.flattenedTree = flatten(tree: tree, depth: 0, maxDepth: maxDepth) 26 | } 27 | 28 | public func value(atIndexPath indexPath: IndexPath) -> TreeType { 29 | return flattenedTree[indexPath.row].value 30 | } 31 | 32 | // MARK: UITableViewDataSource 33 | 34 | func numberOfSections(in tableView: UITableView) -> Int { 35 | return 1 36 | } 37 | 38 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 39 | return flattenedTree.count 40 | } 41 | 42 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 43 | let tree = flattenedTree[indexPath.row] 44 | return cellFactory(tableView, tree.value, tree.depth, indexPath, tree.isCollapsed) 45 | } 46 | } 47 | 48 | extension TreeTableViewDataSource where TreeType: AnyObject { 49 | func indexPath(forValue value: TreeType) -> IndexPath? { 50 | return flattenedTree 51 | .firstIndex { $0.value === value } 52 | .flatMap { IndexPath(row: $0, section: 0) } 53 | } 54 | } 55 | 56 | private struct FlattenedTree { 57 | let value: TreeType 58 | let depth: Int 59 | var isCollapsed = false 60 | 61 | init(value: TreeType, depth: Int) { 62 | self.value = value 63 | self.depth = depth 64 | } 65 | } 66 | 67 | private func flatten(tree: TreeType, depth: Int = 0, maxDepth: Int?) -> [FlattenedTree] { 68 | let initial = [FlattenedTree(value: tree, depth: depth)] 69 | let childDepth = depth + 1 70 | if let maxDepth = maxDepth, childDepth > maxDepth { 71 | return initial 72 | } else { 73 | return tree.children.reduce(initial) { (result, child) in 74 | var newResult = result 75 | newResult.append(contentsOf: flatten(tree: child, depth: childDepth, maxDepth: maxDepth)) 76 | return newResult 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /InAppViewDebugger/VIewDebuggerViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VIewDebuggerViewController.swift 3 | // InAppViewDebugger 4 | // 5 | // Created by Indragie Karunaratne on 4/4/19. 6 | // Copyright © 2019 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Root view controller for the view debugger. 12 | final class ViewDebuggerViewController: UIViewController, SnapshotViewControllerDelegate, HierarchyTableViewControllerDelegate { 13 | private let snapshot: Snapshot 14 | private let configuration: Configuration 15 | 16 | private var pageViewController: UIPageViewController? 17 | 18 | private lazy var snapshotViewController: SnapshotViewController = { [unowned self] in 19 | let viewController = SnapshotViewController(snapshot: snapshot, configuration: configuration.snapshotViewConfiguration) 20 | viewController.delegate = self 21 | return viewController 22 | }() 23 | 24 | private lazy var snapshotNavigationController: UINavigationController = { 25 | let navigationController = UINavigationController(rootViewController: snapshotViewController) 26 | navigationController.navigationBar.isHidden = true 27 | navigationController.title = NSLocalizedString("Snapshot", comment: "The title for the Snapshot tab") 28 | return navigationController 29 | }() 30 | 31 | private lazy var hierarchyViewController: HierarchyTableViewController = { 32 | let viewController = HierarchyTableViewController(snapshot: snapshot, configuration: configuration.hierarchyViewConfiguration) 33 | viewController.delegate = self 34 | return viewController 35 | }() 36 | 37 | private lazy var hierarchyNavigationController: UINavigationController = { 38 | let navigationController = UINavigationController(rootViewController: hierarchyViewController) 39 | navigationController.navigationBar.isHidden = true 40 | navigationController.title = NSLocalizedString("Hierarchy", comment: "The title for the Hierarchy tab") 41 | return navigationController 42 | }() 43 | 44 | init(snapshot: Snapshot, configuration: Configuration = Configuration()) { 45 | self.snapshot = snapshot 46 | self.configuration = configuration 47 | 48 | super.init(nibName: nil, bundle: nil) 49 | } 50 | 51 | required init?(coder aDecoder: NSCoder) { 52 | fatalError("init(coder:) has not been implemented") 53 | } 54 | 55 | override func viewDidLoad() { 56 | super.viewDidLoad() 57 | 58 | if traitCollection.userInterfaceIdiom == .phone { 59 | configureSegmentedControl() 60 | configurePageViewController() 61 | } else { 62 | navigationItem.title = snapshot.element.shortDescription 63 | configureSplitViewController() 64 | } 65 | 66 | navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(done(sender:))) 67 | } 68 | 69 | // MARK: SnapshotViewControllerDelegate 70 | 71 | func snapshotViewController(_ viewController: SnapshotViewController, didSelectSnapshot snapshot: Snapshot) { 72 | hierarchyViewController.selectRow(forSnapshot: snapshot) 73 | } 74 | 75 | func snapshotViewController(_ viewController: SnapshotViewController, didDeselectSnapshot snapshot: Snapshot) { 76 | hierarchyViewController.deselectRow(forSnapshot: snapshot) 77 | } 78 | 79 | func snapshotViewController(_ viewController: SnapshotViewController, didFocusOnSnapshot snapshot: Snapshot) { 80 | hierarchyNavigationController.popToRootViewController(animated: false) 81 | hierarchyViewController.focus(snapshot: snapshot) 82 | } 83 | 84 | func snapshotViewControllerWillNavigateBackToPreviousSnapshot(_ viewController: SnapshotViewController) { 85 | hierarchyNavigationController.popViewController(animated: true) 86 | } 87 | 88 | // MARK: HierarchyTableViewControllerDelegate 89 | 90 | func hierarchyTableViewController(_ viewController: HierarchyTableViewController, didSelectSnapshot snapshot: Snapshot) { 91 | snapshotViewController.select(snapshot: snapshot) 92 | } 93 | 94 | func hierarchyTableViewController(_ viewController: HierarchyTableViewController, didDeselectSnapshot snapshot: Snapshot) { 95 | snapshotViewController.deselect(snapshot: snapshot) 96 | } 97 | 98 | func hierarchyTableViewController(_ viewController: HierarchyTableViewController, didFocusOnSnapshot snapshot: Snapshot) { 99 | snapshotViewController.focus(snapshot: snapshot) 100 | } 101 | 102 | func hierarchyTableViewControllerWillNavigateBackToPreviousSnapshot(_ viewController: HierarchyTableViewController) { 103 | snapshotNavigationController.popViewController(animated: true) 104 | } 105 | 106 | // MARK: Private 107 | 108 | private func configurePageViewController() { 109 | let pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil) 110 | showChildViewController(pageViewController) 111 | self.pageViewController = pageViewController 112 | selectViewController(index: 0) 113 | } 114 | 115 | private func configureSplitViewController() { 116 | let splitViewController = UISplitViewController(nibName: nil, bundle: nil) 117 | splitViewController.viewControllers = [hierarchyNavigationController, snapshotNavigationController] 118 | showChildViewController(splitViewController) 119 | navigationItem.leftItemsSupplementBackButton = true 120 | navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem 121 | } 122 | 123 | private func showChildViewController(_ childViewController: UIViewController) { 124 | addChild(childViewController) 125 | 126 | if let childView = childViewController.view { 127 | childView.translatesAutoresizingMaskIntoConstraints = false 128 | view.addSubview(childView) 129 | NSLayoutConstraint.activate([ 130 | childView.leadingAnchor.constraint(equalTo: view.leadingAnchor), 131 | childView.trailingAnchor.constraint(equalTo: view.trailingAnchor), 132 | childView.topAnchor.constraint(equalTo: view.topAnchor), 133 | childView.bottomAnchor.constraint(equalTo: view.bottomAnchor), 134 | ]) 135 | } 136 | 137 | childViewController.didMove(toParent: self) 138 | } 139 | 140 | private func configureSegmentedControl() { 141 | let segmentedControl = UISegmentedControl(items: [ 142 | NSLocalizedString("Snapshot", comment: "The title for the Snapshot tab"), 143 | NSLocalizedString("Hierarchy", comment: "The title for the Hierarchy tab"), 144 | ]) 145 | segmentedControl.sizeToFit() 146 | segmentedControl.selectedSegmentIndex = 0 147 | segmentedControl.addTarget(self, action: #selector(segmentChanged(sender:)), for: .valueChanged) 148 | navigationItem.title = nil 149 | navigationItem.titleView = segmentedControl 150 | } 151 | 152 | private func selectViewController(index: Int) { 153 | guard let pageViewController = pageViewController else { 154 | return 155 | } 156 | switch index { 157 | case 0: 158 | pageViewController.setViewControllers([snapshotNavigationController], direction: .reverse, animated: false, completion: nil) 159 | case 1: 160 | pageViewController.setViewControllers([hierarchyNavigationController], direction: .forward, animated: false, completion: nil) 161 | default: 162 | fatalError("Invalid view controller index \(index)") 163 | break 164 | } 165 | } 166 | 167 | // MARK: Actions 168 | 169 | @objc private func segmentChanged(sender: UISegmentedControl) { 170 | selectViewController(index: sender.selectedSegmentIndex) 171 | } 172 | 173 | @objc private func done(sender: UIBarButtonItem) { 174 | dismiss(animated: true, completion: nil) 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /InAppViewDebugger/ViewControllerUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewUtils.swift 3 | // InAppViewDebugger 4 | // 5 | // Created by Indragie Karunaratne on 4/4/19. 6 | // Copyright © 2019 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | func getNearestAncestorViewController(responder: UIResponder) -> UIViewController? { 12 | if let viewController = responder as? UIViewController { 13 | return viewController 14 | } else if let nextResponder = responder.next { 15 | return getNearestAncestorViewController(responder: nextResponder) 16 | } 17 | return nil 18 | } 19 | 20 | func topViewController(rootViewController: UIViewController?) -> UIViewController? { 21 | guard let rootViewController = rootViewController else { 22 | return nil 23 | } 24 | guard let presentedViewController = rootViewController.presentedViewController else { 25 | return rootViewController 26 | } 27 | 28 | if let navigationController = presentedViewController as? UINavigationController { 29 | return topViewController(rootViewController: navigationController.viewControllers.last) 30 | } else if let tabBarController = presentedViewController as? UITabBarController { 31 | return topViewController(rootViewController: tabBarController.selectedViewController) 32 | } else { 33 | return topViewController(rootViewController: presentedViewController) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /InAppViewDebugger/ViewElement.swift: -------------------------------------------------------------------------------- 1 | // 2 | // View+Element.swift 3 | // LiveSnapshot 4 | // 5 | // Created by Indragie Karunaratne on 3/30/19. 6 | // Copyright © 2019 Indragie Karunaratne. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// An element that represents a UIView. 12 | @objc(IAVDViewElement) public final class ViewElement: NSObject, Element { 13 | public var label: ElementLabel { 14 | guard let view = view else { 15 | return ElementLabel(name: nil) 16 | } 17 | if let viewController = getViewController(view: view) { 18 | let name = "\(String(describing: Swift.type(of: viewController))) (\(String(describing: Swift.type(of: view))))" 19 | return ElementLabel(name: name, classification: .important) 20 | } else { 21 | return ElementLabel(name: String(describing: Swift.type(of: view))) 22 | } 23 | } 24 | 25 | public var frame: CGRect { 26 | let offset = contentOffsetForView(view) 27 | return view?.frame.offsetBy(dx: offset.x, dy: offset.y) ?? .zero 28 | } 29 | 30 | public var isHidden: Bool { 31 | return view?.isHidden ?? false 32 | } 33 | 34 | public var snapshotImage: CGImage? { 35 | guard let view = view else { 36 | return nil 37 | } 38 | return snapshotView(view) 39 | } 40 | 41 | public var children: [Element] { 42 | guard let view = view else { 43 | return [] 44 | } 45 | return view.subviews.map { ViewElement(view: $0) } 46 | } 47 | 48 | public var shortDescription: String { 49 | guard let view = view else { 50 | return "" 51 | } 52 | let frame = view.frame 53 | return String(format: "%@: %p (%.1f, %.1f, %.1f, %.1f)", String(describing: type(of: view)), view, frame.origin.x, frame.origin.y, frame.size.width, frame.size.height) 54 | } 55 | 56 | override public var description: String { 57 | guard let view = view else { 58 | return "" 59 | } 60 | return view.description 61 | } 62 | 63 | private weak var view: UIView? 64 | 65 | /// Constructs a new `ViewElement` 66 | /// 67 | /// - Parameter view: The `UIView` to create the element for. 68 | @objc public init(view: UIView) { 69 | self.view = view 70 | } 71 | } 72 | 73 | fileprivate func getViewController(view: UIView) -> UIViewController? { 74 | if let viewController = getNearestAncestorViewController(responder: view), viewController.viewIfLoaded == view { 75 | return viewController 76 | } 77 | return nil 78 | } 79 | 80 | fileprivate func drawView(_ view: UIView) -> CGImage? { 81 | let renderer = UIGraphicsImageRenderer(size: view.bounds.size) 82 | let image = renderer.image { context in 83 | view.drawHierarchy(in: view.bounds, afterScreenUpdates: true) 84 | } 85 | return image.cgImage 86 | } 87 | 88 | fileprivate func hideViewsOnTopOf(view: UIView, root: UIView, hiddenViews: inout [UIView]) -> Bool { 89 | if root == view { 90 | return true 91 | } 92 | var foundView = false 93 | for subview in root.subviews.reversed() { 94 | if hideViewsOnTopOf(view: view, root: subview, hiddenViews: &hiddenViews) { 95 | foundView = true 96 | break 97 | } 98 | } 99 | if !foundView { 100 | if !root.isHidden { 101 | hiddenViews.append(root) 102 | } 103 | root.isHidden = true 104 | } 105 | return foundView 106 | } 107 | 108 | fileprivate func snapshotVisualEffectBackdropView(_ view: UIView) -> CGImage? 109 | { 110 | guard let window = view.window else { 111 | return nil 112 | } 113 | var hiddenViews = [UIView]() 114 | defer { 115 | hiddenViews.forEach { $0.isHidden = false } 116 | } 117 | // UIVisualEffectView is a special case that cannot be snapshotted 118 | // the same way as any other view. From Apple docs: 119 | // 120 | // Many effects require support from the window that hosts the 121 | // UIVisualEffectView. Attempting to take a snapshot of only the 122 | // UIVisualEffectView will result in a snapshot that does not 123 | // contain the effect. To take a snapshot of a view hierarchy 124 | // that contains a UIVisualEffectView, you must take a snapshot 125 | // of the entire UIWindow or UIScreen that contains it. 126 | // 127 | // To snapshot this view, we traverse the view hierarchy starting 128 | // from the window and hide any views that are on top of the 129 | // _UIVisualEffectBackdropView so that it is visible in a snapshot 130 | // of the window. We then take a snapshot of the window and crop 131 | // it to the part that contains the backdrop view. This appears to 132 | // be the same technique that Xcode's own view debugger uses to 133 | // snapshot visual effect views. 134 | if hideViewsOnTopOf(view: view, root: window, hiddenViews: &hiddenViews) { 135 | let image = drawView(window) 136 | let cropRect = window.convert(view.bounds, from: view) 137 | return image?.cropping(to: cropRect) 138 | } 139 | return nil 140 | } 141 | 142 | fileprivate func snapshotView(_ view: UIView) -> CGImage? { 143 | if let superview = view.superview, let _ = superview as? UIVisualEffectView, 144 | superview.subviews.first == view { 145 | return snapshotVisualEffectBackdropView(view) 146 | } 147 | var subviewHidden = [Bool]() 148 | subviewHidden.reserveCapacity(view.subviews.count) 149 | for subview in view.subviews { 150 | subviewHidden.append(subview.isHidden) 151 | subview.isHidden = true 152 | } 153 | let image = drawView(view) 154 | for (subview, isHidden) in zip(view.subviews, subviewHidden) { 155 | subview.isHidden = isHidden 156 | } 157 | return image 158 | } 159 | 160 | fileprivate func contentOffsetForView(_ view: UIView?) -> CGPoint { 161 | guard let scrollView = view?.superview as? UIScrollView else { return .zero } 162 | let contentOffset = scrollView.contentOffset 163 | return CGPoint(x: -contentOffset.x, y: -contentOffset.y) 164 | } 165 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Indragie Karunaratne 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "InAppViewDebugger", 7 | platforms: [ 8 | .iOS(.v13), 9 | .watchOS(.v6) 10 | ], 11 | products: [ 12 | .library( 13 | name: "InAppViewDebugger", 14 | targets: ["InAppViewDebugger"]), 15 | ], 16 | targets: [ 17 | .target( 18 | name: "InAppViewDebugger", 19 | dependencies: [], 20 | path: "InAppViewDebugger", 21 | exclude: ["Info.plist", "BUILD"] 22 | ), 23 | ] 24 | ) 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # InAppViewDebugger 2 | 3 | [![License](https://img.shields.io/github/license/indragiek/InAppViewDebugger.svg)](LICENSE) 4 | [![CocoaPods](https://img.shields.io/cocoapods/v/InAppViewDebugger.svg)](https://cocoapods.org/?q=InAppViewDebugger) 5 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 6 | 7 |

8 | InAppViewDebugger 9 |

10 | 11 | `InAppViewDebugger` is a library that implements a view debugger with a 3D snapshot view and a hierarchy view, similar to [Reveal](https://revealapp.com) and [Xcode's own view debugger](https://developer.apple.com/library/archive/documentation/ToolsLanguages/Conceptual/Xcode_Overview/ExaminingtheViewHierarchy.html). The key distinction is, as the project title suggests, that this can be embedded inside the app and used on-device to debug UI issues without needing to be tethered to a computer. 12 | 13 | ## Features 14 | 15 | * **3D snapshot view implemented in SceneKit**: Gesture controls for zooming, panning, and rotating. 16 | * **Hierarchy (tree) view that synchronizes its selection with the 3D view**: This is a feature I really wanted in Xcode, to be able to visually find a view and see where it is in the hierarchy view 17 | * **Support for [iPad](docs/img/main.png) and [iPhone](docs/img/iphone1.png)**: Layouts are designed specifically for each form factor. 18 | * **Extensible:** The base implementation supports `UIView` hierarchies, but this is easily extensible to support any kind of UI framework (e.g. CoreAnimation or SpriteKit) 19 | 20 | ## Requirements 21 | 22 | * iOS 11.0+ 23 | * Xcode 10.1+ (framework built for Swift 4.2) 24 | 25 | ## Installation 26 | 27 | ### CocoaPods 28 | 29 | Add the following line to your `Podfile`: 30 | 31 | ```ruby 32 | pod 'InAppViewDebugger', '~> 1.0.3' 33 | ``` 34 | 35 | ### Carthage 36 | 37 | Add the following line to your `Cartfile`: 38 | 39 | ``` 40 | github "indragiek/InAppViewDebugger" "1.0.3" 41 | ``` 42 | 43 | ## Usage 44 | 45 | ### Swift 46 | 47 | ```swift 48 | import InAppViewDebugger 49 | 50 | @IBAction func showViewDebugger(sender: AnyObject) { 51 | InAppViewDebugger.present() 52 | } 53 | ``` 54 | 55 | ### Objective-C 56 | 57 | ```objc 58 | @import InAppViewDebugger; 59 | 60 | // alternative import (they're the same): 61 | // #import 62 | 63 | - (IBAction)showViewDebugger:(id)sender { 64 | [InAppViewDebugger present]; 65 | } 66 | ``` 67 | 68 | ### `lldb` 69 | 70 | ``` 71 | (lldb) expr -lswift -- import InAppViewDebugger 72 | (lldb) expr -lswift -- InAppViewDebugger.present() 73 | ``` 74 | 75 | The `present` function shows the UI hierarchy for your application's key window, presented over the top view controller of the window's root view controller. There are several other methods available on `InAppViewDebugger` for presenting a view debugger for a given window, view, or view controller. 76 | 77 | ## Controls 78 | 79 | ### Focusing on an Element 80 | 81 | To focus on the subhierarchy of a particular element, **long press on the element** to bring up the action menu and tap "Focus". The long press can be used both in the hierarchy view and the 3D snapshot view. The "Log Description" action will log the description of the element to the console, so that if you're attached to Xcode you can copy the address of the object for further debugging. 82 | 83 |

84 | Focusing on an Element 85 |

86 | 87 | ### Adjusting Distance Between Levels 88 | 89 | The slider on the bottom left of the snapshot view can be used to adjust the spacing between levels of the hierarchy: 90 | 91 |

92 | Adjusting Distance Between Levels 93 |

94 | 95 | ### Adjusting Visible Levels 96 | 97 | The range slider on the bottom right of the snapshot view can be used to adjust the range of levels in the hierarchy that are visible: 98 | 99 |

100 | Adjusting Visible Levels 101 |

102 | 103 | ### Showing/Hiding Headers 104 | 105 | Each UI element has a header above it that shows its class name. These headers can be hidden or shown by **long pressing on an empty area of the snapshot view** to bring up the action menu: 106 | 107 |

108 | Showing/Hiding Headers 109 |

110 | 111 | ### Showing/Hiding Borders 112 | 113 | Similarly to the headers, the borders drawn around each element can also be shown or hidden: 114 | 115 |

116 | Showing/Hiding Borders 117 |

118 | 119 | ## Customization 120 | 121 | Colors, fonts, and other attributes for both the snapshot view and the hierarchy view can be changed by creating a custom [`Configuration`](InAppViewDebugger/Configuration.swift). The configuration is then passed to a function like `InAppViewDebugger.presentForWindow(:configuration:completion:)`. 122 | 123 | ## Extending for Other UI Frameworks 124 | 125 | The current implementation only supports `UIView` hierarchies, but this can easily be extended to support other UI frameworks by conforming to the [`Element`](InAppViewDebugger/Element.swift) protocol. See [`ViewElement`](InAppViewDebugger/ViewElement.swift) to see what an example implementation looks like — by providing a the frame, a snapshot image, and a few other pieces of information, all of the features described above will work for your own framework. 126 | 127 | A [`Snapshot`](InAppViewDebugger/Snapshot.swift) instance represents a recursive snapshot of the *current state* of a UI element hierarchy, and is constructed using an `Element`. The snapshot can then be passed to 128 | ```swift 129 | InAppViewDebugger.presentWithSnapshot(:rootViewController:configuration:completion:) 130 | ``` 131 | to show the view debugger. 132 | 133 | ## Credits 134 | 135 | * [Kyle Van Essen](https://twitter.com/kyleve) for [this tweet](https://twitter.com/kyleve/status/1111689823759171585) picturing Square's implementation that inspired me to build this 136 | * [AudioKit SynthOne](https://github.com/AudioKit/AudioKitSynthOne), an amazing open-source audio synthesizer app for the iPad that made for a great demo as pictured above 137 | 138 | ## Contact 139 | 140 | * Indragie Karunaratne 141 | * [@indragie](http://twitter.com/indragie) 142 | * [http://indragie.com](http://indragie.com) 143 | 144 | ## License 145 | 146 | `InAppViewDebugger` is licensed under the MIT License. See `LICENSE` for more information. 147 | -------------------------------------------------------------------------------- /WORKSPACE: -------------------------------------------------------------------------------- 1 | workspace(name = "InAppViewDebugger") 2 | -------------------------------------------------------------------------------- /docs/Classes/Configuration.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Configuration Class Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |

23 | 24 | InAppViewDebugger Docs 25 | 26 | (100% documented) 27 |

28 | 29 |

30 |

31 | 32 |
33 |

34 | 35 |

36 | 37 | 38 | View on GitHub 39 | 40 |

41 | 42 |
43 | 44 | 49 | 50 |
51 | 95 |
96 | 97 |
98 |
99 |

Configuration

100 |
101 |
102 |
@objc(IAVDConfiguration)
103 | public final class Configuration : NSObject
104 | 105 |
106 |
107 |

Configuration options for the in app view debugger.

108 | 109 |
110 |
111 | 112 |
113 |
114 |
115 |
    116 |
  • 117 |
    118 | 119 | 120 | 121 | snapshotViewConfiguration 122 | 123 |
    124 |
    125 |
    126 |
    127 |
    128 |
    129 |

    Configuration for the 3D snapshot view.

    130 | 131 |
    132 |
    133 |

    Declaration

    134 |
    135 |

    Swift

    136 |
    @objc
    137 | public var snapshotViewConfiguration: SnapshotViewConfiguration
    138 | 139 |
    140 |
    141 |
    142 | Show on GitHub 143 |
    144 |
    145 |
    146 |
  • 147 |
  • 148 |
    149 | 150 | 151 | 152 | hierarchyViewConfiguration 153 | 154 |
    155 |
    156 |
    157 |
    158 |
    159 |
    160 |

    Configuration for the hierarchy (tree) view.

    161 | 162 |
    163 |
    164 |

    Declaration

    165 |
    166 |

    Swift

    167 |
    @objc
    168 | public var hierarchyViewConfiguration: HierarchyViewConfiguration
    169 | 170 |
    171 |
    172 |
    173 | Show on GitHub 174 |
    175 |
    176 |
    177 |
  • 178 |
179 |
180 |
181 |
182 | 183 |
184 |
185 | 189 | 190 | 191 | 192 | -------------------------------------------------------------------------------- /docs/Classes/ElementLabel/Classification.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Classification Enumeration Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |

23 | 24 | InAppViewDebugger Docs 25 | 26 | (100% documented) 27 |

28 | 29 |

30 |

31 | 32 |
33 |

34 | 35 |

36 | 37 | 38 | View on GitHub 39 | 40 |

41 | 42 |
43 | 44 | 49 | 50 |
51 | 95 |
96 | 97 |
98 |
99 |

Classification

100 |
101 |
102 |
@objc(IAVDElementClassification)
103 | public enum Classification : Int
104 | 105 |
106 |
107 |

Classification for an element that determines how it is represented 108 | in the view debugger.

109 | 110 |
111 |
112 | 113 |
114 |
115 |
116 |
    117 |
  • 118 |
    119 | 120 | 121 | 122 | normal 123 | 124 |
    125 |
    126 |
    127 |
    128 |
    129 |
    130 |

    An element of normal importance.

    131 | 132 |
    133 |
    134 |

    Declaration

    135 |
    136 |

    Swift

    137 |
    case normal
    138 | 139 |
    140 |
    141 |
    142 | Show on GitHub 143 |
    144 |
    145 |
    146 |
  • 147 |
  • 148 |
    149 | 150 | 151 | 152 | important 153 | 154 |
    155 |
    156 |
    157 |
    158 |
    159 |
    160 |

    An element of higher importance that is highlighted

    161 | 162 |
    163 |
    164 |

    Declaration

    165 |
    166 |

    Swift

    167 |
    case important
    168 | 169 |
    170 |
    171 |
    172 | Show on GitHub 173 |
    174 |
    175 |
    176 |
  • 177 |
178 |
179 |
180 |
181 | 182 |
183 |
184 | 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /docs/Protocols.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Protocols Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |

23 | 24 | InAppViewDebugger Docs 25 | 26 | (100% documented) 27 |

28 | 29 |

30 |

31 | 32 |
33 |

34 | 35 |

36 | 37 | 38 | View on GitHub 39 | 40 |

41 | 42 |
43 | 44 | 49 | 50 |
51 | 95 |
96 | 97 |
98 |
99 |

Protocols

100 |

The following protocols are available globally.

101 | 102 |
103 |
104 | 105 |
106 |
107 |
108 |
    109 |
  • 110 |
    111 | 112 | 113 | 114 | Element 115 | 116 |
    117 |
    118 |
    119 |
    120 |
    121 |
    122 |

    A UI element that can be snapshotted.

    123 | 124 | See more 125 |
    126 |
    127 |

    Declaration

    128 |
    129 |

    Swift

    130 |
    @objc(IAVDElement)
    131 | public protocol Element
    132 | 133 |
    134 |
    135 |
    136 | Show on GitHub 137 |
    138 |
    139 |
    140 |
  • 141 |
142 |
143 |
144 |
145 | 146 |
147 |
148 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /docs/badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | documentation 17 | 18 | 19 | documentation 20 | 21 | 22 | 100% 23 | 24 | 25 | 100% 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* Credit to https://gist.github.com/wataru420/2048287 */ 2 | .highlight { 3 | /* Comment */ 4 | /* Error */ 5 | /* Keyword */ 6 | /* Operator */ 7 | /* Comment.Multiline */ 8 | /* Comment.Preproc */ 9 | /* Comment.Single */ 10 | /* Comment.Special */ 11 | /* Generic.Deleted */ 12 | /* Generic.Deleted.Specific */ 13 | /* Generic.Emph */ 14 | /* Generic.Error */ 15 | /* Generic.Heading */ 16 | /* Generic.Inserted */ 17 | /* Generic.Inserted.Specific */ 18 | /* Generic.Output */ 19 | /* Generic.Prompt */ 20 | /* Generic.Strong */ 21 | /* Generic.Subheading */ 22 | /* Generic.Traceback */ 23 | /* Keyword.Constant */ 24 | /* Keyword.Declaration */ 25 | /* Keyword.Pseudo */ 26 | /* Keyword.Reserved */ 27 | /* Keyword.Type */ 28 | /* Literal.Number */ 29 | /* Literal.String */ 30 | /* Name.Attribute */ 31 | /* Name.Builtin */ 32 | /* Name.Class */ 33 | /* Name.Constant */ 34 | /* Name.Entity */ 35 | /* Name.Exception */ 36 | /* Name.Function */ 37 | /* Name.Namespace */ 38 | /* Name.Tag */ 39 | /* Name.Variable */ 40 | /* Operator.Word */ 41 | /* Text.Whitespace */ 42 | /* Literal.Number.Float */ 43 | /* Literal.Number.Hex */ 44 | /* Literal.Number.Integer */ 45 | /* Literal.Number.Oct */ 46 | /* Literal.String.Backtick */ 47 | /* Literal.String.Char */ 48 | /* Literal.String.Doc */ 49 | /* Literal.String.Double */ 50 | /* Literal.String.Escape */ 51 | /* Literal.String.Heredoc */ 52 | /* Literal.String.Interpol */ 53 | /* Literal.String.Other */ 54 | /* Literal.String.Regex */ 55 | /* Literal.String.Single */ 56 | /* Literal.String.Symbol */ 57 | /* Name.Builtin.Pseudo */ 58 | /* Name.Variable.Class */ 59 | /* Name.Variable.Global */ 60 | /* Name.Variable.Instance */ 61 | /* Literal.Number.Integer.Long */ } 62 | .highlight .c { 63 | color: #999988; 64 | font-style: italic; } 65 | .highlight .err { 66 | color: #a61717; 67 | background-color: #e3d2d2; } 68 | .highlight .k { 69 | color: #000000; 70 | font-weight: bold; } 71 | .highlight .o { 72 | color: #000000; 73 | font-weight: bold; } 74 | .highlight .cm { 75 | color: #999988; 76 | font-style: italic; } 77 | .highlight .cp { 78 | color: #999999; 79 | font-weight: bold; } 80 | .highlight .c1 { 81 | color: #999988; 82 | font-style: italic; } 83 | .highlight .cs { 84 | color: #999999; 85 | font-weight: bold; 86 | font-style: italic; } 87 | .highlight .gd { 88 | color: #000000; 89 | background-color: #ffdddd; } 90 | .highlight .gd .x { 91 | color: #000000; 92 | background-color: #ffaaaa; } 93 | .highlight .ge { 94 | color: #000000; 95 | font-style: italic; } 96 | .highlight .gr { 97 | color: #aa0000; } 98 | .highlight .gh { 99 | color: #999999; } 100 | .highlight .gi { 101 | color: #000000; 102 | background-color: #ddffdd; } 103 | .highlight .gi .x { 104 | color: #000000; 105 | background-color: #aaffaa; } 106 | .highlight .go { 107 | color: #888888; } 108 | .highlight .gp { 109 | color: #555555; } 110 | .highlight .gs { 111 | font-weight: bold; } 112 | .highlight .gu { 113 | color: #aaaaaa; } 114 | .highlight .gt { 115 | color: #aa0000; } 116 | .highlight .kc { 117 | color: #000000; 118 | font-weight: bold; } 119 | .highlight .kd { 120 | color: #000000; 121 | font-weight: bold; } 122 | .highlight .kp { 123 | color: #000000; 124 | font-weight: bold; } 125 | .highlight .kr { 126 | color: #000000; 127 | font-weight: bold; } 128 | .highlight .kt { 129 | color: #445588; } 130 | .highlight .m { 131 | color: #009999; } 132 | .highlight .s { 133 | color: #d14; } 134 | .highlight .na { 135 | color: #008080; } 136 | .highlight .nb { 137 | color: #0086B3; } 138 | .highlight .nc { 139 | color: #445588; 140 | font-weight: bold; } 141 | .highlight .no { 142 | color: #008080; } 143 | .highlight .ni { 144 | color: #800080; } 145 | .highlight .ne { 146 | color: #990000; 147 | font-weight: bold; } 148 | .highlight .nf { 149 | color: #990000; } 150 | .highlight .nn { 151 | color: #555555; } 152 | .highlight .nt { 153 | color: #000080; } 154 | .highlight .nv { 155 | color: #008080; } 156 | .highlight .ow { 157 | color: #000000; 158 | font-weight: bold; } 159 | .highlight .w { 160 | color: #bbbbbb; } 161 | .highlight .mf { 162 | color: #009999; } 163 | .highlight .mh { 164 | color: #009999; } 165 | .highlight .mi { 166 | color: #009999; } 167 | .highlight .mo { 168 | color: #009999; } 169 | .highlight .sb { 170 | color: #d14; } 171 | .highlight .sc { 172 | color: #d14; } 173 | .highlight .sd { 174 | color: #d14; } 175 | .highlight .s2 { 176 | color: #d14; } 177 | .highlight .se { 178 | color: #d14; } 179 | .highlight .sh { 180 | color: #d14; } 181 | .highlight .si { 182 | color: #d14; } 183 | .highlight .sx { 184 | color: #d14; } 185 | .highlight .sr { 186 | color: #009926; } 187 | .highlight .s1 { 188 | color: #d14; } 189 | .highlight .ss { 190 | color: #990073; } 191 | .highlight .bp { 192 | color: #999999; } 193 | .highlight .vc { 194 | color: #008080; } 195 | .highlight .vg { 196 | color: #008080; } 197 | .highlight .vi { 198 | color: #008080; } 199 | .highlight .il { 200 | color: #009999; } 201 | -------------------------------------------------------------------------------- /docs/css/jazzy.css: -------------------------------------------------------------------------------- 1 | *, *:before, *:after { 2 | box-sizing: inherit; } 3 | 4 | body { 5 | margin: 0; 6 | background: #fff; 7 | color: #333; 8 | font: 16px/1.7 "Helvetica Neue", Helvetica, Arial, sans-serif; 9 | letter-spacing: .2px; 10 | -webkit-font-smoothing: antialiased; 11 | box-sizing: border-box; } 12 | 13 | h1 { 14 | font-size: 2rem; 15 | font-weight: 700; 16 | margin: 1.275em 0 0.6em; } 17 | 18 | h2 { 19 | font-size: 1.75rem; 20 | font-weight: 700; 21 | margin: 1.275em 0 0.3em; } 22 | 23 | h3 { 24 | font-size: 1.5rem; 25 | font-weight: 700; 26 | margin: 1em 0 0.3em; } 27 | 28 | h4 { 29 | font-size: 1.25rem; 30 | font-weight: 700; 31 | margin: 1.275em 0 0.85em; } 32 | 33 | h5 { 34 | font-size: 1rem; 35 | font-weight: 700; 36 | margin: 1.275em 0 0.85em; } 37 | 38 | h6 { 39 | font-size: 1rem; 40 | font-weight: 700; 41 | margin: 1.275em 0 0.85em; 42 | color: #777; } 43 | 44 | p { 45 | margin: 0 0 1em; } 46 | 47 | ul, ol { 48 | padding: 0 0 0 2em; 49 | margin: 0 0 0.85em; } 50 | 51 | blockquote { 52 | margin: 0 0 0.85em; 53 | padding: 0 15px; 54 | color: #858585; 55 | border-left: 4px solid #e5e5e5; } 56 | 57 | img { 58 | max-width: 100%; } 59 | 60 | a { 61 | color: #4183c4; 62 | text-decoration: none; } 63 | a:hover, a:focus { 64 | outline: 0; 65 | text-decoration: underline; } 66 | a.discouraged { 67 | text-decoration: line-through; } 68 | a.discouraged:hover, a.discouraged:focus { 69 | text-decoration: underline line-through; } 70 | 71 | table { 72 | background: #fff; 73 | width: 100%; 74 | border-collapse: collapse; 75 | border-spacing: 0; 76 | overflow: auto; 77 | margin: 0 0 0.85em; } 78 | 79 | tr:nth-child(2n) { 80 | background-color: #fbfbfb; } 81 | 82 | th, td { 83 | padding: 6px 13px; 84 | border: 1px solid #ddd; } 85 | 86 | pre { 87 | margin: 0 0 1.275em; 88 | padding: .85em 1em; 89 | overflow: auto; 90 | background: #f7f7f7; 91 | font-size: .85em; 92 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; } 93 | 94 | code { 95 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; } 96 | 97 | p > code, li > code { 98 | background: #f7f7f7; 99 | padding: .2em; } 100 | p > code:before, p > code:after, li > code:before, li > code:after { 101 | letter-spacing: -.2em; 102 | content: "\00a0"; } 103 | 104 | pre code { 105 | padding: 0; 106 | white-space: pre; } 107 | 108 | .content-wrapper { 109 | display: flex; 110 | flex-direction: column; } 111 | @media (min-width: 768px) { 112 | .content-wrapper { 113 | flex-direction: row; } } 114 | 115 | .header { 116 | display: flex; 117 | padding: 8px; 118 | font-size: 0.875em; 119 | background: #444; 120 | color: #999; } 121 | 122 | .header-col { 123 | margin: 0; 124 | padding: 0 8px; } 125 | 126 | .header-col--primary { 127 | flex: 1; } 128 | 129 | .header-link { 130 | color: #fff; } 131 | 132 | .header-icon { 133 | padding-right: 6px; 134 | vertical-align: -4px; 135 | height: 16px; } 136 | 137 | .breadcrumbs { 138 | font-size: 0.875em; 139 | padding: 8px 16px; 140 | margin: 0; 141 | background: #fbfbfb; 142 | border-bottom: 1px solid #ddd; } 143 | 144 | .carat { 145 | height: 10px; 146 | margin: 0 5px; } 147 | 148 | .navigation { 149 | order: 2; } 150 | @media (min-width: 768px) { 151 | .navigation { 152 | order: 1; 153 | width: 25%; 154 | max-width: 300px; 155 | padding-bottom: 64px; 156 | overflow: hidden; 157 | word-wrap: normal; 158 | background: #fbfbfb; 159 | border-right: 1px solid #ddd; } } 160 | 161 | .nav-groups { 162 | list-style-type: none; 163 | padding-left: 0; } 164 | 165 | .nav-group-name { 166 | border-bottom: 1px solid #ddd; 167 | padding: 8px 0 8px 16px; } 168 | 169 | .nav-group-name-link { 170 | color: #333; } 171 | 172 | .nav-group-tasks { 173 | margin: 8px 0; 174 | padding: 0 0 0 8px; } 175 | 176 | .nav-group-task { 177 | font-size: 1em; 178 | list-style-type: none; 179 | white-space: nowrap; } 180 | 181 | .nav-group-task-link { 182 | color: #808080; } 183 | 184 | .main-content { 185 | order: 1; } 186 | @media (min-width: 768px) { 187 | .main-content { 188 | order: 2; 189 | flex: 1; 190 | padding-bottom: 60px; } } 191 | 192 | .section { 193 | padding: 0 32px; 194 | border-bottom: 1px solid #ddd; } 195 | 196 | .section-content { 197 | max-width: 834px; 198 | margin: 0 auto; 199 | padding: 16px 0; } 200 | 201 | .section-name { 202 | color: #666; 203 | display: block; } 204 | 205 | .declaration .highlight { 206 | overflow-x: initial; 207 | padding: 8px 0; 208 | margin: 0; 209 | background-color: transparent; 210 | border: none; } 211 | 212 | .task-group-section { 213 | border-top: 1px solid #ddd; } 214 | 215 | .task-group { 216 | padding-top: 0px; } 217 | 218 | .task-name-container a[name]:before { 219 | content: ""; 220 | display: block; } 221 | 222 | .item-container { 223 | padding: 0; } 224 | 225 | .item { 226 | padding-top: 8px; 227 | width: 100%; 228 | list-style-type: none; } 229 | .item a[name]:before { 230 | content: ""; 231 | display: block; } 232 | .item .token, .item .direct-link { 233 | padding-left: 3px; 234 | margin-left: 0px; 235 | font-size: 1rem; } 236 | .item .declaration-note { 237 | font-size: .85em; 238 | color: #808080; 239 | font-style: italic; } 240 | 241 | .pointer-container { 242 | border-bottom: 1px solid #ddd; 243 | left: -23px; 244 | padding-bottom: 13px; 245 | position: relative; 246 | width: 110%; } 247 | 248 | .pointer { 249 | left: 21px; 250 | top: 7px; 251 | display: block; 252 | position: absolute; 253 | width: 12px; 254 | height: 12px; 255 | border-left: 1px solid #ddd; 256 | border-top: 1px solid #ddd; 257 | background: #fff; 258 | transform: rotate(45deg); } 259 | 260 | .height-container { 261 | display: none; 262 | position: relative; 263 | width: 100%; 264 | overflow: hidden; } 265 | .height-container .section { 266 | background: #fff; 267 | border: 1px solid #ddd; 268 | border-top-width: 0; 269 | padding-top: 10px; 270 | padding-bottom: 5px; 271 | padding: 8px 16px; } 272 | 273 | .aside, .language { 274 | padding: 6px 12px; 275 | margin: 12px 0; 276 | border-left: 5px solid #dddddd; 277 | overflow-y: hidden; } 278 | .aside .aside-title, .language .aside-title { 279 | font-size: 9px; 280 | letter-spacing: 2px; 281 | text-transform: uppercase; 282 | padding-bottom: 0; 283 | margin: 0; 284 | color: #aaa; 285 | -webkit-user-select: none; } 286 | .aside p:last-child, .language p:last-child { 287 | margin-bottom: 0; } 288 | 289 | .language { 290 | border-left: 5px solid #cde9f4; } 291 | .language .aside-title { 292 | color: #4183c4; } 293 | 294 | .aside-warning, .aside-deprecated, .aside-unavailable { 295 | border-left: 5px solid #ff6666; } 296 | .aside-warning .aside-title, .aside-deprecated .aside-title, .aside-unavailable .aside-title { 297 | color: #ff0000; } 298 | 299 | .graybox { 300 | border-collapse: collapse; 301 | width: 100%; } 302 | .graybox p { 303 | margin: 0; 304 | word-break: break-word; 305 | min-width: 50px; } 306 | .graybox td { 307 | border: 1px solid #ddd; 308 | padding: 5px 25px 5px 10px; 309 | vertical-align: middle; } 310 | .graybox tr td:first-of-type { 311 | text-align: right; 312 | padding: 7px; 313 | vertical-align: top; 314 | word-break: normal; 315 | width: 40px; } 316 | 317 | .slightly-smaller { 318 | font-size: 0.9em; } 319 | 320 | .footer { 321 | padding: 8px 16px; 322 | background: #444; 323 | color: #ddd; 324 | font-size: 0.8em; } 325 | .footer p { 326 | margin: 8px 0; } 327 | .footer a { 328 | color: #fff; } 329 | 330 | html.dash .header, html.dash .breadcrumbs, html.dash .navigation { 331 | display: none; } 332 | html.dash .height-container { 333 | display: block; } 334 | 335 | form[role=search] input { 336 | font: 16px/1.7 "Helvetica Neue", Helvetica, Arial, sans-serif; 337 | font-size: 14px; 338 | line-height: 24px; 339 | padding: 0 10px; 340 | margin: 0; 341 | border: none; 342 | border-radius: 1em; } 343 | .loading form[role=search] input { 344 | background: white url(../img/spinner.gif) center right 4px no-repeat; } 345 | form[role=search] .tt-menu { 346 | margin: 0; 347 | min-width: 300px; 348 | background: #fbfbfb; 349 | color: #333; 350 | border: 1px solid #ddd; } 351 | form[role=search] .tt-highlight { 352 | font-weight: bold; } 353 | form[role=search] .tt-suggestion { 354 | font: 16px/1.7 "Helvetica Neue", Helvetica, Arial, sans-serif; 355 | padding: 0 8px; } 356 | form[role=search] .tt-suggestion span { 357 | display: table-cell; 358 | white-space: nowrap; } 359 | form[role=search] .tt-suggestion .doc-parent-name { 360 | width: 100%; 361 | text-align: right; 362 | font-weight: normal; 363 | font-size: 0.9em; 364 | padding-left: 16px; } 365 | form[role=search] .tt-suggestion:hover, 366 | form[role=search] .tt-suggestion.tt-cursor { 367 | cursor: pointer; 368 | background-color: #4183c4; 369 | color: #fff; } 370 | form[role=search] .tt-suggestion:hover .doc-parent-name, 371 | form[role=search] .tt-suggestion.tt-cursor .doc-parent-name { 372 | color: #fff; } 373 | -------------------------------------------------------------------------------- /docs/docsets/InAppViewDebugger.docset/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.jazzy.inappviewdebugger 7 | CFBundleName 8 | InAppViewDebugger 9 | DocSetPlatformFamily 10 | inappviewdebugger 11 | isDashDocset 12 | 13 | dashIndexFilePath 14 | index.html 15 | isJavaScriptEnabled 16 | 17 | DashDocSetFamily 18 | dashtoc 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/docsets/InAppViewDebugger.docset/Contents/Resources/Documents/Classes/Configuration.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Configuration Class Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |

23 | 24 | InAppViewDebugger Docs 25 | 26 | (100% documented) 27 |

28 | 29 |

30 |

31 | 32 |
33 |

34 | 35 |

36 | 37 | 38 | View on GitHub 39 | 40 |

41 | 42 |
43 | 44 | 49 | 50 |
51 | 95 |
96 | 97 |
98 |
99 |

Configuration

100 |
101 |
102 |
@objc(IAVDConfiguration)
103 | public final class Configuration : NSObject
104 | 105 |
106 |
107 |

Configuration options for the in app view debugger.

108 | 109 |
110 |
111 | 112 |
113 |
114 |
115 |
    116 |
  • 117 |
    118 | 119 | 120 | 121 | snapshotViewConfiguration 122 | 123 |
    124 |
    125 |
    126 |
    127 |
    128 |
    129 |

    Configuration for the 3D snapshot view.

    130 | 131 |
    132 |
    133 |

    Declaration

    134 |
    135 |

    Swift

    136 |
    @objc
    137 | public var snapshotViewConfiguration: SnapshotViewConfiguration
    138 | 139 |
    140 |
    141 |
    142 | Show on GitHub 143 |
    144 |
    145 |
    146 |
  • 147 |
  • 148 |
    149 | 150 | 151 | 152 | hierarchyViewConfiguration 153 | 154 |
    155 |
    156 |
    157 |
    158 |
    159 |
    160 |

    Configuration for the hierarchy (tree) view.

    161 | 162 |
    163 |
    164 |

    Declaration

    165 |
    166 |

    Swift

    167 |
    @objc
    168 | public var hierarchyViewConfiguration: HierarchyViewConfiguration
    169 | 170 |
    171 |
    172 |
    173 | Show on GitHub 174 |
    175 |
    176 |
    177 |
  • 178 |
179 |
180 |
181 |
182 | 183 |
184 |
185 | 189 | 190 | 191 | 192 | -------------------------------------------------------------------------------- /docs/docsets/InAppViewDebugger.docset/Contents/Resources/Documents/Classes/ElementLabel/Classification.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Classification Enumeration Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |

23 | 24 | InAppViewDebugger Docs 25 | 26 | (100% documented) 27 |

28 | 29 |

30 |

31 | 32 |
33 |

34 | 35 |

36 | 37 | 38 | View on GitHub 39 | 40 |

41 | 42 |
43 | 44 | 49 | 50 |
51 | 95 |
96 | 97 |
98 |
99 |

Classification

100 |
101 |
102 |
@objc(IAVDElementClassification)
103 | public enum Classification : Int
104 | 105 |
106 |
107 |

Classification for an element that determines how it is represented 108 | in the view debugger.

109 | 110 |
111 |
112 | 113 |
114 |
115 |
116 |
    117 |
  • 118 |
    119 | 120 | 121 | 122 | normal 123 | 124 |
    125 |
    126 |
    127 |
    128 |
    129 |
    130 |

    An element of normal importance.

    131 | 132 |
    133 |
    134 |

    Declaration

    135 |
    136 |

    Swift

    137 |
    case normal
    138 | 139 |
    140 |
    141 |
    142 | Show on GitHub 143 |
    144 |
    145 |
    146 |
  • 147 |
  • 148 |
    149 | 150 | 151 | 152 | important 153 | 154 |
    155 |
    156 |
    157 |
    158 |
    159 |
    160 |

    An element of higher importance that is highlighted

    161 | 162 |
    163 |
    164 |

    Declaration

    165 |
    166 |

    Swift

    167 |
    case important
    168 | 169 |
    170 |
    171 |
    172 | Show on GitHub 173 |
    174 |
    175 |
    176 |
  • 177 |
178 |
179 |
180 |
181 | 182 |
183 |
184 | 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /docs/docsets/InAppViewDebugger.docset/Contents/Resources/Documents/Protocols.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Protocols Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |

23 | 24 | InAppViewDebugger Docs 25 | 26 | (100% documented) 27 |

28 | 29 |

30 |

31 | 32 |
33 |

34 | 35 |

36 | 37 | 38 | View on GitHub 39 | 40 |

41 | 42 |
43 | 44 | 49 | 50 |
51 | 95 |
96 | 97 |
98 |
99 |

Protocols

100 |

The following protocols are available globally.

101 | 102 |
103 |
104 | 105 |
106 |
107 |
108 |
    109 |
  • 110 |
    111 | 112 | 113 | 114 | Element 115 | 116 |
    117 |
    118 |
    119 |
    120 |
    121 |
    122 |

    A UI element that can be snapshotted.

    123 | 124 | See more 125 |
    126 |
    127 |

    Declaration

    128 |
    129 |

    Swift

    130 |
    @objc(IAVDElement)
    131 | public protocol Element
    132 | 133 |
    134 |
    135 |
    136 | Show on GitHub 137 |
    138 |
    139 |
    140 |
  • 141 |
142 |
143 |
144 |
145 | 146 |
147 |
148 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /docs/docsets/InAppViewDebugger.docset/Contents/Resources/Documents/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* Credit to https://gist.github.com/wataru420/2048287 */ 2 | .highlight { 3 | /* Comment */ 4 | /* Error */ 5 | /* Keyword */ 6 | /* Operator */ 7 | /* Comment.Multiline */ 8 | /* Comment.Preproc */ 9 | /* Comment.Single */ 10 | /* Comment.Special */ 11 | /* Generic.Deleted */ 12 | /* Generic.Deleted.Specific */ 13 | /* Generic.Emph */ 14 | /* Generic.Error */ 15 | /* Generic.Heading */ 16 | /* Generic.Inserted */ 17 | /* Generic.Inserted.Specific */ 18 | /* Generic.Output */ 19 | /* Generic.Prompt */ 20 | /* Generic.Strong */ 21 | /* Generic.Subheading */ 22 | /* Generic.Traceback */ 23 | /* Keyword.Constant */ 24 | /* Keyword.Declaration */ 25 | /* Keyword.Pseudo */ 26 | /* Keyword.Reserved */ 27 | /* Keyword.Type */ 28 | /* Literal.Number */ 29 | /* Literal.String */ 30 | /* Name.Attribute */ 31 | /* Name.Builtin */ 32 | /* Name.Class */ 33 | /* Name.Constant */ 34 | /* Name.Entity */ 35 | /* Name.Exception */ 36 | /* Name.Function */ 37 | /* Name.Namespace */ 38 | /* Name.Tag */ 39 | /* Name.Variable */ 40 | /* Operator.Word */ 41 | /* Text.Whitespace */ 42 | /* Literal.Number.Float */ 43 | /* Literal.Number.Hex */ 44 | /* Literal.Number.Integer */ 45 | /* Literal.Number.Oct */ 46 | /* Literal.String.Backtick */ 47 | /* Literal.String.Char */ 48 | /* Literal.String.Doc */ 49 | /* Literal.String.Double */ 50 | /* Literal.String.Escape */ 51 | /* Literal.String.Heredoc */ 52 | /* Literal.String.Interpol */ 53 | /* Literal.String.Other */ 54 | /* Literal.String.Regex */ 55 | /* Literal.String.Single */ 56 | /* Literal.String.Symbol */ 57 | /* Name.Builtin.Pseudo */ 58 | /* Name.Variable.Class */ 59 | /* Name.Variable.Global */ 60 | /* Name.Variable.Instance */ 61 | /* Literal.Number.Integer.Long */ } 62 | .highlight .c { 63 | color: #999988; 64 | font-style: italic; } 65 | .highlight .err { 66 | color: #a61717; 67 | background-color: #e3d2d2; } 68 | .highlight .k { 69 | color: #000000; 70 | font-weight: bold; } 71 | .highlight .o { 72 | color: #000000; 73 | font-weight: bold; } 74 | .highlight .cm { 75 | color: #999988; 76 | font-style: italic; } 77 | .highlight .cp { 78 | color: #999999; 79 | font-weight: bold; } 80 | .highlight .c1 { 81 | color: #999988; 82 | font-style: italic; } 83 | .highlight .cs { 84 | color: #999999; 85 | font-weight: bold; 86 | font-style: italic; } 87 | .highlight .gd { 88 | color: #000000; 89 | background-color: #ffdddd; } 90 | .highlight .gd .x { 91 | color: #000000; 92 | background-color: #ffaaaa; } 93 | .highlight .ge { 94 | color: #000000; 95 | font-style: italic; } 96 | .highlight .gr { 97 | color: #aa0000; } 98 | .highlight .gh { 99 | color: #999999; } 100 | .highlight .gi { 101 | color: #000000; 102 | background-color: #ddffdd; } 103 | .highlight .gi .x { 104 | color: #000000; 105 | background-color: #aaffaa; } 106 | .highlight .go { 107 | color: #888888; } 108 | .highlight .gp { 109 | color: #555555; } 110 | .highlight .gs { 111 | font-weight: bold; } 112 | .highlight .gu { 113 | color: #aaaaaa; } 114 | .highlight .gt { 115 | color: #aa0000; } 116 | .highlight .kc { 117 | color: #000000; 118 | font-weight: bold; } 119 | .highlight .kd { 120 | color: #000000; 121 | font-weight: bold; } 122 | .highlight .kp { 123 | color: #000000; 124 | font-weight: bold; } 125 | .highlight .kr { 126 | color: #000000; 127 | font-weight: bold; } 128 | .highlight .kt { 129 | color: #445588; } 130 | .highlight .m { 131 | color: #009999; } 132 | .highlight .s { 133 | color: #d14; } 134 | .highlight .na { 135 | color: #008080; } 136 | .highlight .nb { 137 | color: #0086B3; } 138 | .highlight .nc { 139 | color: #445588; 140 | font-weight: bold; } 141 | .highlight .no { 142 | color: #008080; } 143 | .highlight .ni { 144 | color: #800080; } 145 | .highlight .ne { 146 | color: #990000; 147 | font-weight: bold; } 148 | .highlight .nf { 149 | color: #990000; } 150 | .highlight .nn { 151 | color: #555555; } 152 | .highlight .nt { 153 | color: #000080; } 154 | .highlight .nv { 155 | color: #008080; } 156 | .highlight .ow { 157 | color: #000000; 158 | font-weight: bold; } 159 | .highlight .w { 160 | color: #bbbbbb; } 161 | .highlight .mf { 162 | color: #009999; } 163 | .highlight .mh { 164 | color: #009999; } 165 | .highlight .mi { 166 | color: #009999; } 167 | .highlight .mo { 168 | color: #009999; } 169 | .highlight .sb { 170 | color: #d14; } 171 | .highlight .sc { 172 | color: #d14; } 173 | .highlight .sd { 174 | color: #d14; } 175 | .highlight .s2 { 176 | color: #d14; } 177 | .highlight .se { 178 | color: #d14; } 179 | .highlight .sh { 180 | color: #d14; } 181 | .highlight .si { 182 | color: #d14; } 183 | .highlight .sx { 184 | color: #d14; } 185 | .highlight .sr { 186 | color: #009926; } 187 | .highlight .s1 { 188 | color: #d14; } 189 | .highlight .ss { 190 | color: #990073; } 191 | .highlight .bp { 192 | color: #999999; } 193 | .highlight .vc { 194 | color: #008080; } 195 | .highlight .vg { 196 | color: #008080; } 197 | .highlight .vi { 198 | color: #008080; } 199 | .highlight .il { 200 | color: #009999; } 201 | -------------------------------------------------------------------------------- /docs/docsets/InAppViewDebugger.docset/Contents/Resources/Documents/css/jazzy.css: -------------------------------------------------------------------------------- 1 | *, *:before, *:after { 2 | box-sizing: inherit; } 3 | 4 | body { 5 | margin: 0; 6 | background: #fff; 7 | color: #333; 8 | font: 16px/1.7 "Helvetica Neue", Helvetica, Arial, sans-serif; 9 | letter-spacing: .2px; 10 | -webkit-font-smoothing: antialiased; 11 | box-sizing: border-box; } 12 | 13 | h1 { 14 | font-size: 2rem; 15 | font-weight: 700; 16 | margin: 1.275em 0 0.6em; } 17 | 18 | h2 { 19 | font-size: 1.75rem; 20 | font-weight: 700; 21 | margin: 1.275em 0 0.3em; } 22 | 23 | h3 { 24 | font-size: 1.5rem; 25 | font-weight: 700; 26 | margin: 1em 0 0.3em; } 27 | 28 | h4 { 29 | font-size: 1.25rem; 30 | font-weight: 700; 31 | margin: 1.275em 0 0.85em; } 32 | 33 | h5 { 34 | font-size: 1rem; 35 | font-weight: 700; 36 | margin: 1.275em 0 0.85em; } 37 | 38 | h6 { 39 | font-size: 1rem; 40 | font-weight: 700; 41 | margin: 1.275em 0 0.85em; 42 | color: #777; } 43 | 44 | p { 45 | margin: 0 0 1em; } 46 | 47 | ul, ol { 48 | padding: 0 0 0 2em; 49 | margin: 0 0 0.85em; } 50 | 51 | blockquote { 52 | margin: 0 0 0.85em; 53 | padding: 0 15px; 54 | color: #858585; 55 | border-left: 4px solid #e5e5e5; } 56 | 57 | img { 58 | max-width: 100%; } 59 | 60 | a { 61 | color: #4183c4; 62 | text-decoration: none; } 63 | a:hover, a:focus { 64 | outline: 0; 65 | text-decoration: underline; } 66 | a.discouraged { 67 | text-decoration: line-through; } 68 | a.discouraged:hover, a.discouraged:focus { 69 | text-decoration: underline line-through; } 70 | 71 | table { 72 | background: #fff; 73 | width: 100%; 74 | border-collapse: collapse; 75 | border-spacing: 0; 76 | overflow: auto; 77 | margin: 0 0 0.85em; } 78 | 79 | tr:nth-child(2n) { 80 | background-color: #fbfbfb; } 81 | 82 | th, td { 83 | padding: 6px 13px; 84 | border: 1px solid #ddd; } 85 | 86 | pre { 87 | margin: 0 0 1.275em; 88 | padding: .85em 1em; 89 | overflow: auto; 90 | background: #f7f7f7; 91 | font-size: .85em; 92 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; } 93 | 94 | code { 95 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; } 96 | 97 | p > code, li > code { 98 | background: #f7f7f7; 99 | padding: .2em; } 100 | p > code:before, p > code:after, li > code:before, li > code:after { 101 | letter-spacing: -.2em; 102 | content: "\00a0"; } 103 | 104 | pre code { 105 | padding: 0; 106 | white-space: pre; } 107 | 108 | .content-wrapper { 109 | display: flex; 110 | flex-direction: column; } 111 | @media (min-width: 768px) { 112 | .content-wrapper { 113 | flex-direction: row; } } 114 | 115 | .header { 116 | display: flex; 117 | padding: 8px; 118 | font-size: 0.875em; 119 | background: #444; 120 | color: #999; } 121 | 122 | .header-col { 123 | margin: 0; 124 | padding: 0 8px; } 125 | 126 | .header-col--primary { 127 | flex: 1; } 128 | 129 | .header-link { 130 | color: #fff; } 131 | 132 | .header-icon { 133 | padding-right: 6px; 134 | vertical-align: -4px; 135 | height: 16px; } 136 | 137 | .breadcrumbs { 138 | font-size: 0.875em; 139 | padding: 8px 16px; 140 | margin: 0; 141 | background: #fbfbfb; 142 | border-bottom: 1px solid #ddd; } 143 | 144 | .carat { 145 | height: 10px; 146 | margin: 0 5px; } 147 | 148 | .navigation { 149 | order: 2; } 150 | @media (min-width: 768px) { 151 | .navigation { 152 | order: 1; 153 | width: 25%; 154 | max-width: 300px; 155 | padding-bottom: 64px; 156 | overflow: hidden; 157 | word-wrap: normal; 158 | background: #fbfbfb; 159 | border-right: 1px solid #ddd; } } 160 | 161 | .nav-groups { 162 | list-style-type: none; 163 | padding-left: 0; } 164 | 165 | .nav-group-name { 166 | border-bottom: 1px solid #ddd; 167 | padding: 8px 0 8px 16px; } 168 | 169 | .nav-group-name-link { 170 | color: #333; } 171 | 172 | .nav-group-tasks { 173 | margin: 8px 0; 174 | padding: 0 0 0 8px; } 175 | 176 | .nav-group-task { 177 | font-size: 1em; 178 | list-style-type: none; 179 | white-space: nowrap; } 180 | 181 | .nav-group-task-link { 182 | color: #808080; } 183 | 184 | .main-content { 185 | order: 1; } 186 | @media (min-width: 768px) { 187 | .main-content { 188 | order: 2; 189 | flex: 1; 190 | padding-bottom: 60px; } } 191 | 192 | .section { 193 | padding: 0 32px; 194 | border-bottom: 1px solid #ddd; } 195 | 196 | .section-content { 197 | max-width: 834px; 198 | margin: 0 auto; 199 | padding: 16px 0; } 200 | 201 | .section-name { 202 | color: #666; 203 | display: block; } 204 | 205 | .declaration .highlight { 206 | overflow-x: initial; 207 | padding: 8px 0; 208 | margin: 0; 209 | background-color: transparent; 210 | border: none; } 211 | 212 | .task-group-section { 213 | border-top: 1px solid #ddd; } 214 | 215 | .task-group { 216 | padding-top: 0px; } 217 | 218 | .task-name-container a[name]:before { 219 | content: ""; 220 | display: block; } 221 | 222 | .item-container { 223 | padding: 0; } 224 | 225 | .item { 226 | padding-top: 8px; 227 | width: 100%; 228 | list-style-type: none; } 229 | .item a[name]:before { 230 | content: ""; 231 | display: block; } 232 | .item .token, .item .direct-link { 233 | padding-left: 3px; 234 | margin-left: 0px; 235 | font-size: 1rem; } 236 | .item .declaration-note { 237 | font-size: .85em; 238 | color: #808080; 239 | font-style: italic; } 240 | 241 | .pointer-container { 242 | border-bottom: 1px solid #ddd; 243 | left: -23px; 244 | padding-bottom: 13px; 245 | position: relative; 246 | width: 110%; } 247 | 248 | .pointer { 249 | left: 21px; 250 | top: 7px; 251 | display: block; 252 | position: absolute; 253 | width: 12px; 254 | height: 12px; 255 | border-left: 1px solid #ddd; 256 | border-top: 1px solid #ddd; 257 | background: #fff; 258 | transform: rotate(45deg); } 259 | 260 | .height-container { 261 | display: none; 262 | position: relative; 263 | width: 100%; 264 | overflow: hidden; } 265 | .height-container .section { 266 | background: #fff; 267 | border: 1px solid #ddd; 268 | border-top-width: 0; 269 | padding-top: 10px; 270 | padding-bottom: 5px; 271 | padding: 8px 16px; } 272 | 273 | .aside, .language { 274 | padding: 6px 12px; 275 | margin: 12px 0; 276 | border-left: 5px solid #dddddd; 277 | overflow-y: hidden; } 278 | .aside .aside-title, .language .aside-title { 279 | font-size: 9px; 280 | letter-spacing: 2px; 281 | text-transform: uppercase; 282 | padding-bottom: 0; 283 | margin: 0; 284 | color: #aaa; 285 | -webkit-user-select: none; } 286 | .aside p:last-child, .language p:last-child { 287 | margin-bottom: 0; } 288 | 289 | .language { 290 | border-left: 5px solid #cde9f4; } 291 | .language .aside-title { 292 | color: #4183c4; } 293 | 294 | .aside-warning, .aside-deprecated, .aside-unavailable { 295 | border-left: 5px solid #ff6666; } 296 | .aside-warning .aside-title, .aside-deprecated .aside-title, .aside-unavailable .aside-title { 297 | color: #ff0000; } 298 | 299 | .graybox { 300 | border-collapse: collapse; 301 | width: 100%; } 302 | .graybox p { 303 | margin: 0; 304 | word-break: break-word; 305 | min-width: 50px; } 306 | .graybox td { 307 | border: 1px solid #ddd; 308 | padding: 5px 25px 5px 10px; 309 | vertical-align: middle; } 310 | .graybox tr td:first-of-type { 311 | text-align: right; 312 | padding: 7px; 313 | vertical-align: top; 314 | word-break: normal; 315 | width: 40px; } 316 | 317 | .slightly-smaller { 318 | font-size: 0.9em; } 319 | 320 | .footer { 321 | padding: 8px 16px; 322 | background: #444; 323 | color: #ddd; 324 | font-size: 0.8em; } 325 | .footer p { 326 | margin: 8px 0; } 327 | .footer a { 328 | color: #fff; } 329 | 330 | html.dash .header, html.dash .breadcrumbs, html.dash .navigation { 331 | display: none; } 332 | html.dash .height-container { 333 | display: block; } 334 | 335 | form[role=search] input { 336 | font: 16px/1.7 "Helvetica Neue", Helvetica, Arial, sans-serif; 337 | font-size: 14px; 338 | line-height: 24px; 339 | padding: 0 10px; 340 | margin: 0; 341 | border: none; 342 | border-radius: 1em; } 343 | .loading form[role=search] input { 344 | background: white url(../img/spinner.gif) center right 4px no-repeat; } 345 | form[role=search] .tt-menu { 346 | margin: 0; 347 | min-width: 300px; 348 | background: #fbfbfb; 349 | color: #333; 350 | border: 1px solid #ddd; } 351 | form[role=search] .tt-highlight { 352 | font-weight: bold; } 353 | form[role=search] .tt-suggestion { 354 | font: 16px/1.7 "Helvetica Neue", Helvetica, Arial, sans-serif; 355 | padding: 0 8px; } 356 | form[role=search] .tt-suggestion span { 357 | display: table-cell; 358 | white-space: nowrap; } 359 | form[role=search] .tt-suggestion .doc-parent-name { 360 | width: 100%; 361 | text-align: right; 362 | font-weight: normal; 363 | font-size: 0.9em; 364 | padding-left: 16px; } 365 | form[role=search] .tt-suggestion:hover, 366 | form[role=search] .tt-suggestion.tt-cursor { 367 | cursor: pointer; 368 | background-color: #4183c4; 369 | color: #fff; } 370 | form[role=search] .tt-suggestion:hover .doc-parent-name, 371 | form[role=search] .tt-suggestion.tt-cursor .doc-parent-name { 372 | color: #fff; } 373 | -------------------------------------------------------------------------------- /docs/docsets/InAppViewDebugger.docset/Contents/Resources/Documents/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/docs/docsets/InAppViewDebugger.docset/Contents/Resources/Documents/img/carat.png -------------------------------------------------------------------------------- /docs/docsets/InAppViewDebugger.docset/Contents/Resources/Documents/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/docs/docsets/InAppViewDebugger.docset/Contents/Resources/Documents/img/dash.png -------------------------------------------------------------------------------- /docs/docsets/InAppViewDebugger.docset/Contents/Resources/Documents/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/docs/docsets/InAppViewDebugger.docset/Contents/Resources/Documents/img/gh.png -------------------------------------------------------------------------------- /docs/docsets/InAppViewDebugger.docset/Contents/Resources/Documents/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/docs/docsets/InAppViewDebugger.docset/Contents/Resources/Documents/img/spinner.gif -------------------------------------------------------------------------------- /docs/docsets/InAppViewDebugger.docset/Contents/Resources/Documents/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | // On doc load, toggle the URL hash discussion if present 12 | $(document).ready(function() { 13 | if (!window.jazzy.docset) { 14 | var linkToHash = $('a[href="' + window.location.hash +'"]'); 15 | linkToHash.trigger("click"); 16 | } 17 | }); 18 | 19 | // On token click, toggle its discussion and animate token.marginLeft 20 | $(".token").click(function(event) { 21 | if (window.jazzy.docset) { 22 | return; 23 | } 24 | var link = $(this); 25 | var animationDuration = 300; 26 | $content = link.parent().parent().next(); 27 | $content.slideToggle(animationDuration); 28 | 29 | // Keeps the document from jumping to the hash. 30 | var href = $(this).attr('href'); 31 | if (history.pushState) { 32 | history.pushState({}, '', href); 33 | } else { 34 | location.hash = href; 35 | } 36 | event.preventDefault(); 37 | }); 38 | -------------------------------------------------------------------------------- /docs/docsets/InAppViewDebugger.docset/Contents/Resources/Documents/js/jazzy.search.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | var $typeahead = $('[data-typeahead]'); 3 | var $form = $typeahead.parents('form'); 4 | var searchURL = $form.attr('action'); 5 | 6 | function displayTemplate(result) { 7 | return result.name; 8 | } 9 | 10 | function suggestionTemplate(result) { 11 | var t = '
'; 12 | t += '' + result.name + ''; 13 | if (result.parent_name) { 14 | t += '' + result.parent_name + ''; 15 | } 16 | t += '
'; 17 | return t; 18 | } 19 | 20 | $typeahead.one('focus', function() { 21 | $form.addClass('loading'); 22 | 23 | $.getJSON(searchURL).then(function(searchData) { 24 | const searchIndex = lunr(function() { 25 | this.ref('url'); 26 | this.field('name'); 27 | this.field('abstract'); 28 | for (const [url, doc] of Object.entries(searchData)) { 29 | this.add({url: url, name: doc.name, abstract: doc.abstract}); 30 | } 31 | }); 32 | 33 | $typeahead.typeahead( 34 | { 35 | highlight: true, 36 | minLength: 3, 37 | autoselect: true 38 | }, 39 | { 40 | limit: 10, 41 | display: displayTemplate, 42 | templates: { suggestion: suggestionTemplate }, 43 | source: function(query, sync) { 44 | const lcSearch = query.toLowerCase(); 45 | const results = searchIndex.query(function(q) { 46 | q.term(lcSearch, { boost: 100 }); 47 | q.term(lcSearch, { 48 | boost: 10, 49 | wildcard: lunr.Query.wildcard.TRAILING 50 | }); 51 | }).map(function(result) { 52 | var doc = searchData[result.ref]; 53 | doc.url = result.ref; 54 | return doc; 55 | }); 56 | sync(results); 57 | } 58 | } 59 | ); 60 | $form.removeClass('loading'); 61 | $typeahead.trigger('focus'); 62 | }); 63 | }); 64 | 65 | var baseURL = searchURL.slice(0, -"search.json".length); 66 | 67 | $typeahead.on('typeahead:select', function(e, result) { 68 | window.location = baseURL + result.url; 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /docs/docsets/InAppViewDebugger.docset/Contents/Resources/docSet.dsidx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/docs/docsets/InAppViewDebugger.docset/Contents/Resources/docSet.dsidx -------------------------------------------------------------------------------- /docs/docsets/InAppViewDebugger.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/docs/docsets/InAppViewDebugger.tgz -------------------------------------------------------------------------------- /docs/img/borders.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/docs/img/borders.gif -------------------------------------------------------------------------------- /docs/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/docs/img/carat.png -------------------------------------------------------------------------------- /docs/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/docs/img/dash.png -------------------------------------------------------------------------------- /docs/img/distance.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/docs/img/distance.gif -------------------------------------------------------------------------------- /docs/img/focus.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/docs/img/focus.gif -------------------------------------------------------------------------------- /docs/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/docs/img/gh.png -------------------------------------------------------------------------------- /docs/img/headers.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/docs/img/headers.gif -------------------------------------------------------------------------------- /docs/img/iphone1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/docs/img/iphone1.png -------------------------------------------------------------------------------- /docs/img/iphone2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/docs/img/iphone2.png -------------------------------------------------------------------------------- /docs/img/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/docs/img/main.png -------------------------------------------------------------------------------- /docs/img/slicing.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/docs/img/slicing.gif -------------------------------------------------------------------------------- /docs/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/indragiek/InAppViewDebugger/d913c2d725d1b322fd0f26975050248dea6efe1b/docs/img/spinner.gif -------------------------------------------------------------------------------- /docs/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | // On doc load, toggle the URL hash discussion if present 12 | $(document).ready(function() { 13 | if (!window.jazzy.docset) { 14 | var linkToHash = $('a[href="' + window.location.hash +'"]'); 15 | linkToHash.trigger("click"); 16 | } 17 | }); 18 | 19 | // On token click, toggle its discussion and animate token.marginLeft 20 | $(".token").click(function(event) { 21 | if (window.jazzy.docset) { 22 | return; 23 | } 24 | var link = $(this); 25 | var animationDuration = 300; 26 | $content = link.parent().parent().next(); 27 | $content.slideToggle(animationDuration); 28 | 29 | // Keeps the document from jumping to the hash. 30 | var href = $(this).attr('href'); 31 | if (history.pushState) { 32 | history.pushState({}, '', href); 33 | } else { 34 | location.hash = href; 35 | } 36 | event.preventDefault(); 37 | }); 38 | -------------------------------------------------------------------------------- /docs/js/jazzy.search.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | var $typeahead = $('[data-typeahead]'); 3 | var $form = $typeahead.parents('form'); 4 | var searchURL = $form.attr('action'); 5 | 6 | function displayTemplate(result) { 7 | return result.name; 8 | } 9 | 10 | function suggestionTemplate(result) { 11 | var t = '
'; 12 | t += '' + result.name + ''; 13 | if (result.parent_name) { 14 | t += '' + result.parent_name + ''; 15 | } 16 | t += '
'; 17 | return t; 18 | } 19 | 20 | $typeahead.one('focus', function() { 21 | $form.addClass('loading'); 22 | 23 | $.getJSON(searchURL).then(function(searchData) { 24 | const searchIndex = lunr(function() { 25 | this.ref('url'); 26 | this.field('name'); 27 | this.field('abstract'); 28 | for (const [url, doc] of Object.entries(searchData)) { 29 | this.add({url: url, name: doc.name, abstract: doc.abstract}); 30 | } 31 | }); 32 | 33 | $typeahead.typeahead( 34 | { 35 | highlight: true, 36 | minLength: 3, 37 | autoselect: true 38 | }, 39 | { 40 | limit: 10, 41 | display: displayTemplate, 42 | templates: { suggestion: suggestionTemplate }, 43 | source: function(query, sync) { 44 | const lcSearch = query.toLowerCase(); 45 | const results = searchIndex.query(function(q) { 46 | q.term(lcSearch, { boost: 100 }); 47 | q.term(lcSearch, { 48 | boost: 10, 49 | wildcard: lunr.Query.wildcard.TRAILING 50 | }); 51 | }).map(function(result) { 52 | var doc = searchData[result.ref]; 53 | doc.url = result.ref; 54 | return doc; 55 | }); 56 | sync(results); 57 | } 58 | } 59 | ); 60 | $form.removeClass('loading'); 61 | $typeahead.trigger('focus'); 62 | }); 63 | }); 64 | 65 | var baseURL = searchURL.slice(0, -"search.json".length); 66 | 67 | $typeahead.on('typeahead:select', function(e, result) { 68 | window.location = baseURL + result.url; 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /docs/undocumented.json: -------------------------------------------------------------------------------- 1 | { 2 | "warnings": [ 3 | 4 | ], 5 | "source_directory": "/Users/indragie/Desktop/InAppViewDebugger" 6 | } --------------------------------------------------------------------------------