├── .github
└── pull_request_template.md
├── .gitignore
├── .travis.yml
├── Example
├── LayoutInspectorExample.xcodeproj
│ ├── project.pbxproj
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── LayoutInspectorExample.xcscheme
├── LayoutInspectorExample.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
├── LayoutInspectorExample
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ └── mario.imageset
│ │ │ ├── Contents.json
│ │ │ └── Small-mario.png
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── ChangeAutoTriggerProtocol.swift
│ ├── ControlsDemoViewController.swift
│ ├── Info.plist
│ └── TableDemoViewController.swift
├── LayoutInspectorExampleTests
│ ├── Domain Layer Tests
│ │ └── HierarchyBuilderTests.swift
│ ├── Info.plist
│ ├── LayoutInspectorFacadeTests
│ │ └── LayoutInspectorFacadeTests.swift
│ └── UI Layer
│ │ ├── AttributeViewModelTests.swift
│ │ ├── LayoutInspectorContainerViewControllerTests
│ │ ├── LayoutInspectorContainerViewControllerTests.swift
│ │ └── LayoutInspectorViewOutputMock.swift
│ │ ├── LayoutInspectorPresenterTests
│ │ ├── LayoutInspectorPresenterDelegateMock.swift
│ │ └── LayoutInspectorPresenterTests.swift
│ │ ├── RenderingTreeBuilderTests.swift
│ │ ├── SceneViewManagerTests.swift
│ │ └── SceneWidgetViewControllerTests
│ │ ├── SceneViewManagerDelegateMock.swift
│ │ └── SceneWidgetViewControllerTests.swift
├── LayoutInspectorExampleUITests
│ ├── Base
│ │ ├── LIUITestCase.swift
│ │ ├── XCTestCase+Extensions.swift
│ │ └── XCUIApplication+Extensions.swift
│ ├── ControlsScreenLayoutInspectionTests.swift
│ ├── Info.plist
│ ├── ScreenObjects
│ │ ├── ControlsScreenObject.swift
│ │ ├── LayoutInspectorContainerScreenObject.swift
│ │ ├── ScreenObject.swift
│ │ ├── TabScreenObject.swift
│ │ └── TableScreenObject.swift
│ └── TableScreenLayoutInspectionTests.swift
├── Podfile
├── Podfile.lock
└── Pods
│ ├── Headers
│ └── Public
│ │ └── LayoutInspector
│ │ ├── LayoutInspector-umbrella.h
│ │ └── LayoutInspector.modulemap
│ ├── Local Podspecs
│ └── LayoutInspector.podspec.json
│ ├── Manifest.lock
│ ├── Pods.xcodeproj
│ └── project.pbxproj
│ └── Target Support Files
│ ├── LayoutInspector
│ ├── LayoutInspector-dummy.m
│ ├── LayoutInspector-prefix.pch
│ ├── LayoutInspector-umbrella.h
│ ├── LayoutInspector.modulemap
│ └── LayoutInspector.xcconfig
│ ├── Pods-LayoutInspectorExample
│ ├── Pods-LayoutInspectorExample-acknowledgements.markdown
│ ├── Pods-LayoutInspectorExample-acknowledgements.plist
│ ├── Pods-LayoutInspectorExample-dummy.m
│ ├── Pods-LayoutInspectorExample-frameworks.sh
│ ├── Pods-LayoutInspectorExample-resources-Debug-input-files.xcfilelist
│ ├── Pods-LayoutInspectorExample-resources-Debug-output-files.xcfilelist
│ ├── Pods-LayoutInspectorExample-resources-Release-input-files.xcfilelist
│ ├── Pods-LayoutInspectorExample-resources-Release-output-files.xcfilelist
│ ├── Pods-LayoutInspectorExample-resources.sh
│ ├── Pods-LayoutInspectorExample-umbrella.h
│ ├── Pods-LayoutInspectorExample.debug.xcconfig
│ ├── Pods-LayoutInspectorExample.modulemap
│ └── Pods-LayoutInspectorExample.release.xcconfig
│ ├── Pods-LayoutInspectorExampleTests
│ ├── Pods-LayoutInspectorExampleTests-acknowledgements.markdown
│ ├── Pods-LayoutInspectorExampleTests-acknowledgements.plist
│ ├── Pods-LayoutInspectorExampleTests-dummy.m
│ ├── Pods-LayoutInspectorExampleTests-frameworks.sh
│ ├── Pods-LayoutInspectorExampleTests-resources.sh
│ ├── Pods-LayoutInspectorExampleTests.debug.xcconfig
│ └── Pods-LayoutInspectorExampleTests.release.xcconfig
│ └── Pods-LayoutInspectorExampleUITests
│ ├── Pods-LayoutInspectorExampleUITests-acknowledgements.markdown
│ ├── Pods-LayoutInspectorExampleUITests-acknowledgements.plist
│ ├── Pods-LayoutInspectorExampleUITests-dummy.m
│ ├── Pods-LayoutInspectorExampleUITests-frameworks.sh
│ ├── Pods-LayoutInspectorExampleUITests-resources.sh
│ ├── Pods-LayoutInspectorExampleUITests.debug.xcconfig
│ └── Pods-LayoutInspectorExampleUITests.release.xcconfig
├── LICENSE
├── LayoutInspector.podspec
├── LayoutInspector
├── Assets
│ ├── .gitkeep
│ └── LayoutInspectorAssets.xcassets
│ │ ├── Contents.json
│ │ ├── checkeredPattern.imageset
│ │ ├── Contents.json
│ │ └── Screen Shot 2019-01-04 at 11.14.20 PM.png
│ │ ├── closeIcon.imageset
│ │ ├── Contents.json
│ │ └── dialog_close_1342689.png
│ │ ├── resetIcon.imageset
│ │ ├── Contents.json
│ │ └── collect.png
│ │ └── transparent_view_image.imageset
│ │ ├── 100x100.png
│ │ └── Contents.json
├── Classes
│ ├── .gitkeep
│ ├── Domain Layer
│ │ ├── HierarchyBuilder.swift
│ │ ├── Utils
│ │ │ ├── Bundle+Extension.swift
│ │ │ ├── Segue.swift
│ │ │ ├── String+Extension.swift
│ │ │ ├── UICollectionView+Extention.swift
│ │ │ ├── UIColor+Extension.swift
│ │ │ ├── UIFont+Extension.swift
│ │ │ ├── UIView+Extension.swift
│ │ │ └── UtilsProtocols.swift
│ │ ├── ViewControllerFactory.swift
│ │ └── ViewDescription.swift
│ ├── LayoutInspector.swift
│ └── UI Layer
│ │ ├── AttributesWidget
│ │ ├── AttributeViewModel.swift
│ │ ├── AttributesManagerInterfaces.swift
│ │ ├── AttributesWidgetViewController.swift
│ │ ├── ColorAttributeCell.swift
│ │ ├── ObjectAttributesManager.swift
│ │ └── TextAttributeCell.swift
│ │ ├── LayoutInspectorContainer
│ │ ├── DebugNode.swift
│ │ ├── LayoutInspectorContainerInterfaces.swift
│ │ ├── LayoutInspectorContainerViewController.swift
│ │ ├── LayoutInspectorPresenter.swift
│ │ ├── RenderingTreeBuilder.swift
│ │ ├── RenderingView.swift
│ │ └── ViewMetadata.swift
│ │ ├── MenuWidget
│ │ ├── MenuWidgetInterfaces.swift
│ │ └── MenuWidgetViewController.swift
│ │ └── SceneWidget
│ │ ├── SceneViewManager.swift
│ │ ├── SceneViewManagerInterfaces.swift
│ │ ├── SceneWidgetInterfaces.swift
│ │ └── SceneWidgetViewController.swift
└── LayoutInspector.storyboard
├── LayoutInspector_demo.gif
├── README.md
└── _Pods.xcodeproj
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## Description
2 |
3 |
4 |
5 | ## Testing
6 |
7 |
8 | - [ ] Tested manually
9 | - [ ] Unit tests added
10 | - [ ] UI tests added
11 | - [ ] Tests pass locally
12 |
13 |
14 | ## Versioning
15 | Pod version updated in `LayoutInspector.podspec`
16 | - [ ] Major
17 | - [ ] Minor
18 | - [ ] Patch
19 |
--------------------------------------------------------------------------------
/.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/
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 | osx_image: xcode12.2
3 |
4 | cache: cocoapods
5 | podfile: Example/Podfile
6 | before_install:
7 | - gem install cocoapods # Since Travis is not always on latest version
8 | - pod install --project-directory=Example
9 |
10 | script:
11 | - set -o pipefail && xcodebuild -workspace Example/LayoutInspectorExample.xcworkspace -scheme LayoutInspectorExample -sdk iphonesimulator -destination "platform=iOS Simulator,OS=14.2,name=iPhone 12" -UseModernBuildSystem=YES test
12 | - pod lib lint
13 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExample.xcodeproj/xcshareddata/xcschemes/LayoutInspectorExample.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
43 |
49 |
50 |
51 |
52 |
53 |
60 |
61 |
67 |
68 |
69 |
70 |
72 |
78 |
79 |
80 |
82 |
88 |
89 |
90 |
91 |
92 |
98 |
99 |
100 |
101 |
102 |
103 |
113 |
115 |
121 |
122 |
123 |
124 |
125 |
126 |
132 |
134 |
140 |
141 |
142 |
143 |
145 |
146 |
149 |
150 |
151 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExample.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExample.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExample/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 12/25/18.
6 | // Copyright © 2018 Igor Savynskyi. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import LayoutInspector
11 |
12 | @UIApplicationMain
13 | class AppDelegate: UIResponder, UIApplicationDelegate {
14 |
15 | var window: UIWindow?
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 |
19 | return true
20 | }
21 |
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExample/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/LayoutInspectorExample/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Example/LayoutInspectorExample/Assets.xcassets/mario.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "Small-mario.png"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/Example/LayoutInspectorExample/Assets.xcassets/mario.imageset/Small-mario.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/isavynskyi/LayoutInspector/72583864c147301c702c59ed4bbb5356114b9539/Example/LayoutInspectorExample/Assets.xcassets/mario.imageset/Small-mario.png
--------------------------------------------------------------------------------
/Example/LayoutInspectorExample/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/LayoutInspectorExample/ChangeAutoTriggerProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChangeAutoTriggerProtocol.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 9/29/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import LayoutInspector
12 |
13 | protocol ChangeAutoTriggerProtocol {
14 | func changeAutoTrigger()
15 | }
16 |
17 | extension ChangeAutoTriggerProtocol where Self: UIViewController {
18 | func changeAutoTrigger() {
19 | let alert = UIAlertController(title: nil, message: "Select auto trigger", preferredStyle: .actionSheet)
20 | alert.addAction(UIAlertAction(title: "Screenshot", style: .default, handler: { (_) in
21 | LayoutInspector.shared.setAutoTrigger(.screenshot)
22 | }))
23 | alert.addAction(UIAlertAction(title: "Shake", style: .default, handler: { (_) in
24 | LayoutInspector.shared.setAutoTrigger(.shake)
25 | }))
26 | alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
27 | present(alert, animated: true, completion: nil)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExample/ControlsDemoViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ControlsDemoViewController.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 12/25/18.
6 | // Copyright © 2018 Igor Savynskyi. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import LayoutInspector
11 |
12 | class ControlsDemoViewController: UIViewController, ChangeAutoTriggerProtocol {
13 |
14 | @IBAction private func inspectLayoutAction(_ sender: Any) {
15 | LayoutInspector.shared.showLayout()
16 | }
17 | @IBAction func changeAutoTrigger(_ sender: Any) {
18 | changeAutoTrigger()
19 | }
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExample/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/LayoutInspectorExample/TableDemoViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TableDemoViewController.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 1/5/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import LayoutInspector
11 |
12 | class TableDemoViewController: UITableViewController, ChangeAutoTriggerProtocol {
13 |
14 | @IBAction private func inspectAction(_ sender: Any) {
15 | LayoutInspector.shared.showLayout()
16 | }
17 |
18 | @IBAction func changeAutoTrigger(_ sender: Any) {
19 | changeAutoTrigger()
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExampleTests/Domain Layer Tests/HierarchyBuilderTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HierarchyBuilderTests.swift
3 | // LayoutInspectorExampleTests
4 | //
5 | // Created by Igor Savynskyi on 1/12/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import LayoutInspector
11 |
12 | class HierarchyBuilderTests: XCTestCase {
13 |
14 | func testHierarchyCapturing() {
15 | // given
16 | let builderUnderTest = HierarchyBuilder()
17 | // when
18 | let result = builderUnderTest.captureHierarchy()
19 | // then
20 | XCTAssertNotNil(result, "Hierarchy should exist")
21 | XCTAssertEqual(result?.className, "UIWindow", "Root element should be of class UIWindow")
22 | XCTAssertNotNil(result?.subviews, "Root element shoud have subviews")
23 | XCTAssert(result!.subviews!.count > 0, "UIWindow shoud have at least one subview")
24 | XCTAssert(result!.frame.origin == CGPoint.zero, "UIWindow shoud start from (0, 0) point")
25 | }
26 |
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExampleTests/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 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExampleTests/LayoutInspectorFacadeTests/LayoutInspectorFacadeTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LayoutInspectorFacadeTests.swift
3 | // LayoutInspectorExampleTests
4 | //
5 | // Created by Igor Savynskyi on 1/12/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import LayoutInspector
11 |
12 | class LayoutInspectorFacadeTests: XCTestCase {
13 |
14 | func testSubscriptionForScreenshotNotification() {
15 | // given
16 | let sharedInspectorUnderTest = LayoutInspector.shared
17 | let notificationCenter = NotificationCenter.default
18 | let screenshotNotificationName = UIApplication.userDidTakeScreenshotNotification.rawValue
19 | // when, then
20 | sharedInspectorUnderTest.setAutoTrigger(.none)
21 | XCTAssertFalse(notificationCenter.debugDescription.contains(screenshotNotificationName), "Screenshot notification should not have subscribers")
22 |
23 | sharedInspectorUnderTest.setAutoTrigger(.screenshot)
24 | XCTAssertTrue(notificationCenter.debugDescription.contains(screenshotNotificationName), "Screenshot notification should have subscribers")
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExampleTests/UI Layer/AttributeViewModelTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AttributeViewModelTests.swift
3 | // LayoutInspectorExampleTests
4 | //
5 | // Created by Igor Savynskyi on 1/13/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import LayoutInspector
11 |
12 | class AttributeViewModelTests: XCTestCase {
13 |
14 | func testNilColorRepresentationString() {
15 | // given
16 | let attributeValue = AttributeValue.color(nil)
17 | let modelUnderTest = AttributeViewModel(title: "Color", value: attributeValue)
18 | // when
19 | let representationString = modelUnderTest.valueStringRepresentation
20 | // then
21 | XCTAssert(representationString == ColorRepresentationPattern.nilColor, "Wrong representation for nil color")
22 | }
23 |
24 | func testClearColorRepresentationString() {
25 | // given
26 | let attributeValue = AttributeValue.color(.clear)
27 | let modelUnderTest = AttributeViewModel(title: "Color", value: attributeValue)
28 | // when
29 | let representationString = modelUnderTest.valueStringRepresentation
30 | // then
31 | XCTAssert(representationString == ColorRepresentationPattern.clear, "Wrong representation for clear color")
32 | }
33 |
34 | func testCustomColorRepresentationString() {
35 | // given
36 | let color = UIColor.white
37 | let attributeValue = AttributeValue.color(color)
38 | let modelUnderTest = AttributeViewModel(title: "Color", value: attributeValue)
39 | // when
40 | let representationString = modelUnderTest.valueStringRepresentation
41 | // then
42 | let expectedString = String(format: ColorRepresentationPattern.regularColorFormat,
43 | color.redValue,
44 | color.greenValue,
45 | color.blueValue,
46 | color.alphaValue)
47 | XCTAssert(representationString == expectedString, "Wrong representation for custom color")
48 | }
49 |
50 | func testRepresentationStringForCustomText() {
51 | // given
52 | let value = "Attribute value"
53 | let attributeValue = AttributeValue.text(value)
54 | let modelUnderTest = AttributeViewModel(title: "Attribute title", value: attributeValue)
55 | // when
56 | let representationString = modelUnderTest.valueStringRepresentation
57 | // then
58 | XCTAssert(representationString == value, "Wrong representation for attribute value string")
59 | }
60 |
61 | func testRepresentationStringForNilText() {
62 | // given
63 | let attributeValue = AttributeValue.text(nil)
64 | let modelUnderTest = AttributeViewModel(title: "Attribute title", value: attributeValue)
65 | // when
66 | let representationString = modelUnderTest.valueStringRepresentation
67 | // then
68 | XCTAssertNil(representationString, "Wrong representation for nil attribute string")
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExampleTests/UI Layer/LayoutInspectorContainerViewControllerTests/LayoutInspectorContainerViewControllerTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LayoutInspectorContainerViewControllerTests.swift
3 | // LayoutInspectorExampleTests
4 | //
5 | // Created by Igor Savynskyi on 1/13/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import LayoutInspector
11 |
12 | class LayoutInspectorContainerViewControllerTests: XCTestCase {
13 |
14 | var vcUnderTest: LayoutInspectorContainerViewController?
15 |
16 | override func setUp() {
17 | vcUnderTest = ViewControllerFactory.createLayoutInspectorContainerViewController()
18 | }
19 |
20 | override func tearDown() {
21 | vcUnderTest = nil
22 | }
23 |
24 | func testInteractionWithOutput() {
25 | // givel
26 | let mockOutput = LayoutInspectorViewOutputMock()
27 | vcUnderTest?.output = mockOutput
28 | let initialDidCloseActionCallsCount = mockOutput.didCloseActionCallsCounter
29 | // when
30 | vcUnderTest?.didCloseAction()
31 | // when
32 | XCTAssert(mockOutput.didCloseActionCallsCounter == initialDidCloseActionCallsCount + 1, "Didn't propagate close action to output")
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExampleTests/UI Layer/LayoutInspectorContainerViewControllerTests/LayoutInspectorViewOutputMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LayoutInspectorViewOutputMock.swift
3 | // LayoutInspectorExampleTests
4 | //
5 | // Created by Igor Savynskyi on 1/13/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | @testable import LayoutInspector
11 |
12 | class LayoutInspectorViewOutputMock: LayoutInspectorViewOutput {
13 | private(set) var didCloseActionCallsCounter = 0
14 |
15 | func didCloseAction() {
16 | didCloseActionCallsCounter += 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExampleTests/UI Layer/LayoutInspectorPresenterTests/LayoutInspectorPresenterDelegateMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LayoutInspectorPresenterDelegateMock.swift
3 | // LayoutInspectorExampleTests
4 | //
5 | // Created by Igor Savynskyi on 1/13/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | @testable import LayoutInspector
11 |
12 | class LayoutInspectorPresenterDelegateMock: NSObject, LayoutInspectorPresenterDelegate {
13 | private(set) var didFinishLayoutInspectionCallsCounter = 0
14 |
15 | func didFinishLayoutInspection() {
16 | didFinishLayoutInspectionCallsCounter += 1
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExampleTests/UI Layer/LayoutInspectorPresenterTests/LayoutInspectorPresenterTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LayoutInspectorPresenterTests.swift
3 | // LayoutInspectorExampleTests
4 | //
5 | // Created by Igor Savynskyi on 1/13/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import LayoutInspector
11 |
12 | class LayoutInspectorPresenterTests: XCTestCase {
13 |
14 | func testInteractionWithDelegate() {
15 | // given
16 | let presenteUnderTest = LayoutInspectorPresenter()
17 | let mockDelegate = LayoutInspectorPresenterDelegateMock()
18 | presenteUnderTest.delegate = mockDelegate
19 | let initialDidFinishLayoutInspectionCallsCount = mockDelegate.didFinishLayoutInspectionCallsCounter
20 | // when
21 | presenteUnderTest.didCloseAction()
22 | // then
23 | XCTAssert(mockDelegate.didFinishLayoutInspectionCallsCounter == initialDidFinishLayoutInspectionCallsCount + 1, "Didn't propagate close action to delegate")
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExampleTests/UI Layer/RenderingTreeBuilderTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RenderingTreeBuilderTests.swift
3 | // LayoutInspectorExampleTests
4 | //
5 | // Created by Igor Savynskyi on 1/13/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import LayoutInspector
11 |
12 | class RenderingTreeBuilderTests: XCTestCase {
13 |
14 | func testBuildResultForMockViewDescription() {
15 | // given
16 | let builderUnderTest = RenderingTreeBuilder()
17 | let mockViewDescription: ViewDescriptionProtocol = ViewDescription(frame: CGRect(x: 0.0, y: 0.0, width: 375.0, height: 49.0),
18 | snapshot: nil,
19 | subviews: nil,
20 | parentSize: CGSize(width: 375.0, height: 50.0),
21 | center: CGPoint(x: 187.5, y: 24.75),
22 | isHidden: false,
23 | isTransparent: true,
24 | className: "UITableViewCellContentView",
25 | isUserInteractionEnabled: true,
26 | alpha: 1.0,
27 | backgroundColor: nil,
28 | tint: nil,
29 | clipToBounds: false,
30 | font: nil)
31 | // when
32 | let renderingView = builderUnderTest.build(from: mockViewDescription)
33 | // then
34 | XCTAssert(renderingView.viewNode != nil, "RenderingView's viewNode should exist")
35 | XCTAssert(renderingView.viewDescription.subviews?.count == mockViewDescription.subviews?.count, "RenderingView's created subviews count should be equal to the source viewDesctiption subview count")
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExampleTests/UI Layer/SceneViewManagerTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneViewManagerTests.swift
3 | // LayoutInspectorExampleTests
4 | //
5 | // Created by Igor Savynskyi on 1/13/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import LayoutInspector
11 | import SceneKit
12 |
13 | class SceneViewManagerTests: XCTestCase {
14 |
15 | func testNodesManagementInScene() {
16 | // given
17 | let sceneView = SCNView()
18 | let managerUnderTest = SceneViewManager(sceneView: sceneView)
19 | let node = SCNNode()
20 | let sceneNodesInitialCount = sceneView.scene?.rootNode.childNodes.count ?? 0
21 | // when, then
22 | managerUnderTest.addNode(node)
23 | XCTAssert(sceneView.scene?.rootNode.childNodes.count == sceneNodesInitialCount + 1, "Wrong nodes count in scene")
24 | managerUnderTest.removeNode(node)
25 | XCTAssert(sceneView.scene?.rootNode.childNodes.count == sceneNodesInitialCount, "Wrong nodes count in scene")
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExampleTests/UI Layer/SceneWidgetViewControllerTests/SceneViewManagerDelegateMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneViewManagerDelegateMock.swift
3 | // LayoutInspectorExampleTests
4 | //
5 | // Created by Igor Savynskyi on 1/13/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | @testable import LayoutInspector
10 |
11 | class SceneViewManagerDelegateMock: NSObject, SceneViewManagerDelegate {
12 | private(set) var selectedViewMetadataDidUpdateCallsCounter = 0
13 |
14 | func selectedViewMetadataDidUpdate(_ metadata: ViewMetadataProtocol?) {
15 | selectedViewMetadataDidUpdateCallsCounter += 1
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExampleTests/UI Layer/SceneWidgetViewControllerTests/SceneWidgetViewControllerTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneWidgetViewControllerTests.swift
3 | // LayoutInspectorExampleTests
4 | //
5 | // Created by Igor Savynskyi on 1/13/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import LayoutInspector
11 |
12 | class SceneWidgetViewControllerTests: XCTestCase {
13 | var vcUnderTest: SceneWidgetViewController?
14 |
15 | override func setUp() {
16 | vcUnderTest = SceneWidgetViewController()
17 | }
18 |
19 | override func tearDown() {
20 | vcUnderTest = nil
21 | }
22 |
23 | func testInteractionWithDelegate() {
24 | // given
25 | let mockDelegate = SceneViewManagerDelegateMock()
26 | vcUnderTest?.delegate = mockDelegate
27 | let initialCallCount = mockDelegate.selectedViewMetadataDidUpdateCallsCounter
28 | // when
29 | vcUnderTest?.selectedViewMetadataDidUpdate(nil)
30 | // then
31 | XCTAssert(mockDelegate.selectedViewMetadataDidUpdateCallsCounter == initialCallCount + 1)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExampleUITests/Base/LIUITestCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LIUITestCase.swift
3 | // LayoutInspectorExampleUITests
4 | //
5 | // Created by Igor Savynskyi on 1/13/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import XCTest
11 |
12 | class LIUITestCase: XCTestCase {
13 | var app: XCUIApplication!
14 | var tabScreen: TabScreenObject!
15 | var tableScreen: TableScreenObject!
16 | var controlsScreen: ControlsScreenObject!
17 | var layoutInspectorScreen: LayoutInspectorContainerScreenObject!
18 |
19 | override func setUp() {
20 | app = XCUIApplication()
21 | continueAfterFailure = false
22 |
23 | tabScreen = TabScreenObject(application: app, testCase: self)
24 | tableScreen = TableScreenObject(application: app, testCase: self)
25 | controlsScreen = ControlsScreenObject(application: app, testCase: self)
26 | layoutInspectorScreen = LayoutInspectorContainerScreenObject(application: app, testCase: self)
27 | XCUIApplication().launch()
28 | super.setUp()
29 | }
30 |
31 | override func tearDown() {
32 | super.tearDown()
33 | app.terminate()
34 | app = nil
35 | tabScreen = nil
36 | tableScreen = nil
37 | layoutInspectorScreen = nil
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExampleUITests/Base/XCTestCase+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // XCTestCase+Extensions.swift
3 | // LayoutInspectorExampleUITests
4 | //
5 | // Created by Igor Savynskyi on 1/13/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | extension XCTestCase {
12 |
13 | func waitForElementToExist(element: XCUIElement, timeout: Double = 10) {
14 | expectation(for: NSPredicate(format: "exists == true"), evaluatedWith: element, handler: nil)
15 | waitForExpectations(timeout: timeout, handler: nil)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExampleUITests/Base/XCUIApplication+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // XCUIApplication+Extensions.swift
3 | // LayoutInspectorExampleUITests
4 | //
5 | // Created by Igor Savynskyi on 1/13/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import XCTest
11 |
12 | extension XCUIApplication {
13 | var isDisplayingLayoutInspection: Bool {
14 | return otherElements["LayoutInspectorContainerView"].exists
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExampleUITests/ControlsScreenLayoutInspectionTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ControlsScreenLayoutInspectionTests.swift
3 | // LayoutInspectorExampleUITests
4 | //
5 | // Created by Igor Savynskyi on 1/13/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class ControlsScreenLayoutInspectionTests: LIUITestCase {
12 |
13 | func testControlsPageLayoutInpection() {
14 | // when
15 | tabScreen.tapControlsTabButton()
16 | controlsScreen.tapInspectionButton()
17 | // then
18 | XCTAssert(app.isDisplayingLayoutInspection, "LI should be visible")
19 |
20 | // when
21 | layoutInspectorScreen.tapCloseButton()
22 | // then
23 | XCTAssert(app.isDisplayingLayoutInspection == false, "LI should not be visible")
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExampleUITests/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 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExampleUITests/ScreenObjects/ControlsScreenObject.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ControlsScreenObject.swift
3 | // LayoutInspectorExampleUITests
4 | //
5 | // Created by Igor Savynskyi on 1/13/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import XCTest
11 |
12 | class ControlsScreenObject: ScreenObject {
13 | private var inspectButton: XCUIElement
14 |
15 | override init (application: XCUIApplication, testCase: XCTestCase) {
16 | inspectButton = application.buttons["inspect"]
17 | super.init(application: application, testCase: testCase)
18 | }
19 |
20 | func tapInspectionButton() {
21 | inspectButton.tap()
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExampleUITests/ScreenObjects/LayoutInspectorContainerScreenObject.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LayoutInspectorContainerScreenObject.swift
3 | // LayoutInspectorExampleUITests
4 | //
5 | // Created by Igor Savynskyi on 1/13/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import XCTest
11 |
12 | class LayoutInspectorContainerScreenObject: ScreenObject {
13 | private var closeButton: XCUIElement
14 |
15 | override init (application: XCUIApplication, testCase: XCTestCase) {
16 | closeButton = application.buttons["close"]
17 | super.init(application: application, testCase: testCase)
18 | }
19 |
20 | func tapCloseButton() {
21 | tc.waitForElementToExist(element: closeButton)
22 | closeButton.tap()
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExampleUITests/ScreenObjects/ScreenObject.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScreenObject.swift
3 | // LayoutInspectorExampleUITests
4 | //
5 | // Created by Igor Savynskyi on 1/13/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class ScreenObject {
12 | let app: XCUIApplication
13 | let tc: XCTestCase
14 |
15 | init (application: XCUIApplication, testCase: XCTestCase) {
16 | app = application
17 | tc = testCase
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExampleUITests/ScreenObjects/TabScreenObject.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TabScreenObject.swift
3 | // LayoutInspectorExampleUITests
4 | //
5 | // Created by Igor Savynskyi on 1/13/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import XCTest
11 |
12 | class TabScreenObject: ScreenObject {
13 | private var featuredTabButton: XCUIElement
14 | private var moreTabButton: XCUIElement
15 |
16 | override init (application: XCUIApplication, testCase: XCTestCase) {
17 | featuredTabButton = application.tabBars.buttons["Featured"]
18 | moreTabButton = application.tabBars.buttons["More"]
19 | super.init(application: application, testCase: testCase)
20 | }
21 |
22 | func tapTableTabButton() {
23 | featuredTabButton.tap()
24 | }
25 |
26 | func tapControlsTabButton() {
27 | moreTabButton.tap()
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExampleUITests/ScreenObjects/TableScreenObject.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TableScreenObject.swift
3 | // LayoutInspectorExampleUITests
4 | //
5 | // Created by Igor Savynskyi on 1/13/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import XCTest
11 |
12 | class TableScreenObject: ScreenObject {
13 | private var inspectButton: XCUIElement
14 |
15 | override init (application: XCUIApplication, testCase: XCTestCase) {
16 | inspectButton = application.tables.buttons["inspect"]
17 | super.init(application: application, testCase: testCase)
18 | }
19 |
20 | func tapInspectionButton() {
21 | inspectButton.tap()
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Example/LayoutInspectorExampleUITests/TableScreenLayoutInspectionTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TableScreenLayoutInspectionTests.swift
3 | // LayoutInspectorExampleUITests
4 | //
5 | // Created by Igor Savynskyi on 1/13/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class TableScreenLayoutInspectionTests: LIUITestCase {
12 |
13 | func testTablePageLayoutInpection() {
14 | // when
15 | tabScreen.tapTableTabButton()
16 | tableScreen.tapInspectionButton()
17 | // then
18 | XCTAssert(app.isDisplayingLayoutInspection, "LI should be visible")
19 |
20 | // when
21 | layoutInspectorScreen.tapCloseButton()
22 | // then
23 | XCTAssert(app.isDisplayingLayoutInspection == false, "LI should not be visible")
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/Example/Podfile:
--------------------------------------------------------------------------------
1 | platform :ios, '11.0'
2 |
3 | target 'LayoutInspectorExample' do
4 | pod 'LayoutInspector', :path => '../'
5 |
6 |
7 | target 'LayoutInspectorExampleTests' do
8 | inherit! :search_paths
9 | end
10 |
11 | target 'LayoutInspectorExampleUITests' do
12 | inherit! :search_paths
13 | end
14 |
15 | end
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Example/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - LayoutInspector (1.2.0)
3 |
4 | DEPENDENCIES:
5 | - LayoutInspector (from `../`)
6 |
7 | EXTERNAL SOURCES:
8 | LayoutInspector:
9 | :path: "../"
10 |
11 | SPEC CHECKSUMS:
12 | LayoutInspector: b50a2937f1497a5a116d8975ea24b219f7813024
13 |
14 | PODFILE CHECKSUM: ceafd53887cd84376ba1b2a6cc02fd379b250eeb
15 |
16 | COCOAPODS: 1.7.5
17 |
--------------------------------------------------------------------------------
/Example/Pods/Headers/Public/LayoutInspector/LayoutInspector-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double LayoutInspectorVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char LayoutInspectorVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Example/Pods/Headers/Public/LayoutInspector/LayoutInspector.modulemap:
--------------------------------------------------------------------------------
1 | module LayoutInspector {
2 | umbrella header "LayoutInspector-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Pods/Local Podspecs/LayoutInspector.podspec.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "LayoutInspector",
3 | "version": "1.2.0",
4 | "summary": "LayoutInspector module",
5 | "description": "LayoutInspector is a tool for debugging layers on iOS devices. It allows to get common information about each visible view and to inspect layers in 3D mode",
6 | "homepage": "https://github.com/isavynskyi/LayoutInspector",
7 | "license": {
8 | "type": "MIT",
9 | "file": "LICENSE"
10 | },
11 | "authors": {
12 | "Ihor Savynskyi": "wadedunk08@gmail.com"
13 | },
14 | "source": {
15 | "git": "https://github.com/isavynskyi/LayoutInspector.git",
16 | "tag": "1.2.0"
17 | },
18 | "social_media_url": "https://twitter.com/iWadedunk",
19 | "platforms": {
20 | "ios": "11.0"
21 | },
22 | "swift_versions": "5.0",
23 | "source_files": "LayoutInspector/**/*.{h,m,swift}",
24 | "resources": [
25 | "LayoutInspector/**/*.xib",
26 | "LayoutInspector/**/*.xcassets",
27 | "LayoutInspector/**/*.storyboard"
28 | ],
29 | "swift_version": "5.0"
30 | }
31 |
--------------------------------------------------------------------------------
/Example/Pods/Manifest.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - LayoutInspector (1.2.0)
3 |
4 | DEPENDENCIES:
5 | - LayoutInspector (from `../`)
6 |
7 | EXTERNAL SOURCES:
8 | LayoutInspector:
9 | :path: "../"
10 |
11 | SPEC CHECKSUMS:
12 | LayoutInspector: b50a2937f1497a5a116d8975ea24b219f7813024
13 |
14 | PODFILE CHECKSUM: ceafd53887cd84376ba1b2a6cc02fd379b250eeb
15 |
16 | COCOAPODS: 1.7.5
17 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/LayoutInspector/LayoutInspector-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_LayoutInspector : NSObject
3 | @end
4 | @implementation PodsDummy_LayoutInspector
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/LayoutInspector/LayoutInspector-prefix.pch:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/LayoutInspector/LayoutInspector-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double LayoutInspectorVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char LayoutInspectorVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/LayoutInspector/LayoutInspector.modulemap:
--------------------------------------------------------------------------------
1 | module LayoutInspector {
2 | umbrella header "LayoutInspector-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/LayoutInspector/LayoutInspector.xcconfig:
--------------------------------------------------------------------------------
1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/LayoutInspector
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -import-underlying-module -Xcc -fmodule-map-file="${SRCROOT}/${MODULEMAP_FILE}"
4 | PODS_BUILD_DIR = ${BUILD_DIR}
5 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
6 | PODS_ROOT = ${SRCROOT}
7 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/../..
8 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
9 | SKIP_INSTALL = YES
10 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-LayoutInspectorExample/Pods-LayoutInspectorExample-acknowledgements.markdown:
--------------------------------------------------------------------------------
1 | # Acknowledgements
2 | This application makes use of the following third party libraries:
3 |
4 | ## LayoutInspector
5 |
6 | Copyright (c) 2019 Ihor Savynskyi
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining a copy
9 | of this software and associated documentation files (the "Software"), to deal
10 | in the Software without restriction, including without limitation the rights
11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | copies of the Software, and to permit persons to whom the Software is
13 | furnished to do so, subject to the following conditions:
14 |
15 | The above copyright notice and this permission notice shall be included in
16 | all copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | THE SOFTWARE.
25 |
26 | Generated by CocoaPods - https://cocoapods.org
27 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-LayoutInspectorExample/Pods-LayoutInspectorExample-acknowledgements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreferenceSpecifiers
6 |
7 |
8 | FooterText
9 | This application makes use of the following third party libraries:
10 | Title
11 | Acknowledgements
12 | Type
13 | PSGroupSpecifier
14 |
15 |
16 | FooterText
17 | Copyright (c) 2019 Ihor Savynskyi <wadedunk08@gmail.com>
18 |
19 | Permission is hereby granted, free of charge, to any person obtaining a copy
20 | of this software and associated documentation files (the "Software"), to deal
21 | in the Software without restriction, including without limitation the rights
22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
23 | copies of the Software, and to permit persons to whom the Software is
24 | furnished to do so, subject to the following conditions:
25 |
26 | The above copyright notice and this permission notice shall be included in
27 | all copies or substantial portions of the Software.
28 |
29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
35 | THE SOFTWARE.
36 |
37 | License
38 | MIT
39 | Title
40 | LayoutInspector
41 | Type
42 | PSGroupSpecifier
43 |
44 |
45 | FooterText
46 | Generated by CocoaPods - https://cocoapods.org
47 | Title
48 |
49 | Type
50 | PSGroupSpecifier
51 |
52 |
53 | StringsTable
54 | Acknowledgements
55 | Title
56 | Acknowledgements
57 |
58 |
59 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-LayoutInspectorExample/Pods-LayoutInspectorExample-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_LayoutInspectorExample : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_LayoutInspectorExample
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-LayoutInspectorExample/Pods-LayoutInspectorExample-frameworks.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 | set -u
4 | set -o pipefail
5 |
6 | if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then
7 | # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy
8 | # frameworks to, so exit 0 (signalling the script phase was successful).
9 | exit 0
10 | fi
11 |
12 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
13 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
14 |
15 | COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}"
16 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}"
17 |
18 | # Used as a return value for each invocation of `strip_invalid_archs` function.
19 | STRIP_BINARY_RETVAL=0
20 |
21 | # This protects against multiple targets copying the same framework dependency at the same time. The solution
22 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
23 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
24 |
25 | # Copies and strips a vendored framework
26 | install_framework()
27 | {
28 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
29 | local source="${BUILT_PRODUCTS_DIR}/$1"
30 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then
31 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")"
32 | elif [ -r "$1" ]; then
33 | local source="$1"
34 | fi
35 |
36 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
37 |
38 | if [ -L "${source}" ]; then
39 | echo "Symlinked..."
40 | source="$(readlink "${source}")"
41 | fi
42 |
43 | # Use filter instead of exclude so missing patterns don't throw errors.
44 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\""
45 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}"
46 |
47 | local basename
48 | basename="$(basename -s .framework "$1")"
49 | binary="${destination}/${basename}.framework/${basename}"
50 | if ! [ -r "$binary" ]; then
51 | binary="${destination}/${basename}"
52 | fi
53 |
54 | # Strip invalid architectures so "fat" simulator / device frameworks work on device
55 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then
56 | strip_invalid_archs "$binary"
57 | fi
58 |
59 | # Resign the code if required by the build settings to avoid unstable apps
60 | code_sign_if_enabled "${destination}/$(basename "$1")"
61 |
62 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7.
63 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then
64 | local swift_runtime_libs
65 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]})
66 | for lib in $swift_runtime_libs; do
67 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\""
68 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}"
69 | code_sign_if_enabled "${destination}/${lib}"
70 | done
71 | fi
72 | }
73 |
74 | # Copies and strips a vendored dSYM
75 | install_dsym() {
76 | local source="$1"
77 | if [ -r "$source" ]; then
78 | # Copy the dSYM into a the targets temp dir.
79 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\""
80 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}"
81 |
82 | local basename
83 | basename="$(basename -s .framework.dSYM "$source")"
84 | binary="${DERIVED_FILES_DIR}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}"
85 |
86 | # Strip invalid architectures so "fat" simulator / device frameworks work on device
87 | if [[ "$(file "$binary")" == *"Mach-O dSYM companion"* ]]; then
88 | strip_invalid_archs "$binary"
89 | fi
90 |
91 | if [[ $STRIP_BINARY_RETVAL == 1 ]]; then
92 | # Move the stripped file into its final destination.
93 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\""
94 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}"
95 | else
96 | # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing.
97 | touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM"
98 | fi
99 | fi
100 | }
101 |
102 | # Signs a framework with the provided identity
103 | code_sign_if_enabled() {
104 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
105 | # Use the current code_sign_identitiy
106 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
107 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'"
108 |
109 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
110 | code_sign_cmd="$code_sign_cmd &"
111 | fi
112 | echo "$code_sign_cmd"
113 | eval "$code_sign_cmd"
114 | fi
115 | }
116 |
117 | # Strip invalid architectures
118 | strip_invalid_archs() {
119 | binary="$1"
120 | # Get architectures for current target binary
121 | binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)"
122 | # Intersect them with the architectures we are building for
123 | intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)"
124 | # If there are no archs supported by this binary then warn the user
125 | if [[ -z "$intersected_archs" ]]; then
126 | echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)."
127 | STRIP_BINARY_RETVAL=0
128 | return
129 | fi
130 | stripped=""
131 | for arch in $binary_archs; do
132 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then
133 | # Strip non-valid architectures in-place
134 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1
135 | stripped="$stripped $arch"
136 | fi
137 | done
138 | if [[ "$stripped" ]]; then
139 | echo "Stripped $binary of architectures:$stripped"
140 | fi
141 | STRIP_BINARY_RETVAL=1
142 | }
143 |
144 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
145 | wait
146 | fi
147 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-LayoutInspectorExample/Pods-LayoutInspectorExample-resources-Debug-input-files.xcfilelist:
--------------------------------------------------------------------------------
1 | ${PODS_ROOT}/Target Support Files/Pods-LayoutInspectorExample/Pods-LayoutInspectorExample-resources.sh
2 | ${PODS_ROOT}/../../LayoutInspector/Assets/LayoutInspectorAssets.xcassets
3 | ${PODS_ROOT}/../../LayoutInspector/LayoutInspector.storyboard
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-LayoutInspectorExample/Pods-LayoutInspectorExample-resources-Debug-output-files.xcfilelist:
--------------------------------------------------------------------------------
1 | ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Assets.car
2 | ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/LayoutInspector.storyboardc
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-LayoutInspectorExample/Pods-LayoutInspectorExample-resources-Release-input-files.xcfilelist:
--------------------------------------------------------------------------------
1 | ${PODS_ROOT}/Target Support Files/Pods-LayoutInspectorExample/Pods-LayoutInspectorExample-resources.sh
2 | ${PODS_ROOT}/../../LayoutInspector/Assets/LayoutInspectorAssets.xcassets
3 | ${PODS_ROOT}/../../LayoutInspector/LayoutInspector.storyboard
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-LayoutInspectorExample/Pods-LayoutInspectorExample-resources-Release-output-files.xcfilelist:
--------------------------------------------------------------------------------
1 | ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Assets.car
2 | ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/LayoutInspector.storyboardc
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-LayoutInspectorExample/Pods-LayoutInspectorExample-resources.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 | set -u
4 | set -o pipefail
5 |
6 | function on_error {
7 | echo "$(realpath -mq "${0}"):$1: error: Unexpected failure"
8 | }
9 | trap 'on_error $LINENO' ERR
10 |
11 | if [ -z ${UNLOCALIZED_RESOURCES_FOLDER_PATH+x} ]; then
12 | # If UNLOCALIZED_RESOURCES_FOLDER_PATH is not set, then there's nowhere for us to copy
13 | # resources to, so exit 0 (signalling the script phase was successful).
14 | exit 0
15 | fi
16 |
17 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
18 |
19 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt
20 | > "$RESOURCES_TO_COPY"
21 |
22 | XCASSET_FILES=()
23 |
24 | # This protects against multiple targets copying the same framework dependency at the same time. The solution
25 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
26 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
27 |
28 | case "${TARGETED_DEVICE_FAMILY:-}" in
29 | 1,2)
30 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone"
31 | ;;
32 | 1)
33 | TARGET_DEVICE_ARGS="--target-device iphone"
34 | ;;
35 | 2)
36 | TARGET_DEVICE_ARGS="--target-device ipad"
37 | ;;
38 | 3)
39 | TARGET_DEVICE_ARGS="--target-device tv"
40 | ;;
41 | 4)
42 | TARGET_DEVICE_ARGS="--target-device watch"
43 | ;;
44 | *)
45 | TARGET_DEVICE_ARGS="--target-device mac"
46 | ;;
47 | esac
48 |
49 | install_resource()
50 | {
51 | if [[ "$1" = /* ]] ; then
52 | RESOURCE_PATH="$1"
53 | else
54 | RESOURCE_PATH="${PODS_ROOT}/$1"
55 | fi
56 | if [[ ! -e "$RESOURCE_PATH" ]] ; then
57 | cat << EOM
58 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script.
59 | EOM
60 | exit 1
61 | fi
62 | case $RESOURCE_PATH in
63 | *.storyboard)
64 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true
65 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
66 | ;;
67 | *.xib)
68 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true
69 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
70 | ;;
71 | *.framework)
72 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true
73 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
74 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true
75 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
76 | ;;
77 | *.xcdatamodel)
78 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true
79 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom"
80 | ;;
81 | *.xcdatamodeld)
82 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true
83 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd"
84 | ;;
85 | *.xcmappingmodel)
86 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true
87 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm"
88 | ;;
89 | *.xcassets)
90 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH"
91 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE")
92 | ;;
93 | *)
94 | echo "$RESOURCE_PATH" || true
95 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY"
96 | ;;
97 | esac
98 | }
99 | if [[ "$CONFIGURATION" == "Debug" ]]; then
100 | install_resource "${PODS_ROOT}/../../LayoutInspector/Assets/LayoutInspectorAssets.xcassets"
101 | install_resource "${PODS_ROOT}/../../LayoutInspector/LayoutInspector.storyboard"
102 | fi
103 | if [[ "$CONFIGURATION" == "Release" ]]; then
104 | install_resource "${PODS_ROOT}/../../LayoutInspector/Assets/LayoutInspectorAssets.xcassets"
105 | install_resource "${PODS_ROOT}/../../LayoutInspector/LayoutInspector.storyboard"
106 | fi
107 |
108 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
109 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
110 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then
111 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
112 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
113 | fi
114 | rm -f "$RESOURCES_TO_COPY"
115 |
116 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "${XCASSET_FILES:-}" ]
117 | then
118 | # Find all other xcassets (this unfortunately includes those of path pods and other targets).
119 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d)
120 | while read line; do
121 | if [[ $line != "${PODS_ROOT}*" ]]; then
122 | XCASSET_FILES+=("$line")
123 | fi
124 | done <<<"$OTHER_XCASSETS"
125 |
126 | if [ -z ${ASSETCATALOG_COMPILER_APPICON_NAME+x} ]; then
127 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
128 | else
129 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" --app-icon "${ASSETCATALOG_COMPILER_APPICON_NAME}" --output-partial-info-plist "${TARGET_TEMP_DIR}/assetcatalog_generated_info_cocoapods.plist"
130 | fi
131 | fi
132 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-LayoutInspectorExample/Pods-LayoutInspectorExample-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double Pods_LayoutInspectorExampleVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char Pods_LayoutInspectorExampleVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-LayoutInspectorExample/Pods-LayoutInspectorExample.debug.xcconfig:
--------------------------------------------------------------------------------
1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/LayoutInspector"
4 | OTHER_CFLAGS = $(inherited) -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/LayoutInspector/LayoutInspector.modulemap"
5 | OTHER_LDFLAGS = $(inherited) -ObjC -l"LayoutInspector"
6 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/LayoutInspector/LayoutInspector.modulemap"
7 | PODS_BUILD_DIR = ${BUILD_DIR}
8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
9 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
10 | PODS_ROOT = ${SRCROOT}/Pods
11 | SWIFT_INCLUDE_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/LayoutInspector"
12 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-LayoutInspectorExample/Pods-LayoutInspectorExample.modulemap:
--------------------------------------------------------------------------------
1 | module Pods_LayoutInspectorExample {
2 | umbrella header "Pods-LayoutInspectorExample-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-LayoutInspectorExample/Pods-LayoutInspectorExample.release.xcconfig:
--------------------------------------------------------------------------------
1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/LayoutInspector"
4 | OTHER_CFLAGS = $(inherited) -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/LayoutInspector/LayoutInspector.modulemap"
5 | OTHER_LDFLAGS = $(inherited) -ObjC -l"LayoutInspector"
6 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/LayoutInspector/LayoutInspector.modulemap"
7 | PODS_BUILD_DIR = ${BUILD_DIR}
8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
9 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
10 | PODS_ROOT = ${SRCROOT}/Pods
11 | SWIFT_INCLUDE_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/LayoutInspector"
12 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-LayoutInspectorExampleTests/Pods-LayoutInspectorExampleTests-acknowledgements.markdown:
--------------------------------------------------------------------------------
1 | # Acknowledgements
2 | This application makes use of the following third party libraries:
3 | Generated by CocoaPods - https://cocoapods.org
4 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-LayoutInspectorExampleTests/Pods-LayoutInspectorExampleTests-acknowledgements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreferenceSpecifiers
6 |
7 |
8 | FooterText
9 | This application makes use of the following third party libraries:
10 | Title
11 | Acknowledgements
12 | Type
13 | PSGroupSpecifier
14 |
15 |
16 | FooterText
17 | Generated by CocoaPods - https://cocoapods.org
18 | Title
19 |
20 | Type
21 | PSGroupSpecifier
22 |
23 |
24 | StringsTable
25 | Acknowledgements
26 | Title
27 | Acknowledgements
28 |
29 |
30 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-LayoutInspectorExampleTests/Pods-LayoutInspectorExampleTests-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_LayoutInspectorExampleTests : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_LayoutInspectorExampleTests
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-LayoutInspectorExampleTests/Pods-LayoutInspectorExampleTests-frameworks.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 | set -u
4 | set -o pipefail
5 |
6 | if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then
7 | # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy
8 | # frameworks to, so exit 0 (signalling the script phase was successful).
9 | exit 0
10 | fi
11 |
12 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
13 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
14 |
15 | COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}"
16 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}"
17 |
18 | # Used as a return value for each invocation of `strip_invalid_archs` function.
19 | STRIP_BINARY_RETVAL=0
20 |
21 | # This protects against multiple targets copying the same framework dependency at the same time. The solution
22 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
23 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
24 |
25 | # Copies and strips a vendored framework
26 | install_framework()
27 | {
28 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
29 | local source="${BUILT_PRODUCTS_DIR}/$1"
30 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then
31 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")"
32 | elif [ -r "$1" ]; then
33 | local source="$1"
34 | fi
35 |
36 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
37 |
38 | if [ -L "${source}" ]; then
39 | echo "Symlinked..."
40 | source="$(readlink "${source}")"
41 | fi
42 |
43 | # Use filter instead of exclude so missing patterns don't throw errors.
44 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\""
45 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}"
46 |
47 | local basename
48 | basename="$(basename -s .framework "$1")"
49 | binary="${destination}/${basename}.framework/${basename}"
50 | if ! [ -r "$binary" ]; then
51 | binary="${destination}/${basename}"
52 | fi
53 |
54 | # Strip invalid architectures so "fat" simulator / device frameworks work on device
55 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then
56 | strip_invalid_archs "$binary"
57 | fi
58 |
59 | # Resign the code if required by the build settings to avoid unstable apps
60 | code_sign_if_enabled "${destination}/$(basename "$1")"
61 |
62 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7.
63 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then
64 | local swift_runtime_libs
65 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]})
66 | for lib in $swift_runtime_libs; do
67 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\""
68 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}"
69 | code_sign_if_enabled "${destination}/${lib}"
70 | done
71 | fi
72 | }
73 |
74 | # Copies and strips a vendored dSYM
75 | install_dsym() {
76 | local source="$1"
77 | if [ -r "$source" ]; then
78 | # Copy the dSYM into a the targets temp dir.
79 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\""
80 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}"
81 |
82 | local basename
83 | basename="$(basename -s .framework.dSYM "$source")"
84 | binary="${DERIVED_FILES_DIR}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}"
85 |
86 | # Strip invalid architectures so "fat" simulator / device frameworks work on device
87 | if [[ "$(file "$binary")" == *"Mach-O dSYM companion"* ]]; then
88 | strip_invalid_archs "$binary"
89 | fi
90 |
91 | if [[ $STRIP_BINARY_RETVAL == 1 ]]; then
92 | # Move the stripped file into its final destination.
93 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\""
94 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}"
95 | else
96 | # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing.
97 | touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM"
98 | fi
99 | fi
100 | }
101 |
102 | # Signs a framework with the provided identity
103 | code_sign_if_enabled() {
104 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
105 | # Use the current code_sign_identitiy
106 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
107 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'"
108 |
109 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
110 | code_sign_cmd="$code_sign_cmd &"
111 | fi
112 | echo "$code_sign_cmd"
113 | eval "$code_sign_cmd"
114 | fi
115 | }
116 |
117 | # Strip invalid architectures
118 | strip_invalid_archs() {
119 | binary="$1"
120 | # Get architectures for current target binary
121 | binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)"
122 | # Intersect them with the architectures we are building for
123 | intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)"
124 | # If there are no archs supported by this binary then warn the user
125 | if [[ -z "$intersected_archs" ]]; then
126 | echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)."
127 | STRIP_BINARY_RETVAL=0
128 | return
129 | fi
130 | stripped=""
131 | for arch in $binary_archs; do
132 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then
133 | # Strip non-valid architectures in-place
134 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1
135 | stripped="$stripped $arch"
136 | fi
137 | done
138 | if [[ "$stripped" ]]; then
139 | echo "Stripped $binary of architectures:$stripped"
140 | fi
141 | STRIP_BINARY_RETVAL=1
142 | }
143 |
144 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
145 | wait
146 | fi
147 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-LayoutInspectorExampleTests/Pods-LayoutInspectorExampleTests-resources.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 | set -u
4 | set -o pipefail
5 |
6 | if [ -z ${UNLOCALIZED_RESOURCES_FOLDER_PATH+x} ]; then
7 | # If UNLOCALIZED_RESOURCES_FOLDER_PATH is not set, then there's nowhere for us to copy
8 | # resources to, so exit 0 (signalling the script phase was successful).
9 | exit 0
10 | fi
11 |
12 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
13 |
14 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt
15 | > "$RESOURCES_TO_COPY"
16 |
17 | XCASSET_FILES=()
18 |
19 | # This protects against multiple targets copying the same framework dependency at the same time. The solution
20 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
21 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
22 |
23 | case "${TARGETED_DEVICE_FAMILY:-}" in
24 | 1,2)
25 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone"
26 | ;;
27 | 1)
28 | TARGET_DEVICE_ARGS="--target-device iphone"
29 | ;;
30 | 2)
31 | TARGET_DEVICE_ARGS="--target-device ipad"
32 | ;;
33 | 3)
34 | TARGET_DEVICE_ARGS="--target-device tv"
35 | ;;
36 | 4)
37 | TARGET_DEVICE_ARGS="--target-device watch"
38 | ;;
39 | *)
40 | TARGET_DEVICE_ARGS="--target-device mac"
41 | ;;
42 | esac
43 |
44 | install_resource()
45 | {
46 | if [[ "$1" = /* ]] ; then
47 | RESOURCE_PATH="$1"
48 | else
49 | RESOURCE_PATH="${PODS_ROOT}/$1"
50 | fi
51 | if [[ ! -e "$RESOURCE_PATH" ]] ; then
52 | cat << EOM
53 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script.
54 | EOM
55 | exit 1
56 | fi
57 | case $RESOURCE_PATH in
58 | *.storyboard)
59 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true
60 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
61 | ;;
62 | *.xib)
63 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true
64 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
65 | ;;
66 | *.framework)
67 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true
68 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
69 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true
70 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
71 | ;;
72 | *.xcdatamodel)
73 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true
74 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom"
75 | ;;
76 | *.xcdatamodeld)
77 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true
78 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd"
79 | ;;
80 | *.xcmappingmodel)
81 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true
82 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm"
83 | ;;
84 | *.xcassets)
85 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH"
86 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE")
87 | ;;
88 | *)
89 | echo "$RESOURCE_PATH" || true
90 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY"
91 | ;;
92 | esac
93 | }
94 |
95 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
96 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
97 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then
98 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
99 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
100 | fi
101 | rm -f "$RESOURCES_TO_COPY"
102 |
103 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "${XCASSET_FILES:-}" ]
104 | then
105 | # Find all other xcassets (this unfortunately includes those of path pods and other targets).
106 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d)
107 | while read line; do
108 | if [[ $line != "${PODS_ROOT}*" ]]; then
109 | XCASSET_FILES+=("$line")
110 | fi
111 | done <<<"$OTHER_XCASSETS"
112 |
113 | if [ -z ${ASSETCATALOG_COMPILER_APPICON_NAME+x} ]; then
114 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
115 | else
116 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" --app-icon "${ASSETCATALOG_COMPILER_APPICON_NAME}" --output-partial-info-plist "${TARGET_TEMP_DIR}/assetcatalog_generated_info_cocoapods.plist"
117 | fi
118 | fi
119 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-LayoutInspectorExampleTests/Pods-LayoutInspectorExampleTests.debug.xcconfig:
--------------------------------------------------------------------------------
1 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
2 | OTHER_CFLAGS = $(inherited) -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/LayoutInspector/LayoutInspector.modulemap"
3 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/LayoutInspector/LayoutInspector.modulemap"
4 | PODS_BUILD_DIR = ${BUILD_DIR}
5 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
6 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
7 | PODS_ROOT = ${SRCROOT}/Pods
8 | SWIFT_INCLUDE_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/LayoutInspector"
9 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-LayoutInspectorExampleTests/Pods-LayoutInspectorExampleTests.release.xcconfig:
--------------------------------------------------------------------------------
1 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
2 | OTHER_CFLAGS = $(inherited) -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/LayoutInspector/LayoutInspector.modulemap"
3 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/LayoutInspector/LayoutInspector.modulemap"
4 | PODS_BUILD_DIR = ${BUILD_DIR}
5 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
6 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
7 | PODS_ROOT = ${SRCROOT}/Pods
8 | SWIFT_INCLUDE_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/LayoutInspector"
9 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-LayoutInspectorExampleUITests/Pods-LayoutInspectorExampleUITests-acknowledgements.markdown:
--------------------------------------------------------------------------------
1 | # Acknowledgements
2 | This application makes use of the following third party libraries:
3 | Generated by CocoaPods - https://cocoapods.org
4 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-LayoutInspectorExampleUITests/Pods-LayoutInspectorExampleUITests-acknowledgements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreferenceSpecifiers
6 |
7 |
8 | FooterText
9 | This application makes use of the following third party libraries:
10 | Title
11 | Acknowledgements
12 | Type
13 | PSGroupSpecifier
14 |
15 |
16 | FooterText
17 | Generated by CocoaPods - https://cocoapods.org
18 | Title
19 |
20 | Type
21 | PSGroupSpecifier
22 |
23 |
24 | StringsTable
25 | Acknowledgements
26 | Title
27 | Acknowledgements
28 |
29 |
30 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-LayoutInspectorExampleUITests/Pods-LayoutInspectorExampleUITests-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_LayoutInspectorExampleUITests : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_LayoutInspectorExampleUITests
5 | @end
6 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-LayoutInspectorExampleUITests/Pods-LayoutInspectorExampleUITests-frameworks.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 | set -u
4 | set -o pipefail
5 |
6 | if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then
7 | # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy
8 | # frameworks to, so exit 0 (signalling the script phase was successful).
9 | exit 0
10 | fi
11 |
12 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
13 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
14 |
15 | COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}"
16 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}"
17 |
18 | # Used as a return value for each invocation of `strip_invalid_archs` function.
19 | STRIP_BINARY_RETVAL=0
20 |
21 | # This protects against multiple targets copying the same framework dependency at the same time. The solution
22 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
23 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
24 |
25 | # Copies and strips a vendored framework
26 | install_framework()
27 | {
28 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
29 | local source="${BUILT_PRODUCTS_DIR}/$1"
30 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then
31 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")"
32 | elif [ -r "$1" ]; then
33 | local source="$1"
34 | fi
35 |
36 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
37 |
38 | if [ -L "${source}" ]; then
39 | echo "Symlinked..."
40 | source="$(readlink "${source}")"
41 | fi
42 |
43 | # Use filter instead of exclude so missing patterns don't throw errors.
44 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\""
45 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}"
46 |
47 | local basename
48 | basename="$(basename -s .framework "$1")"
49 | binary="${destination}/${basename}.framework/${basename}"
50 | if ! [ -r "$binary" ]; then
51 | binary="${destination}/${basename}"
52 | fi
53 |
54 | # Strip invalid architectures so "fat" simulator / device frameworks work on device
55 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then
56 | strip_invalid_archs "$binary"
57 | fi
58 |
59 | # Resign the code if required by the build settings to avoid unstable apps
60 | code_sign_if_enabled "${destination}/$(basename "$1")"
61 |
62 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7.
63 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then
64 | local swift_runtime_libs
65 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]})
66 | for lib in $swift_runtime_libs; do
67 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\""
68 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}"
69 | code_sign_if_enabled "${destination}/${lib}"
70 | done
71 | fi
72 | }
73 |
74 | # Copies and strips a vendored dSYM
75 | install_dsym() {
76 | local source="$1"
77 | if [ -r "$source" ]; then
78 | # Copy the dSYM into a the targets temp dir.
79 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\""
80 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}"
81 |
82 | local basename
83 | basename="$(basename -s .framework.dSYM "$source")"
84 | binary="${DERIVED_FILES_DIR}/${basename}.framework.dSYM/Contents/Resources/DWARF/${basename}"
85 |
86 | # Strip invalid architectures so "fat" simulator / device frameworks work on device
87 | if [[ "$(file "$binary")" == *"Mach-O dSYM companion"* ]]; then
88 | strip_invalid_archs "$binary"
89 | fi
90 |
91 | if [[ $STRIP_BINARY_RETVAL == 1 ]]; then
92 | # Move the stripped file into its final destination.
93 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\""
94 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.framework.dSYM" "${DWARF_DSYM_FOLDER_PATH}"
95 | else
96 | # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing.
97 | touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.framework.dSYM"
98 | fi
99 | fi
100 | }
101 |
102 | # Signs a framework with the provided identity
103 | code_sign_if_enabled() {
104 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
105 | # Use the current code_sign_identitiy
106 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
107 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'"
108 |
109 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
110 | code_sign_cmd="$code_sign_cmd &"
111 | fi
112 | echo "$code_sign_cmd"
113 | eval "$code_sign_cmd"
114 | fi
115 | }
116 |
117 | # Strip invalid architectures
118 | strip_invalid_archs() {
119 | binary="$1"
120 | # Get architectures for current target binary
121 | binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)"
122 | # Intersect them with the architectures we are building for
123 | intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)"
124 | # If there are no archs supported by this binary then warn the user
125 | if [[ -z "$intersected_archs" ]]; then
126 | echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)."
127 | STRIP_BINARY_RETVAL=0
128 | return
129 | fi
130 | stripped=""
131 | for arch in $binary_archs; do
132 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then
133 | # Strip non-valid architectures in-place
134 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1
135 | stripped="$stripped $arch"
136 | fi
137 | done
138 | if [[ "$stripped" ]]; then
139 | echo "Stripped $binary of architectures:$stripped"
140 | fi
141 | STRIP_BINARY_RETVAL=1
142 | }
143 |
144 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
145 | wait
146 | fi
147 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-LayoutInspectorExampleUITests/Pods-LayoutInspectorExampleUITests-resources.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 | set -u
4 | set -o pipefail
5 |
6 | if [ -z ${UNLOCALIZED_RESOURCES_FOLDER_PATH+x} ]; then
7 | # If UNLOCALIZED_RESOURCES_FOLDER_PATH is not set, then there's nowhere for us to copy
8 | # resources to, so exit 0 (signalling the script phase was successful).
9 | exit 0
10 | fi
11 |
12 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
13 |
14 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt
15 | > "$RESOURCES_TO_COPY"
16 |
17 | XCASSET_FILES=()
18 |
19 | # This protects against multiple targets copying the same framework dependency at the same time. The solution
20 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
21 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
22 |
23 | case "${TARGETED_DEVICE_FAMILY:-}" in
24 | 1,2)
25 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone"
26 | ;;
27 | 1)
28 | TARGET_DEVICE_ARGS="--target-device iphone"
29 | ;;
30 | 2)
31 | TARGET_DEVICE_ARGS="--target-device ipad"
32 | ;;
33 | 3)
34 | TARGET_DEVICE_ARGS="--target-device tv"
35 | ;;
36 | 4)
37 | TARGET_DEVICE_ARGS="--target-device watch"
38 | ;;
39 | *)
40 | TARGET_DEVICE_ARGS="--target-device mac"
41 | ;;
42 | esac
43 |
44 | install_resource()
45 | {
46 | if [[ "$1" = /* ]] ; then
47 | RESOURCE_PATH="$1"
48 | else
49 | RESOURCE_PATH="${PODS_ROOT}/$1"
50 | fi
51 | if [[ ! -e "$RESOURCE_PATH" ]] ; then
52 | cat << EOM
53 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script.
54 | EOM
55 | exit 1
56 | fi
57 | case $RESOURCE_PATH in
58 | *.storyboard)
59 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true
60 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
61 | ;;
62 | *.xib)
63 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true
64 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
65 | ;;
66 | *.framework)
67 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true
68 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
69 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true
70 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
71 | ;;
72 | *.xcdatamodel)
73 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true
74 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom"
75 | ;;
76 | *.xcdatamodeld)
77 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true
78 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd"
79 | ;;
80 | *.xcmappingmodel)
81 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true
82 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm"
83 | ;;
84 | *.xcassets)
85 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH"
86 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE")
87 | ;;
88 | *)
89 | echo "$RESOURCE_PATH" || true
90 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY"
91 | ;;
92 | esac
93 | }
94 |
95 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
96 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
97 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then
98 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
99 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
100 | fi
101 | rm -f "$RESOURCES_TO_COPY"
102 |
103 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "${XCASSET_FILES:-}" ]
104 | then
105 | # Find all other xcassets (this unfortunately includes those of path pods and other targets).
106 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d)
107 | while read line; do
108 | if [[ $line != "${PODS_ROOT}*" ]]; then
109 | XCASSET_FILES+=("$line")
110 | fi
111 | done <<<"$OTHER_XCASSETS"
112 |
113 | if [ -z ${ASSETCATALOG_COMPILER_APPICON_NAME+x} ]; then
114 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
115 | else
116 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" --app-icon "${ASSETCATALOG_COMPILER_APPICON_NAME}" --output-partial-info-plist "${TARGET_TEMP_DIR}/assetcatalog_generated_info_cocoapods.plist"
117 | fi
118 | fi
119 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-LayoutInspectorExampleUITests/Pods-LayoutInspectorExampleUITests.debug.xcconfig:
--------------------------------------------------------------------------------
1 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
2 | OTHER_CFLAGS = $(inherited) -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/LayoutInspector/LayoutInspector.modulemap"
3 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/LayoutInspector/LayoutInspector.modulemap"
4 | PODS_BUILD_DIR = ${BUILD_DIR}
5 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
6 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
7 | PODS_ROOT = ${SRCROOT}/Pods
8 | SWIFT_INCLUDE_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/LayoutInspector"
9 |
--------------------------------------------------------------------------------
/Example/Pods/Target Support Files/Pods-LayoutInspectorExampleUITests/Pods-LayoutInspectorExampleUITests.release.xcconfig:
--------------------------------------------------------------------------------
1 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
2 | OTHER_CFLAGS = $(inherited) -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/LayoutInspector/LayoutInspector.modulemap"
3 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/LayoutInspector/LayoutInspector.modulemap"
4 | PODS_BUILD_DIR = ${BUILD_DIR}
5 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
6 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
7 | PODS_ROOT = ${SRCROOT}/Pods
8 | SWIFT_INCLUDE_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/LayoutInspector"
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2019 Ihor Savynskyi
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/LayoutInspector.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'LayoutInspector'
3 | s.version = '1.2.1'
4 | s.summary = 'LayoutInspector module'
5 |
6 | s.description = <<-DESC
7 | LayoutInspector is a tool for debugging layers on iOS devices. It allows to get common information about each visible view and to inspect layers in 3D mode
8 | DESC
9 |
10 | s.homepage = 'https://github.com/isavynskyi/LayoutInspector'
11 | s.license = { :type => 'MIT', :file => 'LICENSE' }
12 | s.author = { 'Ihor Savynskyi' => 'wadedunk08@gmail.com' }
13 | s.source = { :git => 'https://github.com/isavynskyi/LayoutInspector.git', :tag => s.version.to_s }
14 |
15 | s.ios.deployment_target = '11.0'
16 | s.swift_version = '5.0'
17 | s.source_files = 'LayoutInspector/**/*.{h,m,swift}'
18 | s.resources = [
19 | "LayoutInspector/**/*.xib",
20 | "LayoutInspector/**/*.xcassets",
21 | "LayoutInspector/**/*.storyboard"
22 | ]
23 |
24 | end
25 |
--------------------------------------------------------------------------------
/LayoutInspector/Assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/isavynskyi/LayoutInspector/72583864c147301c702c59ed4bbb5356114b9539/LayoutInspector/Assets/.gitkeep
--------------------------------------------------------------------------------
/LayoutInspector/Assets/LayoutInspectorAssets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/LayoutInspector/Assets/LayoutInspectorAssets.xcassets/checkeredPattern.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "Screen Shot 2019-01-04 at 11.14.20 PM.png"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | }
12 | }
--------------------------------------------------------------------------------
/LayoutInspector/Assets/LayoutInspectorAssets.xcassets/checkeredPattern.imageset/Screen Shot 2019-01-04 at 11.14.20 PM.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/isavynskyi/LayoutInspector/72583864c147301c702c59ed4bbb5356114b9539/LayoutInspector/Assets/LayoutInspectorAssets.xcassets/checkeredPattern.imageset/Screen Shot 2019-01-04 at 11.14.20 PM.png
--------------------------------------------------------------------------------
/LayoutInspector/Assets/LayoutInspectorAssets.xcassets/closeIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "dialog_close_1342689.png"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
--------------------------------------------------------------------------------
/LayoutInspector/Assets/LayoutInspectorAssets.xcassets/closeIcon.imageset/dialog_close_1342689.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/isavynskyi/LayoutInspector/72583864c147301c702c59ed4bbb5356114b9539/LayoutInspector/Assets/LayoutInspectorAssets.xcassets/closeIcon.imageset/dialog_close_1342689.png
--------------------------------------------------------------------------------
/LayoutInspector/Assets/LayoutInspectorAssets.xcassets/resetIcon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "collect.png"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
--------------------------------------------------------------------------------
/LayoutInspector/Assets/LayoutInspectorAssets.xcassets/resetIcon.imageset/collect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/isavynskyi/LayoutInspector/72583864c147301c702c59ed4bbb5356114b9539/LayoutInspector/Assets/LayoutInspectorAssets.xcassets/resetIcon.imageset/collect.png
--------------------------------------------------------------------------------
/LayoutInspector/Assets/LayoutInspectorAssets.xcassets/transparent_view_image.imageset/100x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/isavynskyi/LayoutInspector/72583864c147301c702c59ed4bbb5356114b9539/LayoutInspector/Assets/LayoutInspectorAssets.xcassets/transparent_view_image.imageset/100x100.png
--------------------------------------------------------------------------------
/LayoutInspector/Assets/LayoutInspectorAssets.xcassets/transparent_view_image.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "100x100.png",
6 | "resizing" : {
7 | "mode" : "9-part",
8 | "center" : {
9 | "mode" : "stretch",
10 | "width" : 95,
11 | "height" : 95
12 | },
13 | "cap-insets" : {
14 | "bottom" : 98,
15 | "top" : 2,
16 | "right" : 95,
17 | "left" : 2
18 | }
19 | }
20 | }
21 | ],
22 | "info" : {
23 | "version" : 1,
24 | "author" : "xcode"
25 | }
26 | }
--------------------------------------------------------------------------------
/LayoutInspector/Classes/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/isavynskyi/LayoutInspector/72583864c147301c702c59ed4bbb5356114b9539/LayoutInspector/Classes/.gitkeep
--------------------------------------------------------------------------------
/LayoutInspector/Classes/Domain Layer/HierarchyBuilder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HierarchyBuilder.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 12/26/18.
6 | // Copyright © 2018 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | protocol HierarchyBuilderProtocol {
12 | func captureHierarchy() -> ViewDescriptionProtocol?
13 | }
14 |
15 | class HierarchyBuilder: HierarchyBuilderProtocol {
16 | func captureHierarchy() -> ViewDescriptionProtocol? {
17 | guard let firstWindow = UIApplication.shared.windows.first else { return nil }
18 | return buildHierarchy(view: firstWindow)
19 | }
20 | }
21 |
22 | // MARK: Private API
23 | private extension HierarchyBuilder {
24 | func buildHierarchy(view: UIView) -> ViewDescriptionProtocol {
25 | let children = view.subviews.map { buildHierarchy(view: $0) }
26 | let viewsToHide = view.subviews.filter { $0.isHidden == false }
27 |
28 | // don't capture visible subviews for current view snapshot
29 | viewsToHide.forEach { $0.isHidden = true }
30 | let isTransparent = isViewTransparent(view)
31 | let image = isTransparent ? nil : view.asImage()
32 | // hidden subviews rollback
33 | viewsToHide.forEach { $0.isHidden = false }
34 |
35 | return ViewDescription(frame: view.frame,
36 | snapshot: image,
37 | subviews: children,
38 | parentSize: view.superview?.frame.size,
39 | center: view.center,
40 | isHidden: view.isHidden,
41 | isTransparent: isTransparent,
42 | className: String(describing: type(of: view)),
43 | isUserInteractionEnabled: view.isUserInteractionEnabled,
44 | alpha: Float(view.alpha),
45 | backgroundColor: view.backgroundColor,
46 | tint: view.tintColor,
47 | clipToBounds: view.clipsToBounds,
48 | font: view.visibleFont)
49 | }
50 |
51 | func isViewTransparent(_ view: UIView) -> Bool {
52 | let isTransparent: Bool
53 | if view.isKind(of: UIImageView.self) || view.isKind(of: UILabel.self) || view.isKind(of: UITextView.self) {
54 | isTransparent = false
55 | } else if view.backgroundColor == .clear || view.alpha == 0 || view.backgroundColor?.alphaValue == 0 || view.backgroundColor == nil {
56 | isTransparent = true
57 | } else {
58 | isTransparent = false
59 | }
60 | return isTransparent
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/Domain Layer/Utils/Bundle+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Bundle+Extension.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 1/4/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Bundle {
12 | static let layoutInspectorBundle = Bundle(for: LayoutInspector.self)
13 | }
14 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/Domain Layer/Utils/Segue.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Segue.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 1/2/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | enum Segue: String {
10 | case toMenuWidgetViewControler = "toMenuWidgetViewControler"
11 | case toAttributesWidgetViewController = "toAttributesWidgetViewController"
12 | case toSceneWidgetViewController = "toSceneWidgetViewController"
13 | }
14 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/Domain Layer/Utils/String+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+Extension.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 1/3/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension String {
12 | func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
13 | let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
14 | let boundingBox = self.boundingRect(with: constraintRect,
15 | options: .usesLineFragmentOrigin,
16 | attributes: [.font : font],
17 | context: nil)
18 | return ceil(boundingBox.height)
19 | }
20 |
21 | func width(withConstrainedHeight height: CGFloat, font: UIFont) -> CGFloat {
22 | let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
23 | let boundingBox = self.boundingRect(with: constraintRect,
24 | options: .usesLineFragmentOrigin,
25 | attributes: [.font : font],
26 | context: nil)
27 | return ceil(boundingBox.width)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/Domain Layer/Utils/UICollectionView+Extention.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UICollectionView+Extention.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 1/2/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | protocol ReuseIdentifying {
12 | static var reuseIdentifier: String { get }
13 | }
14 |
15 | extension ReuseIdentifying {
16 | static var reuseIdentifier: String {
17 | return String(describing: Self.self)
18 | }
19 | }
20 |
21 | extension UICollectionViewCell: ReuseIdentifying {}
22 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/Domain Layer/Utils/UIColor+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIColor+Extension.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 1/2/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | // MARK: - Colors palette
12 | extension UIColor {
13 | static let sceneBackground = #colorLiteral(red: 0.2509803922, green: 0.2509803922, blue: 0.2509803922, alpha: 1)
14 | static let selectionBorders = #colorLiteral(red: 0.2549019608, green: 0.4156862745, blue: 0.7607843137, alpha: 1)
15 | static let appLight = #colorLiteral(red: 0.8862745098, green: 0.8862745098, blue: 0.8862745098, alpha: 1)
16 | static let secondaryLightColor = #colorLiteral(red: 0.337254902, green: 0.337254902, blue: 0.337254902, alpha: 1)
17 | }
18 |
19 | extension UIColor {
20 | var redValue: CGFloat { return CIColor(color: self).red }
21 | var greenValue: CGFloat { return CIColor(color: self).green }
22 | var blueValue: CGFloat { return CIColor(color: self).blue }
23 | var alphaValue: CGFloat { return CIColor(color: self).alpha }
24 | }
25 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/Domain Layer/Utils/UIFont+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIFont+Extension.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 1/2/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIFont {
12 | static let appH3 = UIFont.systemFont(ofSize: 12)
13 |
14 | var attributeDescription: String {
15 | "name:\(fontName); weight:\(fontDescriptor.object(forKey: .face) ?? "?"); size:\(fontDescriptor.object(forKey: .size) ?? "?"); style:\(fontDescriptor.object(forKey: .textStyle) ?? "?")"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/Domain Layer/Utils/UIView+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIView+Extension.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 12/25/18.
6 | // Copyright © 2018 Igor Savynskyi. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIView {
12 | func asImage() -> UIImage {
13 | let renderer = UIGraphicsImageRenderer(bounds: bounds)
14 | return renderer.image { rendererContext in
15 | layer.render(in: rendererContext.cgContext)
16 | }
17 | }
18 |
19 | var visibleFont: UIFont? {
20 | if let textView = self as? UITextView {
21 | return textView.font
22 | } else if let label = self as? UILabel {
23 | return label.font
24 | } else if let button = self as? UIButton {
25 | return button.titleLabel?.font
26 | } else if let textField = self as? UITextField {
27 | return textField.font
28 | }
29 |
30 | return nil
31 | }
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/Domain Layer/Utils/UtilsProtocols.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Protocols.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 1/2/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | @objc protocol Themeable {
12 | func configureStyles()
13 | }
14 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/Domain Layer/ViewControllerFactory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewControllerFactory.swift
3 | // LayoutInspector
4 | //
5 | // Created by Igor Savynskyi on 1/13/19.
6 | //
7 |
8 | import UIKit
9 |
10 | class ViewControllerFactory {
11 |
12 | static func createLayoutInspectorContainerViewController() -> LayoutInspectorContainerViewController {
13 | let storyboard = UIStoryboard(name: "LayoutInspector", bundle: Bundle.layoutInspectorBundle)
14 | let viewController: LayoutInspectorContainerViewController = storyboard.instantiateViewController(withIdentifier: "LayoutInspectorContainerViewController") as! LayoutInspectorContainerViewController
15 | return viewController
16 | }
17 | }
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/Domain Layer/ViewDescription.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewDescription.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 12/25/18.
6 | // Copyright © 2018 Igor Savynskyi. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | protocol ViewDescriptionProtocol {
13 | var frame: CGRect { get }
14 | var snapshotImage: UIImage? { get }
15 | var subviews: [ViewDescriptionProtocol]? { get }
16 | var parentSize: CGSize? { get }
17 | var center: CGPoint { get }
18 | var isHidden: Bool { get }
19 | var isTransparent: Bool { get }
20 | var className: String { get }
21 | var isUserInteractionEnabled: Bool { get }
22 | var alpha: Float { get }
23 | var backgroundColor: UIColor? { get }
24 | var tint: UIColor? { get }
25 | var clipToBounds: Bool { get }
26 | var font: UIFont? { get }
27 | }
28 |
29 | class ViewDescription: ViewDescriptionProtocol {
30 | let frame: CGRect
31 | let snapshotImage: UIImage?
32 | let subviews: [ViewDescriptionProtocol]?
33 | let parentSize: CGSize?
34 | let center: CGPoint
35 | let isHidden: Bool
36 | let isTransparent: Bool
37 | let className: String
38 | let isUserInteractionEnabled: Bool
39 | let alpha: Float
40 | let backgroundColor: UIColor?
41 | let tint: UIColor?
42 | let clipToBounds: Bool
43 | let font: UIFont?
44 |
45 | // MARK: - Init
46 | init(frame: CGRect,
47 | snapshot: UIImage?,
48 | subviews: [ViewDescriptionProtocol]?,
49 | parentSize: CGSize?,
50 | center: CGPoint,
51 | isHidden: Bool,
52 | isTransparent: Bool,
53 | className: String,
54 | isUserInteractionEnabled: Bool,
55 | alpha: Float,
56 | backgroundColor: UIColor?,
57 | tint: UIColor?,
58 | clipToBounds: Bool,
59 | font: UIFont?)
60 | {
61 | self.frame = frame
62 | self.snapshotImage = snapshot
63 | self.subviews = subviews
64 | self.parentSize = parentSize
65 | self.center = center
66 | self.isHidden = isHidden
67 | self.isTransparent = isTransparent
68 | self.className = className
69 | self.isUserInteractionEnabled = isUserInteractionEnabled
70 | self.alpha = alpha
71 | self.backgroundColor = backgroundColor
72 | self.tint = tint
73 | self.clipToBounds = clipToBounds
74 | self.font = font
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/LayoutInspector.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LayoutInspector.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 12/25/18.
6 | // Copyright © 2018 Igor Savynskyi. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import CoreMotion
11 |
12 | /// Type of trigger used to fire layout inspection automatically
13 | @objc public enum AutoTrigger: Int {
14 | case none
15 |
16 | /// Fire automatically when device screenshot is taken
17 | case screenshot
18 |
19 | /// Fire automatically when shaking device
20 | case shake
21 | }
22 |
23 | /**
24 | The `LayoutInspector` is designed to be single communication point for the entire LayoutInspector module
25 |
26 | - Important:
27 | Layout inspection shown only in DEBUG build configuration
28 | */
29 | @objc public final class LayoutInspector: NSObject {
30 | /// Returns the singleton instance of an `LayoutInspector`.
31 | @objc public static let shared = LayoutInspector()
32 | private override init() {}
33 |
34 | private var currentAutoTrigger: AutoTrigger = .none
35 | private var viewController: LayoutInspectorContainerViewController?
36 | private var hierarchyBuilder: HierarchyBuilderProtocol = HierarchyBuilder()
37 | private var presenter: LayoutInspectorPresenter?
38 | private var isInspecting = false
39 | private lazy var motionManager = CMMotionManager()
40 | }
41 |
42 | //MARK: - Public API
43 | public extension LayoutInspector {
44 | /**
45 | Call this to fire layout inspection manually
46 | - Important:
47 | Layout inspection shown only in DEBUG build configuration
48 | */
49 | @objc func showLayout() {
50 | startLayoutInspection()
51 | }
52 |
53 | /**
54 | Specifies automatical trigger to start layout inspection
55 | - parameter trigger: The desired trigger type
56 | */
57 | @objc func setAutoTrigger(_ trigger: AutoTrigger) {
58 | guard currentAutoTrigger != trigger else { return }
59 |
60 | unsubscribeFromTrigger(currentAutoTrigger)
61 | currentAutoTrigger = trigger
62 | subscribeForTrigger(currentAutoTrigger)
63 | }
64 | }
65 |
66 | //MARK: - Private API
67 | private extension LayoutInspector {
68 | /**
69 | Starts layout inspection
70 | - Important:
71 | Layout inspection shown only in DEBUG build configuration
72 | */
73 | @objc func startLayoutInspection() {
74 | #if DEBUG
75 | guard let viewDescriptionTree = hierarchyBuilder.captureHierarchy(), isInspecting == false else { return }
76 | presenter = makeLayoutInspectorPresenter()
77 | presenter?.showInspectorView(for: viewDescriptionTree)
78 | isInspecting = true
79 | #endif
80 | }
81 |
82 | func subscribeForTrigger(_ trigger: AutoTrigger) {
83 | switch trigger {
84 | case .screenshot:
85 | NotificationCenter.default.addObserver(self,
86 | selector: #selector(startLayoutInspection),
87 | name: UIApplication.userDidTakeScreenshotNotification,
88 | object: nil)
89 | case .shake:
90 | addShake()
91 | case .none: return
92 | }
93 | }
94 |
95 | func unsubscribeFromTrigger(_ trigger: AutoTrigger) {
96 | switch trigger {
97 | case .screenshot:
98 | NotificationCenter.default.removeObserver(self,
99 | name: UIApplication.userDidTakeScreenshotNotification,
100 | object: nil)
101 | case .shake:
102 | removeShake()
103 | case .none: return
104 | }
105 | }
106 | }
107 |
108 |
109 | // MARK: - Container Module Configuration
110 | private extension LayoutInspector {
111 | func makeLayoutInspectorPresenter() -> LayoutInspectorPresenter {
112 | viewController = ViewControllerFactory.createLayoutInspectorContainerViewController()
113 | viewController?.loadView()
114 | viewController?.viewDidLoad()
115 |
116 | let presenter = LayoutInspectorPresenter()
117 | viewController?.output = presenter
118 | presenter.view = viewController
119 | presenter.delegate = self
120 | return presenter
121 | }
122 |
123 | }
124 |
125 | extension LayoutInspector: LayoutInspectorPresenterDelegate {
126 | func didFinishLayoutInspection() {
127 | viewController = nil
128 | presenter = nil
129 | isInspecting = false
130 | }
131 | }
132 |
133 | // MARK: - Shake detection support
134 | private extension LayoutInspector {
135 | func addShake() {
136 | guard motionManager.isAccelerometerAvailable else { return }
137 | motionManager.accelerometerUpdateInterval = 0.1
138 | motionManager.startAccelerometerUpdates(to: OperationQueue()) { [weak self] (data, error) in
139 | guard let self = self else { return }
140 | guard error == nil, let acceleration = data?.acceleration, self.currentAutoTrigger == .shake else {
141 | self.motionManager.stopAccelerometerUpdates()
142 | return
143 | }
144 | let accelerationMagnitude = sqrt(pow(acceleration.x, 2) + pow(acceleration.y, 2) + pow(acceleration.z, 2))
145 | if accelerationMagnitude > LayoutInspector.Constants.shakeAccelerationThreshold {
146 | DispatchQueue.main.async {
147 | LayoutInspector.shared.showLayout()
148 | }
149 | }
150 | }
151 | }
152 | func removeShake() {
153 | self.motionManager.stopAccelerometerUpdates()
154 | }
155 | }
156 |
157 | // MARK: - Nested types
158 | private extension LayoutInspector {
159 | enum Constants {
160 | static let shakeAccelerationThreshold = 2.3
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/UI Layer/AttributesWidget/AttributeViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AttributeViewModel.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 1/3/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | enum AttributeValue {
12 | case text(String?)
13 | case color(UIColor?)
14 | }
15 |
16 | enum ColorRepresentationPattern {
17 | static let nilColor = ""
18 | static let clear = "White:0\nAlpha:0"
19 | static let regularColorFormat = "R:%.2f\nG:%.2f\nB:%.2f\nA:%.2f"
20 | static let unknown = "unknown color"
21 | }
22 |
23 | struct AttributeViewModel {
24 | let title: String
25 | let value: AttributeValue
26 |
27 | var valueStringRepresentation: String? {
28 | switch value {
29 | case .text(let text):
30 | return text
31 | case .color(let color):
32 | return valueStringFor(color)
33 | }
34 | }
35 |
36 | func valueStringFor(_ color: UIColor?) -> String {
37 | switch color {
38 | case nil:
39 | return ColorRepresentationPattern.nilColor
40 | case UIColor.clear:
41 | return ColorRepresentationPattern.clear
42 | case let customColor?:
43 | return String(format: ColorRepresentationPattern.regularColorFormat,
44 | customColor.redValue,
45 | customColor.greenValue,
46 | customColor.blueValue,
47 | customColor.alphaValue)
48 | default:
49 | return ColorRepresentationPattern.unknown
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/UI Layer/AttributesWidget/AttributesManagerInterfaces.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AttributesManagerProtocol.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 1/3/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | protocol AttributesManagerProtocol {
10 | func renderViewMetadata(_ metadata: ViewMetadataProtocol?)
11 | }
12 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/UI Layer/AttributesWidget/AttributesWidgetViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AttributesWidgetViewController.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 1/2/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class AttributesWidgetViewController: UIViewController {
12 | @IBOutlet private weak var collectionView: UICollectionView!
13 | private var attributesManager: AttributesManagerProtocol?
14 |
15 | override func viewDidLoad() {
16 | super.viewDidLoad()
17 | attributesManager = ObjectAttributesManager(collectionView: collectionView)
18 | }
19 | }
20 |
21 | extension AttributesWidgetViewController: AttributesManagerProtocol {
22 | func renderViewMetadata(_ metadata: ViewMetadataProtocol?) {
23 | attributesManager?.renderViewMetadata(metadata)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/UI Layer/AttributesWidget/ColorAttributeCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ColorAttributeCell.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 1/2/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ColorAttributeCell: TextAttributeCell {
12 | @IBOutlet private weak var colorView: UIView!
13 | @IBOutlet private weak var checkeredPatternImageView: UIImageView!
14 |
15 | static func calculateEstimatedHeight(title: String, value: String?, cellWidth: CGFloat) -> CGFloat {
16 | let contentWidthConstraint = cellWidth - Layout.contentInsets.left - Layout.contentInsets.right
17 | let titleHeight = title.height(withConstrainedWidth: contentWidthConstraint, font: Styleguide.font)
18 | let valueHeight = value?.height(withConstrainedWidth: contentWidthConstraint - Layout.valueLabelXPos, font: Styleguide.font) ?? 0
19 | return titleHeight + valueHeight + Layout.contentInsets.top + Layout.contentInsets.bottom
20 | }
21 |
22 | func renderColorAttribute(_ color: UIColor?) {
23 | colorView.backgroundColor = color
24 |
25 | let colorAlpha = Float(color?.alphaValue ?? 0)
26 | checkeredPatternImageView.isHidden = (colorAlpha == 0.0 || colorAlpha == 1.0)
27 | }
28 |
29 | // MARK: - Private API
30 | private func configureColorView() {
31 | colorView.layer.cornerRadius = colorView.bounds.height/2.0
32 | colorView.layer.borderWidth = 1.0
33 | colorView.layer.borderColor = UIColor.appLight.cgColor
34 |
35 | checkeredPatternImageView.layer.cornerRadius = checkeredPatternImageView.bounds.height/2.0
36 | checkeredPatternImageView.layer.masksToBounds = true
37 | }
38 | }
39 |
40 | // MARK: - Nested types
41 | private extension ColorAttributeCell {
42 | struct Layout {
43 | static let contentInsets = UIEdgeInsets(top: 4, left: 6, bottom: 4, right: 4)
44 | static let valueLabelXPos: CGFloat = 24.0
45 | }
46 |
47 | struct Styleguide {
48 | static let font: UIFont = .appH3
49 | }
50 | }
51 |
52 | // MARK: - Themeable
53 | extension ColorAttributeCell {
54 | override func configureStyles() {
55 | super.configureStyles()
56 | configureColorView()
57 | }
58 | }
59 |
60 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/UI Layer/AttributesWidget/ObjectAttributesManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObjectAttributesManager.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 1/2/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ObjectAttributesManager: NSObject, AttributesManagerProtocol {
12 | private var collectionView: UICollectionView
13 | private var currentItem: ViewMetadataProtocol? {
14 | didSet { dataSource = generateDataSource(from: currentItem) }
15 | }
16 | private var dataSource = [AttributeViewModel]() {
17 | didSet { collectionView.reloadData() }
18 | }
19 |
20 | init(collectionView: UICollectionView) {
21 | self.collectionView = collectionView
22 | super.init()
23 | collectionView.dataSource = self
24 | collectionView.delegate = self
25 | }
26 |
27 | func renderViewMetadata(_ metadata: ViewMetadataProtocol?) {
28 | currentItem = metadata
29 | }
30 | }
31 |
32 | // MARK: - Nested types
33 | private extension ObjectAttributesManager {
34 | enum LayoutConstants {
35 | static let collectionItemWidth: CGFloat = 82.0
36 | }
37 | }
38 |
39 | extension ObjectAttributesManager: UICollectionViewDataSource {
40 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
41 | return dataSource.count
42 | }
43 |
44 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
45 | let sourceItem = dataSource[indexPath.row]
46 |
47 | switch sourceItem.value {
48 | case .text(_):
49 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TextAttributeCell.reuseIdentifier, for: indexPath) as! TextAttributeCell
50 | cell.titleLabel.text = sourceItem.title
51 | cell.valueLabel.text = sourceItem.valueStringRepresentation
52 | return cell
53 | case .color(let colorValue):
54 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ColorAttributeCell.reuseIdentifier, for: indexPath) as! ColorAttributeCell
55 | cell.titleLabel.text = sourceItem.title
56 | cell.renderColorAttribute(colorValue)
57 | cell.valueLabel.text = sourceItem.valueStringRepresentation
58 | return cell
59 | }
60 | }
61 |
62 | }
63 |
64 | extension ObjectAttributesManager: UICollectionViewDelegateFlowLayout {
65 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
66 | let sourceItem = dataSource[indexPath.row]
67 | let height: CGFloat
68 | switch sourceItem.value {
69 | case .text(_):
70 | height = TextAttributeCell.estimatedHeight(title: sourceItem.title,
71 | value: sourceItem.valueStringRepresentation,
72 | cellWidth: LayoutConstants.collectionItemWidth)
73 | case .color(_):
74 | height = ColorAttributeCell.calculateEstimatedHeight(title: sourceItem.title,
75 | value: sourceItem.valueStringRepresentation,
76 | cellWidth: LayoutConstants.collectionItemWidth)
77 | }
78 | return CGSize(width: LayoutConstants.collectionItemWidth, height: height)
79 | }
80 | }
81 |
82 | // MARK: - Private API
83 | private extension ObjectAttributesManager {
84 | func generateDataSource(from metadata: ViewMetadataProtocol?) -> [AttributeViewModel] {
85 | guard let metadata = metadata else { return [] }
86 |
87 | let userInteractionValue = metadata.isUserInteractionEnabled ? "On" : "Off"
88 | let clipToBoundsValue = metadata.clipToBounds ? "On" : "Off"
89 |
90 | var attributes = [AttributeViewModel(title: "Class Name", value: .text(metadata.className)),
91 | AttributeViewModel(title: "User Interaction Enabled", value: .text(userInteractionValue)),
92 | AttributeViewModel(title: "Alpha", value: .text(String(describing: metadata.alpha))),
93 | AttributeViewModel(title: "Background Color", value: .color(metadata.backgroundColor)),
94 | AttributeViewModel(title: "Tint", value: .color(metadata.tint)),
95 | AttributeViewModel(title: "Clip To Bounds", value: .text(clipToBoundsValue)),
96 | AttributeViewModel(title: "Frame", value: .text(String(describing: metadata.frame)))]
97 | if let font = metadata.font {
98 | attributes.append(AttributeViewModel(title: "Font", value: .text(String(describing: font.attributeDescription))))
99 | }
100 |
101 | return attributes
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/UI Layer/AttributesWidget/TextAttributeCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObjectInspectionTextDataCell.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 1/2/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class TextAttributeCell: UICollectionViewCell {
12 | @IBOutlet weak var titleLabel: UILabel!
13 | @IBOutlet weak var valueLabel: UILabel!
14 | @IBOutlet private weak var trailingSeparator: UIView!
15 |
16 | static func estimatedHeight(title: String, value: String?, cellWidth: CGFloat) -> CGFloat {
17 | let contentWidthConstraint = cellWidth - Layout.contentInsets.left - Layout.contentInsets.right
18 | let titleHeight = title.height(withConstrainedWidth: contentWidthConstraint, font: Styleguide.font)
19 | let valueHeight = value?.height(withConstrainedWidth: contentWidthConstraint, font: Styleguide.font) ?? 0
20 | return titleHeight + valueHeight + Layout.contentInsets.top + Layout.contentInsets.bottom
21 | }
22 |
23 | // MARK: - Lifecycle
24 | override func awakeFromNib() {
25 | super.awakeFromNib()
26 | configureStyles()
27 | }
28 | }
29 |
30 | // MARK: - Nested types
31 | private extension TextAttributeCell {
32 | enum Layout {
33 | static let contentInsets = UIEdgeInsets(top: 4, left: 6, bottom: 4, right: 4)
34 | }
35 |
36 | enum Styleguide {
37 | static let font: UIFont = .appH3
38 | }
39 | }
40 |
41 | extension TextAttributeCell: Themeable {
42 | func configureStyles() {
43 | titleLabel.font = Styleguide.font
44 | titleLabel.textColor = .appLight
45 | valueLabel.font = Styleguide.font
46 | valueLabel.textColor = .appLight
47 | trailingSeparator.backgroundColor = .secondaryLightColor
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/UI Layer/LayoutInspectorContainer/DebugNode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DebugNode.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 1/1/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import SceneKit
10 |
11 | class DebugNode: SCNNode {
12 | var metadata: ViewMetadataProtocol?
13 | private var selectionBorderNodeName = "DebugNode.selectionBorderNodeName"
14 |
15 | var isSelected = false {
16 | didSet { updateSelectionState() }
17 | }
18 | }
19 |
20 | // MARK: - Private API
21 | private extension DebugNode {
22 | func updateSelectionState() {
23 | isSelected ? highlight() : unhighlight()
24 | }
25 |
26 | func highlight() {
27 | setFirstMaterialTransparency(Constants.selectedStateTransparency)
28 |
29 | let (min, max) = boundingBox
30 | let topLeft = SCNVector3Make(min.x, max.y, 0)
31 | let bottomLeft = SCNVector3Make(min.x, min.y, 0)
32 | let topRight = SCNVector3Make(max.x, max.y, 0)
33 | let bottomRight = SCNVector3Make(max.x, min.y, 0)
34 |
35 | let bottomSide = createLineNode(fromPos: bottomLeft, toPos: bottomRight, color: .selectionBorders)
36 | let leftSide = createLineNode(fromPos: bottomLeft, toPos: topLeft, color: .selectionBorders)
37 | let rightSide = createLineNode(fromPos: bottomRight, toPos: topRight, color: .selectionBorders)
38 | let topSide = createLineNode(fromPos: topLeft, toPos: topRight, color: .selectionBorders)
39 |
40 | [bottomSide, leftSide, rightSide, topSide].forEach {
41 | $0.name = selectionBorderNodeName
42 | addChildNode($0)
43 | }
44 | }
45 |
46 | func unhighlight() {
47 | setFirstMaterialTransparency(Constants.deselectedStateTransparency)
48 |
49 | let highlightningNodes = childNodes.filter { $0.name == selectionBorderNodeName}
50 | highlightningNodes.forEach { $0.removeFromParentNode() }
51 | }
52 |
53 | func createLineNode(fromPos origin: SCNVector3, toPos destination: SCNVector3, color: UIColor) -> SCNNode {
54 | let line = lineFrom(vector: origin, toVector: destination)
55 | let lineNode = SCNNode(geometry: line)
56 | let planeMaterial = SCNMaterial()
57 | planeMaterial.diffuse.contents = color
58 | line.materials = [planeMaterial]
59 |
60 | return lineNode
61 | }
62 |
63 | func lineFrom(vector vector1: SCNVector3, toVector vector2: SCNVector3) -> SCNGeometry {
64 | let indices: [Int32] = [0, 1]
65 |
66 | let source = SCNGeometrySource(vertices: [vector1, vector2])
67 | let element = SCNGeometryElement(indices: indices, primitiveType: .line)
68 |
69 | return SCNGeometry(sources: [source], elements: [element])
70 | }
71 |
72 | func setFirstMaterialTransparency(_ value: CGFloat) {
73 | geometry?.firstMaterial?.transparency = CGFloat(value)
74 | }
75 |
76 | }
77 |
78 | // MARK: - Nested types
79 | extension DebugNode {
80 | enum Constants {
81 | static let selectedStateTransparency: CGFloat = 0.5
82 | static let deselectedStateTransparency: CGFloat = 1.0
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/UI Layer/LayoutInspectorContainer/LayoutInspectorContainerInterfaces.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LayoutInspectorContainerInterfaces.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 1/3/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import SceneKit
10 |
11 | protocol LayoutInspectorPresenterDelegate: NSObjectProtocol {
12 | func didFinishLayoutInspection()
13 | }
14 |
15 | protocol LayoutInspectorViewOutput {
16 | func didCloseAction()
17 | }
18 |
19 | protocol LayoutInspectorViewInput: NSObjectProtocol, NodesManagementProtocol {
20 | func rootView() -> UIView
21 | }
22 |
23 | protocol NodesManagementProtocol {
24 | func addNode(_ node: SCNNode)
25 | func removeNode(_ node: SCNNode)
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/UI Layer/LayoutInspectorContainer/LayoutInspectorContainerViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LayoutInspectorContainerViewController.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 12/25/18.
6 | // Copyright © 2018 Igor Savynskyi. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import SceneKit
11 |
12 | class LayoutInspectorContainerViewController: UIViewController {
13 | // Props
14 | var output: LayoutInspectorViewOutput?
15 | private var menuWidget: MenuWidgetProtocol?
16 | private var sceneWidget: SceneWidgetProtocol?
17 | private var objectInspectionWidget: AttributesManagerProtocol?
18 |
19 | // MARK: - Lifecycle
20 | override func viewDidLoad() {
21 | super.viewDidLoad()
22 | loadWidgets()
23 | configureStyles()
24 | }
25 |
26 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
27 | guard let identifier = segue.identifier, let segueCase = Segue(rawValue: identifier) else { return }
28 |
29 | switch segueCase {
30 | case .toMenuWidgetViewControler:
31 | menuWidget = segue.destination as? MenuWidgetProtocol
32 | menuWidget?.delegate = self
33 | case .toAttributesWidgetViewController:
34 | objectInspectionWidget = segue.destination as? AttributesManagerProtocol
35 | case .toSceneWidgetViewController:
36 | sceneWidget = segue.destination as? SceneWidgetProtocol
37 | sceneWidget?.delegate = self
38 | }
39 | }
40 |
41 | // MARK: - Private API
42 | private func loadWidgets() {
43 | performSegue(withIdentifier: Segue.toMenuWidgetViewControler.rawValue, sender: self)
44 | performSegue(withIdentifier: Segue.toAttributesWidgetViewController.rawValue, sender: self)
45 | performSegue(withIdentifier: Segue.toSceneWidgetViewController.rawValue, sender: self)
46 | }
47 | }
48 |
49 | extension LayoutInspectorContainerViewController: LayoutInspectorViewInput {
50 | func rootView() -> UIView {
51 | return view
52 | }
53 |
54 | func addNode(_ node: SCNNode) {
55 | sceneWidget?.addNode(node)
56 | }
57 |
58 | func removeNode(_ node: SCNNode) {
59 | sceneWidget?.removeNode(node)
60 | }
61 | }
62 |
63 | extension LayoutInspectorContainerViewController: SceneViewManagerDelegate {
64 | func selectedViewMetadataDidUpdate(_ metadata: ViewMetadataProtocol?) {
65 | objectInspectionWidget?.renderViewMetadata(metadata)
66 | }
67 | }
68 |
69 | extension LayoutInspectorContainerViewController: MenuWidgetDelegate {
70 | func didCloseAction() {
71 | output?.didCloseAction()
72 | }
73 |
74 | func didResetCameraPositionAction() {
75 | sceneWidget?.resetPointOfViewToDefaults()
76 | }
77 | }
78 |
79 | extension LayoutInspectorContainerViewController: Themeable {
80 | func configureStyles() {
81 | view.backgroundColor = .sceneBackground
82 | }
83 | }
84 |
85 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/UI Layer/LayoutInspectorContainer/LayoutInspectorPresenter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LayoutInspectorContainerViewController.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 12/28/18.
6 | // Copyright © 2018 Igor Savynskyi. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import SceneKit
11 |
12 | class LayoutInspectorPresenter {
13 | weak var view: LayoutInspectorViewInput?
14 | weak var delegate: LayoutInspectorPresenterDelegate?
15 |
16 | private var wrapperNode = SCNNode()
17 | private lazy var renderingTreeBuilder: RenderingTreeBuilderProtocol = {
18 | return RenderingTreeBuilder()
19 | }()
20 |
21 | func showInspectorView(for viewDescription: ViewDescriptionProtocol) {
22 | renderLayoutNodes(for: viewDescription)
23 | showView()
24 | }
25 | }
26 |
27 | private extension LayoutInspectorPresenter {
28 | func showView() {
29 | guard let appWindow = UIApplication.shared.keyWindow,
30 | let rootView = view?.rootView() else { return }
31 | appWindow.addSubview(rootView)
32 | }
33 |
34 | func renderLayoutNodes(for viewDescription: ViewDescriptionProtocol) {
35 | let renderingTree = renderingTreeBuilder.build(from: viewDescription)
36 | if let rootNode = renderingTree.viewNode {
37 | wrapperNode.addChildNode(rootNode)
38 | view?.addNode(wrapperNode)
39 | }
40 | }
41 | }
42 |
43 | extension LayoutInspectorPresenter: LayoutInspectorViewOutput {
44 | func didCloseAction() {
45 | view?.removeNode(wrapperNode)
46 | wrapperNode.childNodes.forEach { $0.removeFromParentNode() }
47 | let rootView = view?.rootView()
48 | rootView?.removeFromSuperview()
49 | delegate?.didFinishLayoutInspection()
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/UI Layer/LayoutInspectorContainer/RenderingTreeBuilder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RenderingTreeBuilder.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 12/26/18.
6 | // Copyright © 2018 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import SceneKit
10 |
11 | protocol RenderingTreeBuilderProtocol {
12 | func build(from: ViewDescriptionProtocol) -> RenderingViewProtocol
13 | }
14 |
15 | fileprivate enum SceneConstants {
16 | static let pointsInSceneKitMeter: Float = 100.0
17 | static let layerStep: Float = 0.3
18 | }
19 |
20 | class RenderingTreeBuilder: RenderingTreeBuilderProtocol {
21 | func build(from viewDescription: ViewDescriptionProtocol) -> RenderingViewProtocol {
22 | let renderingSubviews = viewDescription.subviews?.compactMap { build(from: $0) }
23 | var viewNode: SCNNode?
24 |
25 | // skip nodes for hidden views
26 | if viewDescription.isHidden == false {
27 | viewNode = node(for: viewDescription)
28 | adjustNodePositionToSceneKitCoordinateSystem(viewNode, with: viewDescription)
29 | renderingSubviews?.forEach({
30 | guard let node = $0.viewNode else { return }
31 | viewNode?.addChildNode(node)
32 | })
33 | }
34 | return RenderingView(viewNode: viewNode,
35 | viewDescription: viewDescription,
36 | subviews: renderingSubviews)
37 | }
38 | }
39 |
40 | private extension RenderingTreeBuilder {
41 | func node (for viewDescription: ViewDescriptionProtocol) -> SCNNode {
42 | let viewPlane = plane(for: viewDescription)
43 | let node = DebugNode()
44 | node.geometry = viewPlane
45 | node.metadata = ViewMetadata(with: viewDescription)
46 | return node
47 | }
48 |
49 | func plane(for viewDescription: ViewDescriptionProtocol) -> SCNPlane {
50 | let plane = SCNPlane(width: viewDescription.frame.size.width/CGFloat(SceneConstants.pointsInSceneKitMeter),
51 | height: viewDescription.frame.size.height/CGFloat(SceneConstants.pointsInSceneKitMeter))
52 | if viewDescription.isTransparent {
53 | plane.firstMaterial?.diffuse.contents = UIImage(named: "transparent_view_image",
54 | in: Bundle.layoutInspectorBundle,
55 | compatibleWith: nil)
56 | } else if viewDescription.snapshotImage?.size != CGSize.zero {
57 | plane.firstMaterial?.diffuse.contents = viewDescription.snapshotImage
58 | }
59 | plane.firstMaterial?.isDoubleSided = true
60 | return plane
61 | }
62 |
63 |
64 | /**
65 | SceneKit axes SceneKit node center
66 | |y ___________
67 | | | |
68 | |____x | *(0,0)|
69 | z/ |___________|
70 |
71 | Point (0,0) is top left in UIKit whereas in SceneKit it is center
72 | */
73 | func adjustNodePositionToSceneKitCoordinateSystem(_ node: SCNNode?, with viewDescription: ViewDescriptionProtocol) {
74 | guard let parentSize = viewDescription.parentSize, let node = node else { return }
75 | let viewCenter = viewDescription.center
76 | let translatedX = -parentSize.width/2.0 + viewCenter.x
77 | let translatedY = parentSize.height/2.0 - viewCenter.y
78 | node.position = SCNVector3Make(Float(translatedX)/SceneConstants.pointsInSceneKitMeter,
79 | Float(translatedY)/SceneConstants.pointsInSceneKitMeter,
80 | SceneConstants.layerStep)
81 | }
82 | }
83 |
84 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/UI Layer/LayoutInspectorContainer/RenderingView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RenderingView.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 12/26/18.
6 | // Copyright © 2018 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import SceneKit
10 |
11 | protocol RenderingViewProtocol {
12 | var viewNode: SCNNode? { get }
13 | var viewDescription: ViewDescriptionProtocol { get }
14 | var subviews: [RenderingViewProtocol]? { get }
15 | }
16 |
17 | struct RenderingView: RenderingViewProtocol {
18 | let viewNode: SCNNode?
19 | let viewDescription: ViewDescriptionProtocol
20 | let subviews: [RenderingViewProtocol]?
21 | }
22 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/UI Layer/LayoutInspectorContainer/ViewMetadata.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewMetadata.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 1/4/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | protocol ViewMetadataProtocol {
12 | var className: String { get }
13 | var isUserInteractionEnabled: Bool { get }
14 | var alpha: Float { get }
15 | var backgroundColor: UIColor? { get }
16 | var tint: UIColor? { get }
17 | var clipToBounds: Bool { get }
18 | var frame: CGRect { get }
19 | var font: UIFont? { get }
20 | }
21 |
22 | struct ViewMetadata: ViewMetadataProtocol {
23 | var className: String
24 | var isUserInteractionEnabled: Bool
25 | var alpha: Float
26 | var backgroundColor: UIColor?
27 | var tint: UIColor?
28 | var clipToBounds: Bool
29 | var frame: CGRect
30 | var font: UIFont?
31 |
32 | init(with viewDescriptor: ViewDescriptionProtocol) {
33 | self.className = viewDescriptor.className
34 | self.isUserInteractionEnabled = viewDescriptor.isUserInteractionEnabled
35 | self.alpha = viewDescriptor.alpha
36 | self.backgroundColor = viewDescriptor.backgroundColor
37 | self.tint = viewDescriptor.tint
38 | self.clipToBounds = viewDescriptor.clipToBounds
39 | self.frame = viewDescriptor.frame
40 | self.font = viewDescriptor.font
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/UI Layer/MenuWidget/MenuWidgetInterfaces.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MenuWidgetInterfaces.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 1/3/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol MenuWidgetProtocol {
12 | var delegate: MenuWidgetDelegate? { get set }
13 | }
14 |
15 | protocol MenuWidgetDelegate: NSObjectProtocol {
16 | func didCloseAction()
17 | func didResetCameraPositionAction()
18 | }
19 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/UI Layer/MenuWidget/MenuWidgetViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MenuWidgetViewController.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 1/3/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class MenuWidgetViewController: UIViewController, MenuWidgetProtocol {
12 | weak var delegate: MenuWidgetDelegate?
13 | @IBOutlet private weak var closeButton: UIButton!
14 | @IBOutlet private weak var resetButton: UIButton!
15 |
16 | override func viewDidLoad() {
17 | super.viewDidLoad()
18 | configureStyles()
19 | }
20 |
21 | // MARK: - Actions
22 | @IBAction private func closeAction(_ sender: Any) {
23 | delegate?.didCloseAction()
24 | }
25 |
26 | @IBAction private func resetCameraPositionAction(_ sender: Any) {
27 | delegate?.didResetCameraPositionAction()
28 | }
29 | }
30 |
31 | extension MenuWidgetViewController: Themeable {
32 | func configureStyles() {
33 | closeButton.tintColor = .appLight
34 | resetButton.tintColor = .appLight
35 | }
36 | }
37 |
38 |
39 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/UI Layer/SceneWidget/SceneViewManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneViewManager.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 1/2/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import SceneKit
10 |
11 | fileprivate enum CameraParameters {
12 | static let orthographicScale = 6.0
13 | static let zNear = 0.0
14 | static let zFar = 100.0
15 | }
16 |
17 | class SceneViewManager: SceneViewManagerProtocol {
18 | weak var delegate: SceneViewManagerDelegate?
19 |
20 | private let sceneView: SCNView
21 | private var selectedNode: DebugNode?
22 |
23 | init(sceneView: SCNView) {
24 | self.sceneView = sceneView
25 | configureSceneView()
26 | }
27 |
28 | func resetPointOfViewToDefaults() {
29 | sceneView.pointOfView?.position = SCNVector3(0, 0, 10)
30 | sceneView.pointOfView?.eulerAngles = SCNVector3()
31 | sceneView.pointOfView?.camera?.orthographicScale = CameraParameters.orthographicScale
32 | }
33 |
34 | func handleTap(_ sender: UITapGestureRecognizer) {
35 | let location: CGPoint = sender.location(in: sceneView)
36 | let hits = sceneView.hitTest(location, options: nil)
37 | if let tappedNode = hits.first?.node as? DebugNode {
38 | setSelectedNode(tappedNode)
39 | }
40 | }
41 |
42 | func addNode(_ node: SCNNode) {
43 | sceneView.scene?.rootNode.addChildNode(node)
44 | }
45 |
46 | func removeNode(_ node: SCNNode) {
47 | node.removeFromParentNode()
48 | }
49 | }
50 |
51 | // MARK: - Private API
52 | private extension SceneViewManager {
53 | func configureSceneView() {
54 | sceneView.allowsCameraControl = true
55 | sceneView.scene = SCNScene()
56 | sceneView.scene?.background.contents = UIColor.sceneBackground
57 | sceneView.pointOfView = createCameraNode()
58 | resetPointOfViewToDefaults()
59 | }
60 |
61 | func createCameraNode() -> SCNNode {
62 | let camera = SCNCamera()
63 | camera.usesOrthographicProjection = true
64 | camera.zNear = CameraParameters.zNear
65 | camera.zFar = CameraParameters.zFar
66 | let cameraNode = SCNNode()
67 | cameraNode.camera = camera
68 | return cameraNode
69 | }
70 |
71 | func setSelectedNode(_ newSelectedNode: DebugNode) {
72 | newSelectedNode.isSelected = !newSelectedNode.isSelected
73 | if selectedNode == newSelectedNode {
74 | selectedNode = nil
75 | } else {
76 | selectedNode?.isSelected = false
77 | selectedNode = newSelectedNode
78 | }
79 | delegate?.selectedViewMetadataDidUpdate(selectedNode?.metadata)
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/UI Layer/SceneWidget/SceneViewManagerInterfaces.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneViewManagerInterfaces.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 1/2/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import SceneKit
10 |
11 | protocol SceneViewManagerProtocol: NodesManagementProtocol {
12 | var delegate: SceneViewManagerDelegate? { get set}
13 | func resetPointOfViewToDefaults()
14 | func handleTap(_ sender: UITapGestureRecognizer)
15 | }
16 |
17 | protocol SceneViewManagerDelegate: NSObjectProtocol {
18 | func selectedViewMetadataDidUpdate(_ metadata: ViewMetadataProtocol?)
19 | }
20 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/UI Layer/SceneWidget/SceneWidgetInterfaces.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneWidgetInterfaces.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 1/3/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import SceneKit
10 |
11 | protocol SceneWidgetProtocol: NodesManagementProtocol {
12 | var delegate: SceneViewManagerDelegate? { get set }
13 | func resetPointOfViewToDefaults()
14 | }
15 |
--------------------------------------------------------------------------------
/LayoutInspector/Classes/UI Layer/SceneWidget/SceneWidgetViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneWidgetViewController.swift
3 | // LayoutInspectorExample
4 | //
5 | // Created by Igor Savynskyi on 1/3/19.
6 | // Copyright © 2019 Ihor Savynskyi. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import SceneKit
11 |
12 | class SceneWidgetViewController: UIViewController {
13 | weak var delegate: SceneViewManagerDelegate?
14 | private var sceneViewManager: SceneViewManagerProtocol?
15 | private var tapGestureRecognizer: UITapGestureRecognizer {
16 | return UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
17 | }
18 |
19 | @IBOutlet private weak var sceneView: SCNView!
20 |
21 | override func viewDidLoad() {
22 | super.viewDidLoad()
23 | sceneViewManager = SceneViewManager(sceneView: sceneView)
24 | sceneViewManager?.delegate = self
25 | configure()
26 | }
27 | }
28 |
29 | extension SceneWidgetViewController: SceneWidgetProtocol {
30 | func resetPointOfViewToDefaults() {
31 | sceneViewManager?.resetPointOfViewToDefaults()
32 | }
33 |
34 | func addNode(_ node: SCNNode) {
35 | sceneViewManager?.addNode(node)
36 | }
37 |
38 | func removeNode(_ node: SCNNode) {
39 | sceneViewManager?.removeNode(node)
40 | }
41 | }
42 |
43 | // MARK: - Private API
44 | private extension SceneWidgetViewController {
45 | func configure() {
46 | configureGestures()
47 | }
48 |
49 | func configureGestures() {
50 | sceneView.addGestureRecognizer(tapGestureRecognizer)
51 | }
52 |
53 | @objc func handleTap(_ sender: UITapGestureRecognizer) {
54 | sceneViewManager?.handleTap(sender)
55 | }
56 | }
57 |
58 | extension SceneWidgetViewController: SceneViewManagerDelegate {
59 | func selectedViewMetadataDidUpdate(_ metadata: ViewMetadataProtocol?) {
60 | delegate?.selectedViewMetadataDidUpdate(metadata)
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/LayoutInspector/LayoutInspector.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 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
128 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
307 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
--------------------------------------------------------------------------------
/LayoutInspector_demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/isavynskyi/LayoutInspector/72583864c147301c702c59ed4bbb5356114b9539/LayoutInspector_demo.gif
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | [](https://travis-ci.org/isavynskyi/LayoutInspector)
4 | [](https://cocoapods.org/pods/LayoutInspector)
5 | [](https://cocoapods.org/pods/LayoutInspector)
6 | [](https://cocoapods.org/pods/LayoutInspector)
7 |
8 |
9 |
10 | ## Features
11 |
12 | - [x] Inspect layouts directly on iOS devices
13 | - [x] Inspection could be triggered only if app is running under `DEBUG` build configuration, so it won't affect any other kind of the app builds (i.e. `RELEASE`)
14 | - [x] Objective-C compatible
15 | - [x] Works on all devices with iOS 11.0+
16 |
17 | ## Example
18 |
19 | To run the example project, clone the repo, and run `pod install` from the Example directory first.
20 |
21 | ## Requirements
22 |
23 | - iOS 11.0 and higher
24 | - Xcode 10.3 and higher
25 | - Swift 5.0 and higher
26 |
27 | ## Installation
28 |
29 | `LayoutInspector` is available through [CocoaPods](https://cocoapods.org). To install
30 | it, simply add the following line to your Podfile:
31 |
32 | ```ruby
33 | pod 'LayoutInspector'
34 | ```
35 |
36 | ## Usage
37 |
38 | **Layout inspection could be trigger:**
39 | - manually whenever you want (on some actions, events, notifications etc)
40 | ```swift
41 | LayoutInspector.shared.showLayout()
42 | ```
43 |
44 | - automatically when taking app screenshot
45 | ```swift
46 | LayoutInspector.shared.setAutoTrigger(.screenshot)
47 | ```
48 |
49 | - automatically on device shaking
50 | ```swift
51 | LayoutInspector.shared.setAutoTrigger(.shake)
52 | ```
53 |
54 |
55 | **Swift**
56 | ```swift
57 | import LayoutInspector
58 |
59 | @UIApplicationMain
60 | class AppDelegate: UIResponder, UIApplicationDelegate {
61 |
62 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
63 | LayoutInspector.shared.setAutoTrigger(.screenshot)
64 | return true
65 | }
66 | }
67 |
68 | ```
69 |
70 | **Objective-C**
71 | ```obj-c
72 | @import LayoutInspector;
73 |
74 | @implementation AppDelegate
75 |
76 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
77 | [LayoutInspector.shared setAutoTrigger:AutoTriggerScreenshot];
78 | return YES;
79 | }
80 |
81 | @end
82 | ```
83 |
84 | ## Changelog
85 |
86 | | Version | Description |
87 | | ------------- | ------------- |
88 | | `1.2.1` | 📝 Font attribute added to inspection pane|
89 | | `1.2.0` | 👋 Shake gesture added to trigger layout inspection
🧰 CI configuration upgrade|
90 | | `1.1.0` | 🏎️ Swift 5.0 and ABI stability in da house
✈️ New build system activated
🔌 Dropped iOS 10 support|
91 | | `1.0.0` | 🎉 Release 1.0
👮 Test coverage added|
92 | | `0.2.0` | Pre-release: Objective-C compatibility 👦 |
93 | | `0.1.1` | Pre-release: fixed Lint warnings 👶 |
94 | | `0.1.0` | Pre-release: initial version 🎬 |
95 |
96 | ## Author
97 |
98 | Ihor Savynskyi\
99 | ✉️ wadedunk08@gmail.com\
100 | 🌎 [Twitter](https://twitter.com/iWadedunk), [LinkedIn](https://www.linkedin.com/in/isavynskyi/)
101 |
102 |
103 | ## License
104 |
105 | LayoutInspector is available under the MIT license. See the LICENSE file for more info.
106 |
--------------------------------------------------------------------------------
/_Pods.xcodeproj:
--------------------------------------------------------------------------------
1 | Example/Pods/Pods.xcodeproj
--------------------------------------------------------------------------------