├── .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)
4 | [](https://cocoapods.org/?q=InAppViewDebugger)
5 | [](https://github.com/Carthage/Carthage)
6 |
7 |
8 |
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 |
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 |
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 |
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 |
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 |
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 |
34 |
35 |
41 |
42 |
43 |
44 |
45 | InAppViewDebugger Reference
46 |
47 | Configuration Class Reference
48 |
49 |
50 |
51 |
52 |
94 |
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 |
124 |
125 |
126 |
127 |
128 |
129 |
Configuration for the 3D snapshot view.
130 |
131 |
132 |
133 |
Declaration
134 |
140 |
141 |
144 |
145 |
146 |
147 |
148 |
155 |
156 |
157 |
158 |
159 |
160 |
Configuration for the hierarchy (tree) view.
161 |
162 |
163 |
164 |
Declaration
165 |
171 |
172 |
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 |
43 |
44 |
45 | InAppViewDebugger Reference
46 |
47 | Classification Enumeration Reference
48 |
49 |
50 |
51 |
52 |
94 |
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 |
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 |
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 |
43 |
44 |
45 | InAppViewDebugger Reference
46 |
47 | Protocols Reference
48 |
49 |
50 |
51 |
52 |
94 |
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 |
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 |
43 |
44 |
45 | InAppViewDebugger Reference
46 |
47 | Configuration Class Reference
48 |
49 |
50 |
51 |
52 |
94 |
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 |
124 |
125 |
126 |
127 |
128 |
129 |
Configuration for the 3D snapshot view.
130 |
131 |
132 |
133 |
Declaration
134 |
140 |
141 |
144 |
145 |
146 |
147 |
148 |
155 |
156 |
157 |
158 |
159 |
160 |
Configuration for the hierarchy (tree) view.
161 |
162 |
163 |
164 |
Declaration
165 |
171 |
172 |
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 |
43 |
44 |
45 | InAppViewDebugger Reference
46 |
47 | Classification Enumeration Reference
48 |
49 |
50 |
51 |
52 |
94 |
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 |
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 |
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 |
43 |
44 |
45 | InAppViewDebugger Reference
46 |
47 | Protocols Reference
48 |
49 |
50 |
51 |
52 |
94 |
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 |
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 | }
--------------------------------------------------------------------------------