├── .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 | 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 | [![CI Status](https://img.shields.io/travis/isavynskyi/LayoutInspector.svg?style=flat)](https://travis-ci.org/isavynskyi/LayoutInspector) 4 | [![Version](https://img.shields.io/cocoapods/v/LayoutInspector.svg?style=flat)](https://cocoapods.org/pods/LayoutInspector) 5 | [![License](https://img.shields.io/cocoapods/l/LayoutInspector.svg?style=flat)](https://cocoapods.org/pods/LayoutInspector) 6 | [![Platform](https://img.shields.io/cocoapods/p/LayoutInspector.svg?style=flat)](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 --------------------------------------------------------------------------------