├── Drawer ├── Sources │ └── Drawer │ │ ├── DrawerModels.swift │ │ ├── DrawerCoordinator.swift │ │ └── DrawerviewController.swift ├── Example │ ├── Assets.xcassets │ │ ├── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Scenes │ │ ├── Background View Controller │ │ │ ├── ViewController.swift │ │ │ └── Base.lproj │ │ │ │ └── Main.storyboard │ │ └── Content View Controller │ │ │ ├── ContentViewController.swift │ │ │ └── ContentViewController.storyboard │ ├── Info.plist │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ └── AppDelegate.swift ├── Drawer.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── Drawer.xcscheme │ └── project.pbxproj ├── Drawer │ ├── Drawer │ │ ├── Protocols │ │ │ ├── EmbeddableContentDelegate.swift │ │ │ ├── EmbeddedScrollable.swift │ │ │ └── Embeddable.swift │ │ ├── DrawerCoordinator.swift │ │ ├── DrawerModels.swift │ │ └── DrawerViewController.swift │ ├── Drawer.h │ ├── Extensions │ │ └── UIView+Superview.swift │ ├── Info.plist │ └── Touch Intercepting View │ │ └── TouchInterceptingView.swift ├── DrawerTests │ ├── Info.plist │ └── DrawerTests.swift └── ExampleTests │ ├── Info.plist │ └── ExampleTests.swift ├── Sources └── Drawer │ ├── DrawerModels.swift │ ├── DrawerCoordinator.swift │ ├── Embeddable.swift │ ├── UIView+Superview.swift │ ├── DrawerViewController.swift │ ├── EmbeddedScrollable.swift │ ├── TouchInterceptingView.swift │ └── EmbeddableContentDelegate.swift ├── drawer_close.gif ├── drawer_open.gif ├── Drawer.framework.zip ├── NDrawer.podspec ├── .github └── ISSUE_TEMPLATE │ └── bug_report.md ├── LICENSE ├── .circleci └── config.yml ├── Package.swift ├── .gitignore └── README.md /Drawer/Sources/Drawer/DrawerModels.swift: -------------------------------------------------------------------------------- 1 | ../../Drawer/Drawer/DrawerModels.swift -------------------------------------------------------------------------------- /Sources/Drawer/DrawerModels.swift: -------------------------------------------------------------------------------- 1 | ../../Drawer/Drawer/Drawer/DrawerModels.swift -------------------------------------------------------------------------------- /Drawer/Sources/Drawer/DrawerCoordinator.swift: -------------------------------------------------------------------------------- 1 | ../../Drawer/Drawer/DrawerCoordinator.swift -------------------------------------------------------------------------------- /Sources/Drawer/DrawerCoordinator.swift: -------------------------------------------------------------------------------- 1 | ../../Drawer/Drawer/Drawer/DrawerCoordinator.swift -------------------------------------------------------------------------------- /Sources/Drawer/Embeddable.swift: -------------------------------------------------------------------------------- 1 | ../../Drawer/Drawer/Drawer/Protocols/Embeddable.swift -------------------------------------------------------------------------------- /drawer_close.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/Drawer/master/drawer_close.gif -------------------------------------------------------------------------------- /drawer_open.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/Drawer/master/drawer_open.gif -------------------------------------------------------------------------------- /Sources/Drawer/UIView+Superview.swift: -------------------------------------------------------------------------------- 1 | ../../Drawer/Drawer/Extensions/UIView+Superview.swift -------------------------------------------------------------------------------- /Drawer.framework.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/Drawer/master/Drawer.framework.zip -------------------------------------------------------------------------------- /Drawer/Sources/Drawer/DrawerviewController.swift: -------------------------------------------------------------------------------- 1 | ../../Drawer/Drawer/DrawerviewController.swift -------------------------------------------------------------------------------- /Sources/Drawer/DrawerViewController.swift: -------------------------------------------------------------------------------- 1 | ../../Drawer/Drawer/Drawer/DrawerViewController.swift -------------------------------------------------------------------------------- /Sources/Drawer/EmbeddedScrollable.swift: -------------------------------------------------------------------------------- 1 | ../../Drawer/Drawer/Drawer/Protocols/EmbeddedScrollable.swift -------------------------------------------------------------------------------- /Sources/Drawer/TouchInterceptingView.swift: -------------------------------------------------------------------------------- 1 | ../../Drawer/Drawer/Touch Intercepting View/TouchInterceptingView.swift -------------------------------------------------------------------------------- /Sources/Drawer/EmbeddableContentDelegate.swift: -------------------------------------------------------------------------------- 1 | ../../Drawer/Drawer/Drawer/Protocols/EmbeddableContentDelegate.swift -------------------------------------------------------------------------------- /Drawer/Example/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Drawer/Drawer.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Drawer/Drawer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Drawer/Drawer/Drawer/Protocols/EmbeddableContentDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmbeddableContentDelegate.swift 3 | // Drawer 4 | // 5 | // Created by Andrei Hogea on 27/02/2019. 6 | // Copyright © 2019 Nodes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | public protocol EmbeddableContentDelegate: class { 13 | func handle(action: Drawer.Action) 14 | var maxAllowedHeight: CGFloat { get } 15 | } 16 | 17 | 18 | -------------------------------------------------------------------------------- /Drawer/Drawer/Drawer.h: -------------------------------------------------------------------------------- 1 | // 2 | // Drawer.h 3 | // Drawer 4 | // 5 | // Created by Andrei Hogea on 27/02/2019. 6 | // Copyright © 2019 Andrei Hogea. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Drawer. 12 | FOUNDATION_EXPORT double DrawerVersionNumber; 13 | 14 | //! Project version string for Drawer. 15 | FOUNDATION_EXPORT const unsigned char DrawerVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Drawer/Drawer/Extensions/UIView+Superview.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+Superview.swift 3 | // Drawer 4 | // 5 | // Created by Andrei Hogea on 27/02/2019. 6 | // Copyright © 2019 Nodes. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIView { 12 | 13 | func superview(of type: T.Type) -> T? { 14 | return superview as? T ?? superview.flatMap { $0.superview(of: type) } 15 | } 16 | 17 | func subview(of type: T.Type) -> T? { 18 | return subviews.compactMap { $0 as? T ?? $0.subview(of: type) }.first 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /NDrawer.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'NDrawer' 3 | s.version = '1.0.4' 4 | s.summary = 'Drawer is a framework that enables you to easily embed a UIViewController in a drawer and display it on top of another UIViewController.' 5 | s.homepage = 'https://github.com/nodes-ios/Drawer' 6 | s.author = { "Nodes Agency - iOS" => "ios@nodes.dk" } 7 | s.license = { :type => 'MIT', :file => './LICENSE' } 8 | s.source = { :git => 'https://github.com/nodes-ios/Drawer.git', :tag => s.version.to_s } 9 | 10 | s.ios.deployment_target = '11.0' 11 | 12 | s.source_files = 'Drawer/Drawer/**/*.swift' 13 | 14 | s.swift_version = '5' 15 | s.pod_target_xcconfig = { 'SWIFT_VERSION' => '5' } 16 | end 17 | -------------------------------------------------------------------------------- /Drawer/DrawerTests/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 | -------------------------------------------------------------------------------- /Drawer/ExampleTests/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 | -------------------------------------------------------------------------------- /Drawer/Example/Scenes/Background View Controller/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Example 4 | // 5 | // Created by Andrei Hogea on 27/02/2019. 6 | // Copyright © 2019 Andrei Hogea. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Drawer 11 | 12 | class ViewController: UIViewController { 13 | 14 | private let contentVC = ContentViewController.instantiate() 15 | var drawer: DrawerCoordinator? 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | drawer = DrawerCoordinator(contentViewController: contentVC, 20 | backgroundViewController: self.tabBarController!, 21 | drawerBackgroundType: .withColor(UIColor.black.withAlphaComponent(0.5))) 22 | } 23 | 24 | } 25 | 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **iPhone/iPad (please complete the following information):** 27 | - Device: [e.g. iPhoneX] 28 | - OS: [e.g. iOS11] 29 | - Version [e.g. 1.0] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /Drawer/Drawer/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Drawer/Drawer/Drawer/Protocols/EmbeddedScrollable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmbeddedScrollable.swift 3 | // Drawer 4 | // 5 | // Created by Andrei Hogea on 27/02/2019. 6 | // Copyright © 2019 Andrei Hogea. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // A Protocol exposed to the Application 12 | public protocol EmbeddedScrollable: Embeddable { 13 | /// Use this parameter to tell the drawer if the content UIViewContoller's embeded UIScrollView is scrolled to top. 14 | /// 15 | /// - parameters: 16 | /// - enabled: Bool 17 | /// 18 | var isScrolledToTop: Bool { get } 19 | /// Use this method to determine if the content UIViewContoller's embeded UIScrollView should allow scrolling. 20 | /// 21 | /// - parameters: 22 | /// - enabled: Bool 23 | /// 24 | func setScrollEnabled(_ enabled: Bool) 25 | } 26 | -------------------------------------------------------------------------------- /Drawer/DrawerTests/DrawerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DrawerTests.swift 3 | // DrawerTests 4 | // 5 | // Created by Andrei Hogea on 27/02/2019. 6 | // Copyright © 2019 Andrei Hogea. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Drawer 11 | 12 | class DrawerTests: XCTestCase { 13 | 14 | override func setUp() { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testExample() { 23 | // This is an example of a functional test case. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | } 26 | 27 | func testPerformanceExample() { 28 | // This is an example of a performance test case. 29 | self.measure { 30 | // Put the code you want to measure the time of here. 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Drawer/ExampleTests/ExampleTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleTests.swift 3 | // ExampleTests 4 | // 5 | // Created by Andrei Hogea on 27/02/2019. 6 | // Copyright © 2019 Andrei Hogea. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Example 11 | 12 | class ExampleTests: XCTestCase { 13 | 14 | override func setUp() { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testExample() { 23 | // This is an example of a functional test case. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | } 26 | 27 | func testPerformanceExample() { 28 | // This is an example of a performance test case. 29 | self.measure { 30 | // Put the code you want to measure the time of here. 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Nodes Agency - iOS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # iOS CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/ios-migrating-from-1-2/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | 9 | # Specify the Xcode version to use 10 | macos: 11 | xcode: "10.0" 12 | 13 | steps: 14 | - checkout 15 | 16 | # Install Carthage 17 | - run: 18 | name: Install Carthage 19 | command: carthage update --platform ios 20 | 21 | # Build the app and run tests 22 | - run: 23 | name: Build and run tests 24 | command: fastlane scan 25 | environment: 26 | SCAN_DEVICE: iPhone 6 27 | SCAN_SCHEME: WebTests 28 | 29 | # Collect XML test results data to show in the UI, 30 | # and save the same XML files under test-results folder 31 | # in the Artifacts tab 32 | - store_test_results: 33 | path: test_output/report.xml 34 | - store_artifacts: 35 | path: /tmp/test-results 36 | destination: scan-test-results 37 | - store_artifacts: 38 | path: ~/Library/Logs/scan 39 | destination: scan-logs 40 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Drawer", 8 | platforms: [ 9 | .iOS(.v11), 10 | ], 11 | products: [ 12 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 13 | .library( 14 | name: "Drawer", 15 | targets: ["Drawer"]), 16 | ], 17 | dependencies: [ 18 | // Dependencies declare other packages that this package depends on. 19 | // .package(url: /* package url */, from: "1.0.0"), 20 | ], 21 | targets: [ 22 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 23 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 24 | .target( 25 | name: "Drawer", 26 | dependencies: []), 27 | .testTarget( 28 | name: "DrawerTests", 29 | dependencies: ["Drawer"], 30 | path: "Drawer/DrawerTests"), 31 | ] 32 | ) 33 | -------------------------------------------------------------------------------- /Drawer/Drawer/Touch Intercepting View/TouchInterceptingView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TouchInterceptingView.swift 3 | // Drawer 4 | // 5 | // Created by Jakob Mygind Jensen on 07/02/2018. 6 | // Copyright © 2019 Nodes. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol TouchPassingWindowDelegate: class { 12 | func windowShouldBlockAll() -> Bool 13 | var viewsForInterceptingTouches: [UIView] { get } 14 | } 15 | 16 | class TouchInterceptingView: UIView { 17 | 18 | weak var delegate: TouchPassingWindowDelegate? 19 | 20 | override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { 21 | let hitView = super.hitTest(point, with: event) 22 | if delegate?.windowShouldBlockAll() == true { 23 | return self 24 | } 25 | 26 | var isDescendant = false 27 | if let hitView = hitView, let interceptList = delegate?.viewsForInterceptingTouches { 28 | for v in interceptList { 29 | if hitView.isDescendant(of: v) { 30 | isDescendant = true 31 | } 32 | } 33 | 34 | if delegate?.viewsForInterceptingTouches.contains(hitView) == true || isDescendant { 35 | return hitView 36 | } 37 | } 38 | 39 | return nil 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /Drawer/Drawer/Drawer/DrawerCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DrawerCoordinator.swift 3 | // Drawer 4 | // 5 | // Created by Andrei Hogea on 27/02/2019. 6 | // Copyright © 2019 Nodes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | public class DrawerCoordinator { 13 | 14 | private let backgroundViewController: UIViewController 15 | private let contentViewController: (UIViewController & Embeddable) 16 | private let drawerBackgroundType: DrawerViewController.DrawerBackgroundType 17 | 18 | public init(contentViewController: UIViewController & Embeddable, 19 | backgroundViewController: UIViewController, 20 | drawerBackgroundType: DrawerViewController.DrawerBackgroundType) { 21 | self.contentViewController = contentViewController 22 | self.backgroundViewController = backgroundViewController 23 | self.drawerBackgroundType = drawerBackgroundType 24 | 25 | start() 26 | } 27 | 28 | private func start() { 29 | if backgroundViewController.children.contains(where: { $0 is DrawerViewController }) { 30 | assertionFailure("\(backgroundViewController) already contains a Drawer. Multiple drawers not supported.") 31 | return 32 | } 33 | 34 | let drawerVC = DrawerViewController() 35 | drawerVC.contentViewController = contentViewController 36 | drawerVC.backgroundViewController = backgroundViewController 37 | 38 | drawerVC.makeViews(with: drawerBackgroundType) 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Drawer/Example/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.1 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 | -------------------------------------------------------------------------------- /.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 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | 55 | Carthage/ 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 63 | 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots/**/*.png 67 | fastlane/test_output 68 | -------------------------------------------------------------------------------- /Drawer/Drawer/Drawer/Protocols/Embeddable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Embeddable.swift 3 | // Drawer 4 | // 5 | // Created by Andrei Hogea on 27/02/2019. 6 | // Copyright © 2019 Andrei Hogea. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | // A Protocol exposed to the Application 13 | public protocol Embeddable where Self: UIViewController { 14 | /// Do not set this property directly. The drawer will assign it when it has finished creating. 15 | var embedDelegate: EmbeddableContentDelegate? { get set } 16 | /// Use this method to track upcoming changes in state. Triggered when Drawer state starts changing. This can be triggered by user Touch/Pan events or by calling the `embedDelegate` functions 17 | /// 18 | /// - parameters: 19 | /// - state: EmbeddableState 20 | /// 21 | func willChangeState(to state: EmbeddableState) 22 | /// Use this method to track changes in state. 23 | /// 24 | /// - parameters: 25 | /// - state: EmbeddableState 26 | /// 27 | func didChangeState(to state: EmbeddableState) 28 | /// Use this method to track changes of the content position. 29 | /// 30 | /// - parameters: 31 | /// - progress: CGFloat (values between 0 and 1). Example: A value of 0.1 means that the user just started the transition from state, while a value of 0.9 means that the user is getting close to finishing the transition to the oposite state of the `from state` 32 | /// - state: Drawer.State 33 | /// - direction: DrawerViewController.Direction direction of scroll 34 | /// 35 | func didScroll(with progress: CGFloat, from state: Drawer.State) 36 | } 37 | 38 | public enum EmbeddableState { 39 | case fullSize 40 | case minimized 41 | case closed 42 | } 43 | -------------------------------------------------------------------------------- /Drawer/Example/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 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Drawer/Example/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 | } -------------------------------------------------------------------------------- /Drawer/Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Example 4 | // 5 | // Created by Andrei Hogea on 27/02/2019. 6 | // Copyright © 2019 Andrei Hogea. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Drawer/Drawer/Drawer/DrawerModels.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BottomDrawerModels.swift 3 | // Drawer 4 | // 5 | // Created by Andrei Hogea on 27/02/2019. 6 | // Copyright © 2019 Nodes. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public enum Drawer { 12 | 13 | public enum State: Int { 14 | case fullSize 15 | case minimized 16 | } 17 | 18 | internal enum ContainerInteraction: Int { 19 | case whenMinimised 20 | } 21 | 22 | public enum Action { 23 | case layoutUpdated(config: Drawer.Configuration) 24 | case changeState(to: MovementState) 25 | 26 | public enum MovementState: Int { 27 | case minimize 28 | case fullScreen 29 | case dismiss 30 | } 31 | } 32 | 33 | public struct Configuration { 34 | 35 | let duration: TimeInterval 36 | let embeddedFullHeight: CGFloat 37 | let embeddedMinimumHeight: CGFloat 38 | let state: Drawer.State 39 | let cornerRadius: Configuration.CornerRadius 40 | let dismissCompleteCallback: (() -> Void)? 41 | 42 | /** 43 | Creates a Configuration for the Drawer 44 | 45 | - Parameter options: Use options to configure the Drawer. 46 | - Parameter dismissCompleteCallback: Called when the Drawer has finished dismissing 47 | 48 | Available configurations: 49 | - .animationDuration: TimeInterval 50 | -- default value is 0.3 51 | 52 | - .fullHeight: CGFloat 53 | -- default value is 300 54 | 55 | - .minimumHeight: CGFloat 56 | -- default value is 100 57 | 58 | - .initialState: Drawer.State 59 | -- default value is .minimized 60 | 61 | - .cornerRadius: ContentConfiguration.CornerRadius 62 | -- default value is CornerRadius(fullSize: 0, minimized: 0) 63 | 64 | */ 65 | public init(options: [Drawer.Configuration.Key: Any], 66 | dismissCompleteCallback: (() -> Void)? = nil) { 67 | 68 | duration = options[.animationDuration] as? TimeInterval ?? 0.3 69 | embeddedFullHeight = options[.fullHeight] as? CGFloat ?? 300 70 | embeddedMinimumHeight = options[.minimumHeight] as? CGFloat ?? 100 71 | state = options[.initialState] as? Drawer.State ?? .minimized 72 | cornerRadius = options[.cornerRadius] as? Configuration.CornerRadius ?? Configuration.CornerRadius(fullSize: 0, minimized: 0) 73 | 74 | 75 | self.dismissCompleteCallback = dismissCompleteCallback 76 | } 77 | 78 | public enum Key: String, CaseIterable { 79 | case animationDuration 80 | case fullHeight 81 | case minimumHeight 82 | case initialState 83 | case cornerRadius 84 | } 85 | 86 | public struct CornerRadius { 87 | let fullSize: CGFloat 88 | let minimized: CGFloat 89 | 90 | public init(fullSize: CGFloat, minimized: CGFloat) { 91 | self.fullSize = fullSize 92 | self.minimized = minimized 93 | } 94 | } 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /Drawer/Drawer.xcodeproj/xcshareddata/xcschemes/Drawer.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### This library has been deprecated and the repo has been archived. 2 | ### The code is still here and you can still clone it, however the library will not receive any more updates or support. 3 | 4 | [![Carthage Compatible](https://img.shields.io/badge/carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 5 | ![Plaforms](https://img.shields.io/badge/platforms-iOS%20-lightgrey.svg) 6 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/nodes-ios/Drawer/blob/master/LICENSE) 7 | [![CircleCI](https://circleci.com/gh/nodes-ios/Drawer.svg?style=shield)](https://circleci.com/gh/nodes-ios/Drawer) 8 | 9 | ## Intro 10 | 11 | `Drawer` is a framework that enables you to easily embed a `UIViewController` in a drawer and display it on top of another `UIViewController`. 12 | 13 | ![](drawer_open.gif) 14 | ![](drawer_close.gif) 15 | 16 | ## 📝 Requirements 17 | 18 | - iOS 11 19 | - Swift 4.0+ 20 | 21 | ## 📦 Installation 22 | 23 | ### Carthage 24 | ~~~bash 25 | github "nodes-ios/Drawer" 26 | ~~~ 27 | 28 | ### Cocoapods 29 | ~~~bash 30 | pod 'NDrawer' 31 | ~~~ 32 | 33 | ## 💻 Usage 34 | 35 | ### Requirements 36 | 37 | * A `UIViewController` for the drawer to be displayed over. This `ViewController` is referred to as the `backgroundViewController` in the following steps 38 | * A `UIViewController` to act as the content of the drawer. This `ViewController` is referred to as the `contentViewController` in the following steps. 39 | 40 | ### Steps 41 | 42 | #### Creating a Drawer 43 | Start by conforming your `contentViewController` to the `Embeddable` protocol. This exposes several delegate functions to the `contentViewController`. 44 | 45 | ```swift 46 | extension ContentViewController: Embeddable {} 47 | ``` 48 | 49 | Furthermore an instance of `EmbeddableContentDelegate` is exposed. This `delegate` can be used to instruct the drawer to perform various tasks by calling the `handle` function on it. 50 | 51 | The `handle` function takes an `enum` of type `Drawer.Action` which allows these actions: 52 | 53 | - `layoutUpdated(config: Drawer.Configuration)` to update the layout of your drawer 54 | - `changeState(to: MovementState)` to show/hide your drawer. 55 | 56 | After creating the `contentViewController`, initialize an instance of `DrawerCoordinator` in your `backgroundViewController` to initialize the drawer. 57 | 58 | ```swift 59 | let drawer = DrawerCoordinator(contentViewController: contentVC, 60 | backgroundViewController: self, 61 | drawerBackgroundType: .withColor(UIColor.black.withAlphaComponent(0.5))) 62 | ``` 63 | 64 | #### Displaying a Drawer 65 | After your content's views have finished creating and you are ready to display the drawer, create an instance of `Drawer.Configuration` to set the drawer state and properties. 66 | 67 | ```swift 68 | 69 | let options: [Drawer.Configuration.Key : Any] = [ 70 | .animationDuration: 0.5, 71 | .fullHeight: maxHeight, 72 | .minimumHeight: minHeight, 73 | .initialState: Drawer.State.minimized, 74 | .cornerRadius: Drawer.Configuration.CornerRadius(fullSize: 20, 75 | minimized: 0) 76 | ] 77 | 78 | let contentConfiguration = Drawer.Configuration(options: options, 79 | dismissCompleteCallback: nil) 80 | 81 | ``` 82 | 83 | Communication with the `EmbeddableContentDelegate` is managed by calling the `handle` function, which takes an `enum` of type `Drawer.Action` as a parameter. 84 | Finally call the `EmbeddableContentDelegate` `handle` function to update the drawer's layout to the new `Configuration` 85 | 86 | ```swift 87 | embedDelegate?.handle(action: .layoutUpdated(config: contentConfiguration)) 88 | ``` 89 | 90 | #### Expanding and Collapsing a Drawer 91 | To expand and collapse the drawer programatically, call the `EmbeddableContentDelegate` `handle` function with a `changeState` action containing the state which the drawer should transition to. 92 | 93 | ```swift 94 | embedDelegate?.handle(action: .changeState(to: .fullScreen)) 95 | ``` 96 | 97 | ## Example Project 98 | To learn more, please refer to the example project contained in this repository. 99 | 100 | ## 👥 Credits 101 | Made with ❤️ at [Nodes](http://nodesagency.com). 102 | 103 | ## 📄 License 104 | **Drawer** is available under the MIT license. See the [LICENSE](https://github.com/nodes-ios/DrawerI/blob/master/LICENSE) file for more info. 105 | -------------------------------------------------------------------------------- /Drawer/Example/Scenes/Content View Controller/ContentViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentViewController.swift 3 | // DrawerExample 4 | // 5 | // Created by Andrei Hogea on 26/02/2019. 6 | // Copyright (c) 2019 Andrei Hogea. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Drawer 11 | 12 | class ContentViewController: UIViewController { 13 | 14 | // MARK: - Outlets 15 | 16 | @IBOutlet weak var expandButton: UIButton! 17 | @IBOutlet weak var collapseButton: UIButton! 18 | @IBOutlet weak var titleLabel: UILabel! 19 | 20 | // MARK: - Properties 21 | 22 | var embedDelegate: EmbeddableContentDelegate? 23 | private var animationDuration: TimeInterval = 1 24 | 25 | // title animations 26 | private var runningAnimators: [UIViewPropertyAnimator] = [] 27 | private var titleAnimator: UIViewPropertyAnimator! 28 | private let titleScaleMax: CGFloat = 1.6 29 | 30 | // MARK: - Init 31 | 32 | class func instantiate() -> ContentViewController { 33 | let name = "\(ContentViewController.self)" 34 | let storyboard = UIStoryboard(name: name, bundle: nil) 35 | return storyboard.instantiateViewController(withIdentifier: name) as! ContentViewController 36 | } 37 | 38 | // MARK: - View Lifecycle - 39 | 40 | override func viewDidLoad() { 41 | super.viewDidLoad() 42 | 43 | titleAnimator = UIViewPropertyAnimator(duration: animationDuration, dampingRatio: 1) 44 | titleAnimator.addAnimations { 45 | 46 | } 47 | adjustDrawer(with: 400, with: 100) 48 | } 49 | 50 | override func viewDidLayoutSubviews() { 51 | super.viewDidLayoutSubviews() 52 | 53 | view.layer.shadowColor = UIColor.black.cgColor 54 | view.layer.shadowRadius = 2 55 | view.layer.shadowOffset = CGSize(width: 0, height: -1) 56 | view.layer.shadowOpacity = 0.3 57 | view.clipsToBounds = false 58 | view.layer.masksToBounds = false 59 | } 60 | 61 | private func adjustDrawer(with maxHeight: CGFloat, with minHeight: CGFloat) { 62 | 63 | let options: [Drawer.Configuration.Key : Any] = [ 64 | .animationDuration: 0.5, 65 | .fullHeight: maxHeight, 66 | .minimumHeight: minHeight, 67 | .initialState: Drawer.State.minimized, 68 | .cornerRadius: Drawer.Configuration.CornerRadius(fullSize: 20, 69 | minimized: 0) 70 | ] 71 | 72 | let contentConfiguration = Drawer.Configuration(options: options, 73 | dismissCompleteCallback: nil) 74 | 75 | embedDelegate?.handle(action: .layoutUpdated(config: contentConfiguration)) 76 | } 77 | 78 | // MARK: - Callbacks - 79 | 80 | @IBAction func expandTapped(_ sender: Any) { 81 | embedDelegate?.handle(action: .changeState(to: .fullScreen)) 82 | } 83 | 84 | @IBAction func collapseTapped(_ sender: Any) { 85 | embedDelegate?.handle(action: .changeState(to: .minimize)) 86 | } 87 | 88 | } 89 | 90 | extension ContentViewController: Embeddable { 91 | func willChangeState(to state: EmbeddableState) { 92 | runningAnimators.forEach({ 93 | $0.stopAnimation(false) 94 | $0.finishAnimation(at: .current) 95 | }) 96 | let transform: CGAffineTransform 97 | let collapseAlpha: CGFloat 98 | let expandAlpha: CGFloat 99 | 100 | guard runningAnimators.isEmpty else { return } 101 | 102 | switch state { 103 | case .minimized: 104 | transform = .identity 105 | collapseAlpha = 0 106 | expandAlpha = 1 107 | case .fullSize: 108 | transform = CGAffineTransform(scaleX: titleScaleMax, y: titleScaleMax).concatenating(CGAffineTransform(translationX: 25, y: 0)) 109 | collapseAlpha = 1 110 | expandAlpha = 0 111 | default: 112 | transform = .identity 113 | collapseAlpha = 0 114 | expandAlpha = 1 115 | } 116 | 117 | titleAnimator.addAnimations { 118 | self.titleLabel.transform = transform 119 | self.collapseButton.alpha = collapseAlpha 120 | self.expandButton.alpha = expandAlpha 121 | } 122 | titleAnimator.addCompletion {_ in 123 | self.titleLabel.transform = transform 124 | self.collapseButton.alpha = collapseAlpha 125 | self.expandButton.alpha = expandAlpha 126 | self.runningAnimators.removeAll() 127 | } 128 | 129 | titleAnimator.startAnimation() 130 | runningAnimators.append(titleAnimator) 131 | } 132 | 133 | func didChangeState(to state: EmbeddableState) { 134 | switch state { 135 | case .minimized: 136 | collapseButton.alpha = 0 137 | expandButton.alpha = 1 138 | case .fullSize: 139 | collapseButton.alpha = 1 140 | expandButton.alpha = 0 141 | case .closed: 142 | break 143 | } 144 | 145 | runningAnimators.forEach({ 146 | $0.stopAnimation(false) 147 | $0.finishAnimation(at: .end) 148 | }) 149 | } 150 | 151 | func didScroll(with progress: CGFloat, from state: Drawer.State) { 152 | runningAnimators.forEach({$0.pauseAnimation()}) 153 | switch state { 154 | case .fullSize: 155 | collapseButton.alpha = 1 - progress 156 | expandButton.alpha = progress 157 | titleAnimator.fractionComplete = progress 158 | case .minimized: 159 | collapseButton.alpha = progress 160 | expandButton.alpha = 1 - progress 161 | titleAnimator.fractionComplete = progress 162 | } 163 | 164 | runningAnimators.forEach({$0.continueAnimation(withTimingParameters: nil, durationFactor: 0)}) 165 | } 166 | 167 | } 168 | -------------------------------------------------------------------------------- /Drawer/Example/Scenes/Background View Controller/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda.Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda.Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. 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 | -------------------------------------------------------------------------------- /Drawer/Example/Scenes/Content View Controller/ContentViewController.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 32 | 43 | 44 | 45 | 46 | Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. 47 | 48 | 49 | 50 | 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 | -------------------------------------------------------------------------------- /Drawer/Drawer/Drawer/DrawerViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DrawerViewController.swift 3 | // Nodes 4 | // 5 | // Created by Andrei Hogea on 06/08/2018. 6 | // Copyright © 2019 Nodes. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class DrawerViewController: UIViewController { //swiftlint:disable:this type_body_length 12 | 13 | weak var contentViewController: (UIViewController & Embeddable)? 14 | weak var backgroundViewController: UIViewController? 15 | 16 | // MARK: Configuration 17 | 18 | private var embedConfig: Drawer.Configuration! { 19 | didSet { 20 | animationDuration = embedConfig.duration 21 | 22 | //drawer heights 23 | ownMaxHeight = embedConfig.embeddedFullHeight 24 | ownMinHeight = embedConfig.embeddedMinimumHeight 25 | heightAnchorContent.constant = ownMaxHeight 26 | cornerRadius = embedConfig.cornerRadius 27 | 28 | //drawer state 29 | state = embedConfig.state 30 | showAnimation() 31 | 32 | } 33 | } 34 | 35 | // MARK: Sizes and Constrains 36 | 37 | private var heightAnchorContent: NSLayoutConstraint! 38 | private var bottomAnchorContent: NSLayoutConstraint! 39 | private var ownMaxHeight: CGFloat = 0 40 | private var ownMinHeight: CGFloat = 0 41 | private var cornerRadius: Drawer.Configuration.CornerRadius! 42 | 43 | // MARK: States 44 | 45 | private var isInitiated: Bool = false 46 | private var state: Drawer.State = .minimized 47 | private var backgroundInteraction: Drawer.ContainerInteraction = .whenMinimised 48 | 49 | // MARK: Drawer possible backgrounds 50 | 51 | private var backgroundType: DrawerBackgroundType! 52 | 53 | private var backgroundColorView: UIView? 54 | private var backgroundBlurEffectView: UIVisualEffectView? 55 | 56 | // MARK: Slide Animation Properties 57 | 58 | private var animationDuration: TimeInterval! 59 | private let damping: CGFloat = 0.85 60 | // direction represents scrolling direction of the view 61 | private var direction: Direction! 62 | private var runningAnimators: [UIViewPropertyAnimator] = [] 63 | private var animationProgress: [CGFloat] = [] 64 | private var lastFractionComplete: CGFloat = 0 65 | 66 | // MARK: - Init 67 | 68 | public override func loadView() { 69 | super.loadView() 70 | self.view = TouchInterceptingView() 71 | } 72 | 73 | override public func viewDidLayoutSubviews() { 74 | super.viewDidLayoutSubviews() 75 | 76 | if ownMaxHeight > 0 && isInitiated == false { 77 | DispatchQueue.main.async { 78 | self.showAnimation() 79 | } 80 | } 81 | 82 | } 83 | 84 | func makeViews(with backgroundType: DrawerBackgroundType) { 85 | self.backgroundType = backgroundType 86 | view.backgroundColor = .clear 87 | 88 | addDrawerToBackground() 89 | switch backgroundType { 90 | case .withBlur: 91 | addBlurEffectViewToDrawer() 92 | case .withColor: 93 | addColorViewToDrawer() 94 | default: break 95 | } 96 | addContentToDrawer() 97 | setupGestureRecognizers() 98 | 99 | view.setNeedsLayout() 100 | view.layoutIfNeeded() 101 | } 102 | 103 | private func roundCorners(with radius: CGFloat) { 104 | contentViewController?.view.layer.cornerRadius = radius 105 | let corners = UIRectCorner(arrayLiteral: .topLeft, .topRight) 106 | contentViewController?.view.layer.maskedCorners = CACornerMask(rawValue: corners.rawValue) 107 | } 108 | 109 | } 110 | 111 | // MARK: - Add Children - 112 | 113 | extension DrawerViewController { 114 | 115 | /// Adds the Drawer(self) to the backgroundViewController 116 | private func addDrawerToBackground() { 117 | guard let backgroundViewController = backgroundViewController else { return } 118 | 119 | //swiftlint:disable:next force_cast 120 | let touchInterceptingView = view as! TouchInterceptingView 121 | touchInterceptingView.delegate = self 122 | 123 | backgroundViewController.view.addSubview(view) 124 | willMove(toParent: backgroundViewController) 125 | backgroundViewController.addChild(self) 126 | didMove(toParent: backgroundViewController) 127 | 128 | view.translatesAutoresizingMaskIntoConstraints = false 129 | view.heightAnchor.constraint(equalToConstant: maxAllowedHeight).isActive = true 130 | view.bottomAnchor.constraint(equalTo: backgroundViewController.view.bottomAnchor).isActive = true 131 | view.leadingAnchor.constraint(equalTo: backgroundViewController.view.leadingAnchor).isActive = true 132 | view.trailingAnchor.constraint(equalTo: backgroundViewController.view.trailingAnchor).isActive = true 133 | } 134 | 135 | /// Adds a UIVisualEffectView to self to act as background 136 | private func addBlurEffectViewToDrawer() { 137 | backgroundBlurEffectView = UIVisualEffectView() 138 | view.addSubview(backgroundBlurEffectView!) 139 | backgroundBlurEffectView?.translatesAutoresizingMaskIntoConstraints = false 140 | backgroundBlurEffectView?.topAnchor.constraint(equalTo: view.topAnchor).isActive = true 141 | backgroundBlurEffectView?.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true 142 | backgroundBlurEffectView?.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true 143 | backgroundBlurEffectView?.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true 144 | addTapToMinimise(on: backgroundBlurEffectView!) 145 | } 146 | 147 | /// Adds a UIView to self to act as background 148 | private func addColorViewToDrawer() { 149 | backgroundColorView = UIView() 150 | view.addSubview(backgroundColorView!) 151 | backgroundColorView?.translatesAutoresizingMaskIntoConstraints = false 152 | backgroundColorView?.topAnchor.constraint(equalTo: view.topAnchor).isActive = true 153 | backgroundColorView?.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true 154 | backgroundColorView?.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true 155 | backgroundColorView?.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true 156 | addTapToMinimise(on: backgroundColorView!) 157 | } 158 | 159 | private func addTapToMinimise(on tapView: UIView) { 160 | let tap = UITapGestureRecognizer(target: self, action: #selector(tapMinimize)) 161 | tap.delegate = self 162 | tapView.isUserInteractionEnabled = true 163 | tapView.addGestureRecognizer(tap) 164 | } 165 | 166 | @objc private func tapMinimize() { 167 | contentViewController?.willChangeState(to: .minimized) 168 | closeDrawer() 169 | } 170 | 171 | /// Adds a contents UIViewController's view to self 172 | private func addContentToDrawer() { 173 | guard 174 | contentViewController != nil 175 | else { return } 176 | 177 | contentViewController?.embedDelegate = self 178 | 179 | view.addSubview(contentViewController!.view) 180 | contentViewController?.willMove(toParent: self) 181 | addChild(contentViewController!) 182 | contentViewController?.didMove(toParent: self) 183 | 184 | contentViewController?.view.translatesAutoresizingMaskIntoConstraints = false 185 | heightAnchorContent = contentViewController?.view.heightAnchor.constraint(equalToConstant: maxAllowedHeight) 186 | heightAnchorContent.identifier = "heightAnchorContent" 187 | heightAnchorContent.isActive = true 188 | bottomAnchorContent = contentViewController?.view.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: maxAllowedHeight) 189 | bottomAnchorContent.identifier = "bottomAnchorContent" 190 | bottomAnchorContent.isActive = true 191 | contentViewController?.view.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true 192 | contentViewController?.view.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true 193 | } 194 | 195 | } 196 | 197 | // MARK: - UIGestureRecognizer - 198 | 199 | extension DrawerViewController { 200 | 201 | private func setupGestureRecognizers() { 202 | 203 | func makePanGestureRecognizer(addToView view: UIView) { 204 | let contentPan = UIPanGestureRecognizer.init(target: self, action: #selector(handlePan)) 205 | contentPan.delegate = self 206 | view.addGestureRecognizer(contentPan) 207 | } 208 | 209 | func makeTapGestureRecognizer(addToView view: UIView) { 210 | let contentPan = UITapGestureRecognizer.init(target: self, action: #selector(tapMinimize)) 211 | contentPan.delegate = self 212 | view.addGestureRecognizer(contentPan) 213 | } 214 | 215 | do { 216 | if let contentViewController = contentViewController { 217 | makePanGestureRecognizer(addToView: contentViewController.view) 218 | } 219 | 220 | if let backgroundColorView = backgroundColorView { 221 | makePanGestureRecognizer(addToView: backgroundColorView) 222 | } 223 | 224 | if let backgroundBlurEffectView = backgroundBlurEffectView { 225 | makePanGestureRecognizer(addToView: backgroundBlurEffectView) 226 | } 227 | } 228 | } 229 | 230 | @objc private func handlePan(recognizer: UIPanGestureRecognizer) { 231 | switch recognizer.state { 232 | case .began: 233 | startInteractiveTransition(duration: animationDuration) 234 | case .changed: 235 | let translation = recognizer.translation(in: contentViewController?.view) 236 | var fractionComplete = translation.y / (ownMaxHeight - ownMinHeight) 237 | 238 | switch state { 239 | case .fullSize: 240 | direction = fourDecimal(fractionComplete) >= fourDecimal(lastFractionComplete) ? .down : .up 241 | case .minimized: 242 | fractionComplete *= -1 243 | direction = fourDecimal(fractionComplete) >= fourDecimal(lastFractionComplete) ? .up : .down 244 | } 245 | 246 | // if we have a progress, that means that the user started the swipe during the animation 247 | if animationProgress[0] > 0 { 248 | for (index, animator) in runningAnimators.enumerated() { 249 | if animator.isReversed { 250 | animator.fractionComplete = -fractionComplete + animationProgress[index] 251 | } else { 252 | animator.fractionComplete = fractionComplete + animationProgress[index] 253 | } 254 | } 255 | } else { // scroll started from initial state 256 | guard fractionComplete > 0 && fractionComplete < 1 else { 257 | return 258 | } 259 | for (index, animator) in runningAnimators.enumerated() { 260 | if animator.isReversed { 261 | animator.fractionComplete = fractionComplete - animationProgress[index] 262 | } else { 263 | animator.fractionComplete = fractionComplete + animationProgress[index] 264 | } 265 | } 266 | } 267 | 268 | // set lastFractionComplete -> used to determine pan direction 269 | lastFractionComplete = fractionComplete 270 | 271 | // normalise the progress and return it to the delegate 272 | var returnFractionComplete = runningAnimators[0].fractionComplete 273 | if returnFractionComplete >= 1 { 274 | returnFractionComplete = 1 275 | } else if returnFractionComplete <= 0 { 276 | returnFractionComplete = 0 277 | } 278 | 279 | contentViewController?.didScroll(with: returnFractionComplete, from: state) 280 | 281 | case .ended: 282 | // normal animation conditions 283 | if state == .fullSize && direction == .down || state == .minimized && direction == .up { 284 | switch state { 285 | case .fullSize: 286 | switch direction { 287 | case .down?: 288 | if runningAnimators[0].isReversed { 289 | runningAnimators.forEach { $0.isReversed = !$0.isReversed } // will Cancel reverse 290 | contentViewController?.willChangeState(to: .minimized) 291 | } 292 | default: break 293 | } 294 | case .minimized: 295 | switch direction { 296 | case .up?: 297 | if runningAnimators[0].isReversed { 298 | runningAnimators.forEach { $0.isReversed = !$0.isReversed } //will Cancel reverse 299 | contentViewController?.willChangeState(to: .fullSize) 300 | } 301 | default: break 302 | } 303 | } 304 | } else { 305 | // reverse the animations based on their current state and pan motion direction 306 | switch state { 307 | case .fullSize: 308 | switch direction { 309 | case .up?: 310 | if !runningAnimators[0].isReversed { 311 | runningAnimators.forEach { $0.isReversed = !$0.isReversed } //will reverse 312 | contentViewController?.willChangeState(to: .fullSize) 313 | } 314 | default: break 315 | } 316 | case .minimized: 317 | switch direction { 318 | case .down?: 319 | if !runningAnimators[0].isReversed { 320 | runningAnimators.forEach { $0.isReversed = !$0.isReversed } //will reverse 321 | contentViewController?.willChangeState(to: .minimized) 322 | } 323 | default: break 324 | } 325 | } 326 | } 327 | 328 | // continue the paused animations 329 | runningAnimators.forEach { $0.continueAnimation(withTimingParameters: nil, durationFactor: 0) } 330 | default: break 331 | } 332 | } 333 | 334 | private func startInteractiveTransition(duration: TimeInterval) { 335 | animateTransitionIfNeeded(duration: animationDuration) 336 | runningAnimators.forEach({ animator in 337 | animator.pauseAnimation() 338 | }) 339 | 340 | animationProgress = runningAnimators.map { $0.fractionComplete } 341 | } 342 | 343 | } 344 | 345 | // MARK: - Content Constrains Helpers - 346 | 347 | extension DrawerViewController { 348 | 349 | private func setupOpenConstraints() { 350 | guard bottomAnchorContent != nil else { return } 351 | bottomAnchorContent.constant = 0 352 | } 353 | 354 | private func setupClosedConstraints() { 355 | guard bottomAnchorContent != nil else { return } 356 | bottomAnchorContent.constant = ownMaxHeight - ownMinHeight 357 | } 358 | 359 | private func setupDismissConstraints() { 360 | guard bottomAnchorContent != nil else { return } 361 | bottomAnchorContent.constant = heightAnchorContent.constant 362 | } 363 | 364 | } 365 | 366 | // MARK: - Background Helpers - 367 | 368 | extension DrawerViewController { 369 | 370 | private func handleOpenBackgroundAnimation() { 371 | switch self.backgroundType { 372 | case .withBlur(let style)?: 373 | self.backgroundBlurEffectView?.effect = UIBlurEffect(style: style) 374 | case .withColor(let color)?: 375 | backgroundColorView?.backgroundColor = color 376 | default: break 377 | } 378 | } 379 | 380 | 381 | private func handleCloseBackgroundAnimation() { 382 | switch self.backgroundType { 383 | case .withBlur?: 384 | self.backgroundBlurEffectView?.effect = nil 385 | case .withColor(let color)?: 386 | backgroundColorView?.backgroundColor = color.withAlphaComponent(0) 387 | default: break 388 | } 389 | } 390 | } 391 | 392 | // MARK: - Full Animations - 393 | 394 | extension DrawerViewController { 395 | private func openDrawer(animated: Bool = true, completion: (() -> Void)? = nil) { 396 | state = .fullSize 397 | setupOpenConstraints() 398 | 399 | //allow for scrolling in ContentVC 400 | if let scrollableContent = contentViewController as? EmbeddedScrollable { 401 | scrollableContent.setScrollEnabled(true) 402 | } 403 | 404 | let duration: TimeInterval = animated ? animationDuration : 0 405 | UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: damping, initialSpringVelocity: 0, options: [.beginFromCurrentState], animations: { [weak self] in 406 | guard let self = self else { return } 407 | self.handleOpenBackgroundAnimation() 408 | self.roundCorners(with: self.cornerRadius.fullSize) 409 | self.view.layoutIfNeeded() 410 | }, completion: { [weak self] _ in 411 | completion?() 412 | guard let self = self else { return } 413 | self.contentViewController?.didChangeState(to: .fullSize) 414 | }) 415 | } 416 | 417 | private func closeDrawer(animated: Bool = true, completion: (() -> Void)? = nil) { 418 | state = .minimized 419 | setupClosedConstraints() 420 | 421 | //allow for scrolling in ContentVC 422 | if let scrollableContent = contentViewController as? EmbeddedScrollable { 423 | scrollableContent.setScrollEnabled(true) 424 | } 425 | 426 | let duration: TimeInterval = animated ? animationDuration : 0 427 | UIView.animate(withDuration: duration, delay: 0, usingSpringWithDamping: damping, initialSpringVelocity: 0, options: [.beginFromCurrentState], animations: { [weak self] in 428 | guard let self = self else { return } 429 | self.handleCloseBackgroundAnimation() 430 | self.roundCorners(with: self.cornerRadius.minimized) 431 | self.view.layoutIfNeeded() 432 | 433 | }, completion: { [weak self] _ in 434 | completion?() 435 | guard let self = self else { return } 436 | self.contentViewController?.didChangeState(to: .minimized) 437 | }) 438 | } 439 | 440 | private func dismiss(completion: (() -> Void)? = nil) { 441 | state = .minimized 442 | setupDismissConstraints() 443 | 444 | UIView.animate(withDuration: animationDuration, delay: 0, usingSpringWithDamping: damping, initialSpringVelocity: 0, options: [.beginFromCurrentState], animations: { [weak self] in 445 | self?.view.layoutIfNeeded() 446 | }, completion: { [weak self] _ in 447 | completion?() 448 | guard let self = self else { return } 449 | self.contentViewController?.didChangeState(to: .closed) 450 | self.destroySelf() 451 | }) 452 | } 453 | 454 | } 455 | 456 | // MARK: - Scroll Animations - 457 | 458 | extension DrawerViewController { 459 | private enum Direction { 460 | case up, down 461 | 462 | mutating func reversed() { 463 | switch self { 464 | case .up: 465 | self = .down 466 | case .down: 467 | self = .up 468 | } 469 | } 470 | } 471 | 472 | /// Initiate transition if not already running 473 | private func animateTransitionIfNeeded(duration: TimeInterval) { 474 | guard runningAnimators.isEmpty else { 475 | direction.reversed() 476 | return 477 | } 478 | switch state { 479 | case .fullSize: 480 | direction = .down 481 | case .minimized: 482 | direction = .up 483 | } 484 | 485 | let animator = UIViewPropertyAnimator(duration: duration, dampingRatio: 1) 486 | 487 | animator.addAnimations { 488 | switch self.state { 489 | case .fullSize: 490 | self.contentViewController?.willChangeState(to: .minimized) 491 | self.setupClosedConstraints() 492 | self.roundCorners(with: self.cornerRadius.minimized) 493 | self.handleCloseBackgroundAnimation() 494 | case .minimized: 495 | self.contentViewController?.willChangeState(to: .fullSize) 496 | self.setupOpenConstraints() 497 | self.roundCorners(with: self.cornerRadius.fullSize) 498 | self.handleOpenBackgroundAnimation() 499 | } 500 | self.view.layoutIfNeeded() 501 | } 502 | 503 | animator.addCompletion { position in 504 | switch self.direction { 505 | case .down?: 506 | self.closeDrawer(animated: self.state == .minimized) 507 | case .up?: 508 | self.openDrawer(animated: self.state == .fullSize) 509 | default: break 510 | } 511 | 512 | self.direction = nil 513 | self.animationProgress.removeAll() 514 | self.runningAnimators.removeAll() 515 | } 516 | 517 | animator.startAnimation() 518 | runningAnimators.append(animator) 519 | } 520 | 521 | // MARK: - Show animation 522 | 523 | //initial animation to display 524 | private func showAnimation() { 525 | isInitiated = true 526 | switch state { 527 | case .fullSize: 528 | openDrawer() 529 | case .minimized: 530 | closeDrawer() 531 | } 532 | } 533 | 534 | 535 | } 536 | 537 | // MARK: - EmbeddableContentDelegate 538 | 539 | extension DrawerViewController: EmbeddableContentDelegate { 540 | 541 | public var maxAllowedHeight: CGFloat { 542 | return UIScreen.main.bounds.height 543 | } 544 | 545 | public func handle(action: Drawer.Action) { 546 | 547 | switch action { 548 | case .layoutUpdated(config: let config): 549 | DispatchQueue.main.asyncAfter(deadline: .now()) { 550 | self.embedConfig = config 551 | } 552 | case .changeState(let drawerState): 553 | switch drawerState { 554 | case .minimize: 555 | contentViewController?.willChangeState(to: .minimized) 556 | closeDrawer() 557 | case .fullScreen: 558 | contentViewController?.willChangeState(to: .fullSize) 559 | openDrawer() 560 | case .dismiss: 561 | contentViewController?.willChangeState(to: .closed) 562 | dismiss() 563 | } 564 | } 565 | } 566 | } 567 | 568 | // MARK: - UIGestureRecognizerDelegate 569 | 570 | extension DrawerViewController: UIGestureRecognizerDelegate { 571 | public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { 572 | guard let embeddedContentViewController = contentViewController else { 573 | return false 574 | } 575 | 576 | guard let gesture = gestureRecognizer as? UIPanGestureRecognizer else { return false } 577 | let direction = gesture.velocity(in: view).y 578 | 579 | if let scrollableContent = embeddedContentViewController as? EmbeddedScrollable { 580 | if state == .fullSize && scrollableContent.isScrolledToTop && direction > 0 { 581 | scrollableContent.setScrollEnabled(false) 582 | } else { 583 | scrollableContent.setScrollEnabled(true) 584 | } 585 | } 586 | return false 587 | } 588 | 589 | public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { 590 | if gestureRecognizer is UIScreenEdgePanGestureRecognizer { 591 | return false 592 | } 593 | 594 | if touch.view?.superview(of: UITableViewCell.self) != nil && gestureRecognizer is UIPanGestureRecognizer { 595 | return false 596 | } 597 | 598 | if touch.view is UIButton && gestureRecognizer is UIPanGestureRecognizer { 599 | return false 600 | } 601 | 602 | // if the touch view is not the content's view, then we don't handle the touch in some cases 603 | if touch.view != contentViewController?.view && gestureRecognizer is UIPanGestureRecognizer { 604 | 605 | // check if the scrollview can scroll, then allow gesture based on that 606 | if let scrollView = touch.view as? UIScrollView, 607 | self.state == .fullSize { 608 | 609 | let scrollViewHeight = scrollView.frame.size.height 610 | let scrollContentSizeHeight = scrollView.contentSize.height 611 | 612 | if scrollViewHeight >= scrollContentSizeHeight { 613 | return true 614 | } else { 615 | return false 616 | } 617 | 618 | } 619 | 620 | return true 621 | } 622 | 623 | return true 624 | } 625 | 626 | } 627 | 628 | // MARK: - TouchPassingWindowDelegate 629 | 630 | extension DrawerViewController: TouchPassingWindowDelegate { 631 | func windowShouldBlockAll() -> Bool { 632 | switch backgroundInteraction { 633 | case .whenMinimised: 634 | return false 635 | } 636 | } 637 | 638 | var viewsForInterceptingTouches: [UIView] { 639 | guard 640 | let embeddedContentViewController = contentViewController 641 | else { 642 | return [] 643 | } 644 | var views: [UIView] = [embeddedContentViewController.view] 645 | 646 | // intercept taps on the background if state is fullSize 647 | if backgroundBlurEffectView != nil && state == .fullSize { 648 | views.append(backgroundBlurEffectView!) 649 | } 650 | 651 | // intercept taps on the background if state is fullSize 652 | if backgroundColorView != nil && state == .fullSize { 653 | views.append(backgroundColorView!) 654 | } 655 | 656 | return views 657 | } 658 | 659 | } 660 | 661 | 662 | // MARK: - Destroy - 663 | 664 | extension DrawerViewController { 665 | 666 | private func destroySelf() { 667 | contentViewController?.view.removeFromSuperview() 668 | contentViewController?.removeFromParent() 669 | 670 | view.removeFromSuperview() 671 | removeFromParent() 672 | 673 | contentViewController = nil 674 | backgroundViewController = nil 675 | embedConfig?.dismissCompleteCallback?() 676 | 677 | isInitiated = false 678 | } 679 | 680 | } 681 | 682 | // MARK: - DrawerBackgroundType 683 | 684 | extension DrawerViewController { 685 | 686 | public enum DrawerBackgroundType { 687 | case clear 688 | case withColor(UIColor) 689 | case withBlur(UIBlurEffect.Style) 690 | } 691 | 692 | } 693 | 694 | // MARK: - CGFloat 2 decimal 695 | 696 | extension DrawerViewController { 697 | 698 | func fourDecimal(_ value: CGFloat) -> CGFloat { 699 | return round(10000*value)/10000 700 | } 701 | 702 | }//swiftlint:disable:this file_length 703 | 704 | -------------------------------------------------------------------------------- /Drawer/Drawer.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 42321BF92226973500CE1DCB /* Drawer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 42321BEF2226973500CE1DCB /* Drawer.framework */; }; 11 | 42321BFE2226973500CE1DCB /* DrawerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42321BFD2226973500CE1DCB /* DrawerTests.swift */; }; 12 | 42321C002226973500CE1DCB /* Drawer.h in Headers */ = {isa = PBXBuildFile; fileRef = 42321BF22226973500CE1DCB /* Drawer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | 42321C0B222697EA00CE1DCB /* TouchInterceptingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42321C0A222697EA00CE1DCB /* TouchInterceptingView.swift */; }; 14 | 42321C0E2226982700CE1DCB /* UIView+Superview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42321C0D2226982700CE1DCB /* UIView+Superview.swift */; }; 15 | 42321C152226987C00CE1DCB /* DrawerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42321C102226987C00CE1DCB /* DrawerCoordinator.swift */; }; 16 | 42321C162226987C00CE1DCB /* EmbeddableContentDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42321C112226987C00CE1DCB /* EmbeddableContentDelegate.swift */; }; 17 | 42321C172226987C00CE1DCB /* DrawerModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42321C122226987C00CE1DCB /* DrawerModels.swift */; }; 18 | 42321C182226987C00CE1DCB /* DrawerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42321C132226987C00CE1DCB /* DrawerViewController.swift */; }; 19 | 42321C1C2226A79F00CE1DCB /* Embeddable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42321C1B2226A79F00CE1DCB /* Embeddable.swift */; }; 20 | 42321C1E2226A7ED00CE1DCB /* EmbeddedScrollable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42321C1D2226A7ED00CE1DCB /* EmbeddedScrollable.swift */; }; 21 | 42321C982226BD1000CE1DCB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42321C972226BD1000CE1DCB /* AppDelegate.swift */; }; 22 | 42321C9A2226BD1000CE1DCB /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42321C992226BD1000CE1DCB /* ViewController.swift */; }; 23 | 42321C9D2226BD1000CE1DCB /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 42321C9B2226BD1000CE1DCB /* Main.storyboard */; }; 24 | 42321C9F2226BD1100CE1DCB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 42321C9E2226BD1100CE1DCB /* Assets.xcassets */; }; 25 | 42321CA22226BD1100CE1DCB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 42321CA02226BD1100CE1DCB /* LaunchScreen.storyboard */; }; 26 | 42321CAD2226BD1100CE1DCB /* ExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42321CAC2226BD1100CE1DCB /* ExampleTests.swift */; }; 27 | 42321CBA2226BD8600CE1DCB /* ContentViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 42321CB82226BD8600CE1DCB /* ContentViewController.storyboard */; }; 28 | 42321CBB2226BD8600CE1DCB /* ContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42321CB92226BD8600CE1DCB /* ContentViewController.swift */; }; 29 | 42321CEB2226BE8400CE1DCB /* Drawer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 42321BEF2226973500CE1DCB /* Drawer.framework */; }; 30 | 42321CEC2226BE8400CE1DCB /* Drawer.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 42321BEF2226973500CE1DCB /* Drawer.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 31 | /* End PBXBuildFile section */ 32 | 33 | /* Begin PBXContainerItemProxy section */ 34 | 42321BFA2226973500CE1DCB /* PBXContainerItemProxy */ = { 35 | isa = PBXContainerItemProxy; 36 | containerPortal = 42321BE62226973500CE1DCB /* Project object */; 37 | proxyType = 1; 38 | remoteGlobalIDString = 42321BEE2226973500CE1DCB; 39 | remoteInfo = Drawer; 40 | }; 41 | 42321CA92226BD1100CE1DCB /* PBXContainerItemProxy */ = { 42 | isa = PBXContainerItemProxy; 43 | containerPortal = 42321BE62226973500CE1DCB /* Project object */; 44 | proxyType = 1; 45 | remoteGlobalIDString = 42321C942226BD1000CE1DCB; 46 | remoteInfo = Example; 47 | }; 48 | 42321CED2226BE8400CE1DCB /* PBXContainerItemProxy */ = { 49 | isa = PBXContainerItemProxy; 50 | containerPortal = 42321BE62226973500CE1DCB /* Project object */; 51 | proxyType = 1; 52 | remoteGlobalIDString = 42321BEE2226973500CE1DCB; 53 | remoteInfo = Drawer; 54 | }; 55 | /* End PBXContainerItemProxy section */ 56 | 57 | /* Begin PBXCopyFilesBuildPhase section */ 58 | 42321CEF2226BE8400CE1DCB /* Embed Frameworks */ = { 59 | isa = PBXCopyFilesBuildPhase; 60 | buildActionMask = 2147483647; 61 | dstPath = ""; 62 | dstSubfolderSpec = 10; 63 | files = ( 64 | 42321CEC2226BE8400CE1DCB /* Drawer.framework in Embed Frameworks */, 65 | ); 66 | name = "Embed Frameworks"; 67 | runOnlyForDeploymentPostprocessing = 0; 68 | }; 69 | /* End PBXCopyFilesBuildPhase section */ 70 | 71 | /* Begin PBXFileReference section */ 72 | 42321BEF2226973500CE1DCB /* Drawer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Drawer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 73 | 42321BF22226973500CE1DCB /* Drawer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Drawer.h; sourceTree = ""; }; 74 | 42321BF32226973500CE1DCB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 75 | 42321BF82226973500CE1DCB /* DrawerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DrawerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 76 | 42321BFD2226973500CE1DCB /* DrawerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DrawerTests.swift; sourceTree = ""; }; 77 | 42321BFF2226973500CE1DCB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 78 | 42321C0A222697EA00CE1DCB /* TouchInterceptingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchInterceptingView.swift; sourceTree = ""; }; 79 | 42321C0D2226982700CE1DCB /* UIView+Superview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Superview.swift"; sourceTree = ""; }; 80 | 42321C102226987C00CE1DCB /* DrawerCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DrawerCoordinator.swift; sourceTree = ""; }; 81 | 42321C112226987C00CE1DCB /* EmbeddableContentDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmbeddableContentDelegate.swift; sourceTree = ""; }; 82 | 42321C122226987C00CE1DCB /* DrawerModels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DrawerModels.swift; sourceTree = ""; }; 83 | 42321C132226987C00CE1DCB /* DrawerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DrawerViewController.swift; sourceTree = ""; }; 84 | 42321C1B2226A79F00CE1DCB /* Embeddable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Embeddable.swift; sourceTree = ""; }; 85 | 42321C1D2226A7ED00CE1DCB /* EmbeddedScrollable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmbeddedScrollable.swift; sourceTree = ""; }; 86 | 42321C952226BD1000CE1DCB /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 87 | 42321C972226BD1000CE1DCB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 88 | 42321C992226BD1000CE1DCB /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 89 | 42321C9C2226BD1000CE1DCB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 90 | 42321C9E2226BD1100CE1DCB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 91 | 42321CA12226BD1100CE1DCB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 92 | 42321CA32226BD1100CE1DCB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 93 | 42321CA82226BD1100CE1DCB /* ExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 94 | 42321CAC2226BD1100CE1DCB /* ExampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleTests.swift; sourceTree = ""; }; 95 | 42321CAE2226BD1100CE1DCB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 96 | 42321CB82226BD8600CE1DCB /* ContentViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = ContentViewController.storyboard; sourceTree = ""; }; 97 | 42321CB92226BD8600CE1DCB /* ContentViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentViewController.swift; sourceTree = ""; }; 98 | /* End PBXFileReference section */ 99 | 100 | /* Begin PBXFrameworksBuildPhase section */ 101 | 42321BEC2226973500CE1DCB /* Frameworks */ = { 102 | isa = PBXFrameworksBuildPhase; 103 | buildActionMask = 2147483647; 104 | files = ( 105 | ); 106 | runOnlyForDeploymentPostprocessing = 0; 107 | }; 108 | 42321BF52226973500CE1DCB /* Frameworks */ = { 109 | isa = PBXFrameworksBuildPhase; 110 | buildActionMask = 2147483647; 111 | files = ( 112 | 42321BF92226973500CE1DCB /* Drawer.framework in Frameworks */, 113 | ); 114 | runOnlyForDeploymentPostprocessing = 0; 115 | }; 116 | 42321C922226BD1000CE1DCB /* Frameworks */ = { 117 | isa = PBXFrameworksBuildPhase; 118 | buildActionMask = 2147483647; 119 | files = ( 120 | 42321CEB2226BE8400CE1DCB /* Drawer.framework in Frameworks */, 121 | ); 122 | runOnlyForDeploymentPostprocessing = 0; 123 | }; 124 | 42321CA52226BD1100CE1DCB /* Frameworks */ = { 125 | isa = PBXFrameworksBuildPhase; 126 | buildActionMask = 2147483647; 127 | files = ( 128 | ); 129 | runOnlyForDeploymentPostprocessing = 0; 130 | }; 131 | /* End PBXFrameworksBuildPhase section */ 132 | 133 | /* Begin PBXGroup section */ 134 | 42321BE52226973500CE1DCB = { 135 | isa = PBXGroup; 136 | children = ( 137 | 42321BF12226973500CE1DCB /* Drawer */, 138 | 42321BFC2226973500CE1DCB /* DrawerTests */, 139 | 42321C962226BD1000CE1DCB /* Example */, 140 | 42321CAB2226BD1100CE1DCB /* ExampleTests */, 141 | 42321BF02226973500CE1DCB /* Products */, 142 | 42321CBC2226BDE700CE1DCB /* Frameworks */, 143 | ); 144 | sourceTree = ""; 145 | }; 146 | 42321BF02226973500CE1DCB /* Products */ = { 147 | isa = PBXGroup; 148 | children = ( 149 | 42321BEF2226973500CE1DCB /* Drawer.framework */, 150 | 42321BF82226973500CE1DCB /* DrawerTests.xctest */, 151 | 42321C952226BD1000CE1DCB /* Example.app */, 152 | 42321CA82226BD1100CE1DCB /* ExampleTests.xctest */, 153 | ); 154 | name = Products; 155 | sourceTree = ""; 156 | }; 157 | 42321BF12226973500CE1DCB /* Drawer */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | 42321C0F2226984A00CE1DCB /* Drawer */, 161 | 42321C0C2226981200CE1DCB /* Extensions */, 162 | 42321C092226977500CE1DCB /* Touch Intercepting View */, 163 | 42321BF22226973500CE1DCB /* Drawer.h */, 164 | 42321BF32226973500CE1DCB /* Info.plist */, 165 | ); 166 | path = Drawer; 167 | sourceTree = ""; 168 | }; 169 | 42321BFC2226973500CE1DCB /* DrawerTests */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | 42321BFD2226973500CE1DCB /* DrawerTests.swift */, 173 | 42321BFF2226973500CE1DCB /* Info.plist */, 174 | ); 175 | path = DrawerTests; 176 | sourceTree = ""; 177 | }; 178 | 42321C092226977500CE1DCB /* Touch Intercepting View */ = { 179 | isa = PBXGroup; 180 | children = ( 181 | 42321C0A222697EA00CE1DCB /* TouchInterceptingView.swift */, 182 | ); 183 | path = "Touch Intercepting View"; 184 | sourceTree = ""; 185 | }; 186 | 42321C0C2226981200CE1DCB /* Extensions */ = { 187 | isa = PBXGroup; 188 | children = ( 189 | 42321C0D2226982700CE1DCB /* UIView+Superview.swift */, 190 | ); 191 | path = Extensions; 192 | sourceTree = ""; 193 | }; 194 | 42321C0F2226984A00CE1DCB /* Drawer */ = { 195 | isa = PBXGroup; 196 | children = ( 197 | 42321C102226987C00CE1DCB /* DrawerCoordinator.swift */, 198 | 42321C132226987C00CE1DCB /* DrawerViewController.swift */, 199 | 42321C122226987C00CE1DCB /* DrawerModels.swift */, 200 | 42321C1F2226A80700CE1DCB /* Protocols */, 201 | ); 202 | path = Drawer; 203 | sourceTree = ""; 204 | }; 205 | 42321C1F2226A80700CE1DCB /* Protocols */ = { 206 | isa = PBXGroup; 207 | children = ( 208 | 42321C1B2226A79F00CE1DCB /* Embeddable.swift */, 209 | 42321C1D2226A7ED00CE1DCB /* EmbeddedScrollable.swift */, 210 | 42321C112226987C00CE1DCB /* EmbeddableContentDelegate.swift */, 211 | ); 212 | path = Protocols; 213 | sourceTree = ""; 214 | }; 215 | 42321C962226BD1000CE1DCB /* Example */ = { 216 | isa = PBXGroup; 217 | children = ( 218 | 42321C972226BD1000CE1DCB /* AppDelegate.swift */, 219 | 42321CB52226BD2D00CE1DCB /* Scenes */, 220 | 42321C9E2226BD1100CE1DCB /* Assets.xcassets */, 221 | 42321CA02226BD1100CE1DCB /* LaunchScreen.storyboard */, 222 | 42321CA32226BD1100CE1DCB /* Info.plist */, 223 | ); 224 | path = Example; 225 | sourceTree = ""; 226 | }; 227 | 42321CAB2226BD1100CE1DCB /* ExampleTests */ = { 228 | isa = PBXGroup; 229 | children = ( 230 | 42321CAC2226BD1100CE1DCB /* ExampleTests.swift */, 231 | 42321CAE2226BD1100CE1DCB /* Info.plist */, 232 | ); 233 | path = ExampleTests; 234 | sourceTree = ""; 235 | }; 236 | 42321CB52226BD2D00CE1DCB /* Scenes */ = { 237 | isa = PBXGroup; 238 | children = ( 239 | 42321CB62226BD3B00CE1DCB /* Background View Controller */, 240 | 42321CB72226BD6800CE1DCB /* Content View Controller */, 241 | ); 242 | path = Scenes; 243 | sourceTree = ""; 244 | }; 245 | 42321CB62226BD3B00CE1DCB /* Background View Controller */ = { 246 | isa = PBXGroup; 247 | children = ( 248 | 42321C992226BD1000CE1DCB /* ViewController.swift */, 249 | 42321C9B2226BD1000CE1DCB /* Main.storyboard */, 250 | ); 251 | path = "Background View Controller"; 252 | sourceTree = ""; 253 | }; 254 | 42321CB72226BD6800CE1DCB /* Content View Controller */ = { 255 | isa = PBXGroup; 256 | children = ( 257 | 42321CB92226BD8600CE1DCB /* ContentViewController.swift */, 258 | 42321CB82226BD8600CE1DCB /* ContentViewController.storyboard */, 259 | ); 260 | path = "Content View Controller"; 261 | sourceTree = ""; 262 | }; 263 | 42321CBC2226BDE700CE1DCB /* Frameworks */ = { 264 | isa = PBXGroup; 265 | children = ( 266 | ); 267 | name = Frameworks; 268 | sourceTree = ""; 269 | }; 270 | /* End PBXGroup section */ 271 | 272 | /* Begin PBXHeadersBuildPhase section */ 273 | 42321BEA2226973500CE1DCB /* Headers */ = { 274 | isa = PBXHeadersBuildPhase; 275 | buildActionMask = 2147483647; 276 | files = ( 277 | 42321C002226973500CE1DCB /* Drawer.h in Headers */, 278 | ); 279 | runOnlyForDeploymentPostprocessing = 0; 280 | }; 281 | /* End PBXHeadersBuildPhase section */ 282 | 283 | /* Begin PBXNativeTarget section */ 284 | 42321BEE2226973500CE1DCB /* Drawer */ = { 285 | isa = PBXNativeTarget; 286 | buildConfigurationList = 42321C032226973500CE1DCB /* Build configuration list for PBXNativeTarget "Drawer" */; 287 | buildPhases = ( 288 | 42321BEA2226973500CE1DCB /* Headers */, 289 | 42321BEB2226973500CE1DCB /* Sources */, 290 | 42321BEC2226973500CE1DCB /* Frameworks */, 291 | 42321BED2226973500CE1DCB /* Resources */, 292 | ); 293 | buildRules = ( 294 | ); 295 | dependencies = ( 296 | ); 297 | name = Drawer; 298 | productName = Drawer; 299 | productReference = 42321BEF2226973500CE1DCB /* Drawer.framework */; 300 | productType = "com.apple.product-type.framework"; 301 | }; 302 | 42321BF72226973500CE1DCB /* DrawerTests */ = { 303 | isa = PBXNativeTarget; 304 | buildConfigurationList = 42321C062226973500CE1DCB /* Build configuration list for PBXNativeTarget "DrawerTests" */; 305 | buildPhases = ( 306 | 42321BF42226973500CE1DCB /* Sources */, 307 | 42321BF52226973500CE1DCB /* Frameworks */, 308 | 42321BF62226973500CE1DCB /* Resources */, 309 | ); 310 | buildRules = ( 311 | ); 312 | dependencies = ( 313 | 42321BFB2226973500CE1DCB /* PBXTargetDependency */, 314 | ); 315 | name = DrawerTests; 316 | productName = DrawerTests; 317 | productReference = 42321BF82226973500CE1DCB /* DrawerTests.xctest */; 318 | productType = "com.apple.product-type.bundle.unit-test"; 319 | }; 320 | 42321C942226BD1000CE1DCB /* Example */ = { 321 | isa = PBXNativeTarget; 322 | buildConfigurationList = 42321CB32226BD1100CE1DCB /* Build configuration list for PBXNativeTarget "Example" */; 323 | buildPhases = ( 324 | 42321C912226BD1000CE1DCB /* Sources */, 325 | 42321C922226BD1000CE1DCB /* Frameworks */, 326 | 42321C932226BD1000CE1DCB /* Resources */, 327 | 42321CEF2226BE8400CE1DCB /* Embed Frameworks */, 328 | ); 329 | buildRules = ( 330 | ); 331 | dependencies = ( 332 | 42321CEE2226BE8400CE1DCB /* PBXTargetDependency */, 333 | ); 334 | name = Example; 335 | productName = Example; 336 | productReference = 42321C952226BD1000CE1DCB /* Example.app */; 337 | productType = "com.apple.product-type.application"; 338 | }; 339 | 42321CA72226BD1100CE1DCB /* ExampleTests */ = { 340 | isa = PBXNativeTarget; 341 | buildConfigurationList = 42321CB42226BD1100CE1DCB /* Build configuration list for PBXNativeTarget "ExampleTests" */; 342 | buildPhases = ( 343 | 42321CA42226BD1100CE1DCB /* Sources */, 344 | 42321CA52226BD1100CE1DCB /* Frameworks */, 345 | 42321CA62226BD1100CE1DCB /* Resources */, 346 | ); 347 | buildRules = ( 348 | ); 349 | dependencies = ( 350 | 42321CAA2226BD1100CE1DCB /* PBXTargetDependency */, 351 | ); 352 | name = ExampleTests; 353 | productName = ExampleTests; 354 | productReference = 42321CA82226BD1100CE1DCB /* ExampleTests.xctest */; 355 | productType = "com.apple.product-type.bundle.unit-test"; 356 | }; 357 | /* End PBXNativeTarget section */ 358 | 359 | /* Begin PBXProject section */ 360 | 42321BE62226973500CE1DCB /* Project object */ = { 361 | isa = PBXProject; 362 | attributes = { 363 | LastSwiftUpdateCheck = 1010; 364 | LastUpgradeCheck = 1010; 365 | ORGANIZATIONNAME = "Andrei Hogea"; 366 | TargetAttributes = { 367 | 42321BEE2226973500CE1DCB = { 368 | CreatedOnToolsVersion = 10.1; 369 | LastSwiftMigration = 1020; 370 | }; 371 | 42321BF72226973500CE1DCB = { 372 | CreatedOnToolsVersion = 10.1; 373 | }; 374 | 42321C942226BD1000CE1DCB = { 375 | CreatedOnToolsVersion = 10.1; 376 | LastSwiftMigration = 1020; 377 | }; 378 | 42321CA72226BD1100CE1DCB = { 379 | CreatedOnToolsVersion = 10.1; 380 | LastSwiftMigration = 1020; 381 | TestTargetID = 42321C942226BD1000CE1DCB; 382 | }; 383 | }; 384 | }; 385 | buildConfigurationList = 42321BE92226973500CE1DCB /* Build configuration list for PBXProject "Drawer" */; 386 | compatibilityVersion = "Xcode 9.3"; 387 | developmentRegion = en; 388 | hasScannedForEncodings = 0; 389 | knownRegions = ( 390 | en, 391 | Base, 392 | ); 393 | mainGroup = 42321BE52226973500CE1DCB; 394 | productRefGroup = 42321BF02226973500CE1DCB /* Products */; 395 | projectDirPath = ""; 396 | projectRoot = ""; 397 | targets = ( 398 | 42321BEE2226973500CE1DCB /* Drawer */, 399 | 42321BF72226973500CE1DCB /* DrawerTests */, 400 | 42321C942226BD1000CE1DCB /* Example */, 401 | 42321CA72226BD1100CE1DCB /* ExampleTests */, 402 | ); 403 | }; 404 | /* End PBXProject section */ 405 | 406 | /* Begin PBXResourcesBuildPhase section */ 407 | 42321BED2226973500CE1DCB /* Resources */ = { 408 | isa = PBXResourcesBuildPhase; 409 | buildActionMask = 2147483647; 410 | files = ( 411 | ); 412 | runOnlyForDeploymentPostprocessing = 0; 413 | }; 414 | 42321BF62226973500CE1DCB /* Resources */ = { 415 | isa = PBXResourcesBuildPhase; 416 | buildActionMask = 2147483647; 417 | files = ( 418 | ); 419 | runOnlyForDeploymentPostprocessing = 0; 420 | }; 421 | 42321C932226BD1000CE1DCB /* Resources */ = { 422 | isa = PBXResourcesBuildPhase; 423 | buildActionMask = 2147483647; 424 | files = ( 425 | 42321CA22226BD1100CE1DCB /* LaunchScreen.storyboard in Resources */, 426 | 42321C9F2226BD1100CE1DCB /* Assets.xcassets in Resources */, 427 | 42321CBA2226BD8600CE1DCB /* ContentViewController.storyboard in Resources */, 428 | 42321C9D2226BD1000CE1DCB /* Main.storyboard in Resources */, 429 | ); 430 | runOnlyForDeploymentPostprocessing = 0; 431 | }; 432 | 42321CA62226BD1100CE1DCB /* Resources */ = { 433 | isa = PBXResourcesBuildPhase; 434 | buildActionMask = 2147483647; 435 | files = ( 436 | ); 437 | runOnlyForDeploymentPostprocessing = 0; 438 | }; 439 | /* End PBXResourcesBuildPhase section */ 440 | 441 | /* Begin PBXSourcesBuildPhase section */ 442 | 42321BEB2226973500CE1DCB /* Sources */ = { 443 | isa = PBXSourcesBuildPhase; 444 | buildActionMask = 2147483647; 445 | files = ( 446 | 42321C1E2226A7ED00CE1DCB /* EmbeddedScrollable.swift in Sources */, 447 | 42321C0B222697EA00CE1DCB /* TouchInterceptingView.swift in Sources */, 448 | 42321C162226987C00CE1DCB /* EmbeddableContentDelegate.swift in Sources */, 449 | 42321C182226987C00CE1DCB /* DrawerViewController.swift in Sources */, 450 | 42321C152226987C00CE1DCB /* DrawerCoordinator.swift in Sources */, 451 | 42321C172226987C00CE1DCB /* DrawerModels.swift in Sources */, 452 | 42321C1C2226A79F00CE1DCB /* Embeddable.swift in Sources */, 453 | 42321C0E2226982700CE1DCB /* UIView+Superview.swift in Sources */, 454 | ); 455 | runOnlyForDeploymentPostprocessing = 0; 456 | }; 457 | 42321BF42226973500CE1DCB /* Sources */ = { 458 | isa = PBXSourcesBuildPhase; 459 | buildActionMask = 2147483647; 460 | files = ( 461 | 42321BFE2226973500CE1DCB /* DrawerTests.swift in Sources */, 462 | ); 463 | runOnlyForDeploymentPostprocessing = 0; 464 | }; 465 | 42321C912226BD1000CE1DCB /* Sources */ = { 466 | isa = PBXSourcesBuildPhase; 467 | buildActionMask = 2147483647; 468 | files = ( 469 | 42321C9A2226BD1000CE1DCB /* ViewController.swift in Sources */, 470 | 42321CBB2226BD8600CE1DCB /* ContentViewController.swift in Sources */, 471 | 42321C982226BD1000CE1DCB /* AppDelegate.swift in Sources */, 472 | ); 473 | runOnlyForDeploymentPostprocessing = 0; 474 | }; 475 | 42321CA42226BD1100CE1DCB /* Sources */ = { 476 | isa = PBXSourcesBuildPhase; 477 | buildActionMask = 2147483647; 478 | files = ( 479 | 42321CAD2226BD1100CE1DCB /* ExampleTests.swift in Sources */, 480 | ); 481 | runOnlyForDeploymentPostprocessing = 0; 482 | }; 483 | /* End PBXSourcesBuildPhase section */ 484 | 485 | /* Begin PBXTargetDependency section */ 486 | 42321BFB2226973500CE1DCB /* PBXTargetDependency */ = { 487 | isa = PBXTargetDependency; 488 | target = 42321BEE2226973500CE1DCB /* Drawer */; 489 | targetProxy = 42321BFA2226973500CE1DCB /* PBXContainerItemProxy */; 490 | }; 491 | 42321CAA2226BD1100CE1DCB /* PBXTargetDependency */ = { 492 | isa = PBXTargetDependency; 493 | target = 42321C942226BD1000CE1DCB /* Example */; 494 | targetProxy = 42321CA92226BD1100CE1DCB /* PBXContainerItemProxy */; 495 | }; 496 | 42321CEE2226BE8400CE1DCB /* PBXTargetDependency */ = { 497 | isa = PBXTargetDependency; 498 | target = 42321BEE2226973500CE1DCB /* Drawer */; 499 | targetProxy = 42321CED2226BE8400CE1DCB /* PBXContainerItemProxy */; 500 | }; 501 | /* End PBXTargetDependency section */ 502 | 503 | /* Begin PBXVariantGroup section */ 504 | 42321C9B2226BD1000CE1DCB /* Main.storyboard */ = { 505 | isa = PBXVariantGroup; 506 | children = ( 507 | 42321C9C2226BD1000CE1DCB /* Base */, 508 | ); 509 | name = Main.storyboard; 510 | sourceTree = ""; 511 | }; 512 | 42321CA02226BD1100CE1DCB /* LaunchScreen.storyboard */ = { 513 | isa = PBXVariantGroup; 514 | children = ( 515 | 42321CA12226BD1100CE1DCB /* Base */, 516 | ); 517 | name = LaunchScreen.storyboard; 518 | sourceTree = ""; 519 | }; 520 | /* End PBXVariantGroup section */ 521 | 522 | /* Begin XCBuildConfiguration section */ 523 | 42321C012226973500CE1DCB /* Debug */ = { 524 | isa = XCBuildConfiguration; 525 | buildSettings = { 526 | ALWAYS_SEARCH_USER_PATHS = NO; 527 | CLANG_ANALYZER_NONNULL = YES; 528 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 529 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 530 | CLANG_CXX_LIBRARY = "libc++"; 531 | CLANG_ENABLE_MODULES = YES; 532 | CLANG_ENABLE_OBJC_ARC = YES; 533 | CLANG_ENABLE_OBJC_WEAK = YES; 534 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 535 | CLANG_WARN_BOOL_CONVERSION = YES; 536 | CLANG_WARN_COMMA = YES; 537 | CLANG_WARN_CONSTANT_CONVERSION = YES; 538 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 539 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 540 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 541 | CLANG_WARN_EMPTY_BODY = YES; 542 | CLANG_WARN_ENUM_CONVERSION = YES; 543 | CLANG_WARN_INFINITE_RECURSION = YES; 544 | CLANG_WARN_INT_CONVERSION = YES; 545 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 546 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 547 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 548 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 549 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 550 | CLANG_WARN_STRICT_PROTOTYPES = YES; 551 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 552 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 553 | CLANG_WARN_UNREACHABLE_CODE = YES; 554 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 555 | CODE_SIGN_IDENTITY = "iPhone Developer"; 556 | COPY_PHASE_STRIP = NO; 557 | CURRENT_PROJECT_VERSION = 1; 558 | DEBUG_INFORMATION_FORMAT = dwarf; 559 | ENABLE_STRICT_OBJC_MSGSEND = YES; 560 | ENABLE_TESTABILITY = YES; 561 | GCC_C_LANGUAGE_STANDARD = gnu11; 562 | GCC_DYNAMIC_NO_PIC = NO; 563 | GCC_NO_COMMON_BLOCKS = YES; 564 | GCC_OPTIMIZATION_LEVEL = 0; 565 | GCC_PREPROCESSOR_DEFINITIONS = ( 566 | "DEBUG=1", 567 | "$(inherited)", 568 | ); 569 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 570 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 571 | GCC_WARN_UNDECLARED_SELECTOR = YES; 572 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 573 | GCC_WARN_UNUSED_FUNCTION = YES; 574 | GCC_WARN_UNUSED_VARIABLE = YES; 575 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 576 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 577 | MTL_FAST_MATH = YES; 578 | ONLY_ACTIVE_ARCH = YES; 579 | SDKROOT = iphoneos; 580 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 581 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 582 | VERSIONING_SYSTEM = "apple-generic"; 583 | VERSION_INFO_PREFIX = ""; 584 | }; 585 | name = Debug; 586 | }; 587 | 42321C022226973500CE1DCB /* Release */ = { 588 | isa = XCBuildConfiguration; 589 | buildSettings = { 590 | ALWAYS_SEARCH_USER_PATHS = NO; 591 | CLANG_ANALYZER_NONNULL = YES; 592 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 593 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 594 | CLANG_CXX_LIBRARY = "libc++"; 595 | CLANG_ENABLE_MODULES = YES; 596 | CLANG_ENABLE_OBJC_ARC = YES; 597 | CLANG_ENABLE_OBJC_WEAK = YES; 598 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 599 | CLANG_WARN_BOOL_CONVERSION = YES; 600 | CLANG_WARN_COMMA = YES; 601 | CLANG_WARN_CONSTANT_CONVERSION = YES; 602 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 603 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 604 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 605 | CLANG_WARN_EMPTY_BODY = YES; 606 | CLANG_WARN_ENUM_CONVERSION = YES; 607 | CLANG_WARN_INFINITE_RECURSION = YES; 608 | CLANG_WARN_INT_CONVERSION = YES; 609 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 610 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 611 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 612 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 613 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 614 | CLANG_WARN_STRICT_PROTOTYPES = YES; 615 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 616 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 617 | CLANG_WARN_UNREACHABLE_CODE = YES; 618 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 619 | CODE_SIGN_IDENTITY = "iPhone Developer"; 620 | COPY_PHASE_STRIP = NO; 621 | CURRENT_PROJECT_VERSION = 1; 622 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 623 | ENABLE_NS_ASSERTIONS = NO; 624 | ENABLE_STRICT_OBJC_MSGSEND = YES; 625 | GCC_C_LANGUAGE_STANDARD = gnu11; 626 | GCC_NO_COMMON_BLOCKS = YES; 627 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 628 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 629 | GCC_WARN_UNDECLARED_SELECTOR = YES; 630 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 631 | GCC_WARN_UNUSED_FUNCTION = YES; 632 | GCC_WARN_UNUSED_VARIABLE = YES; 633 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 634 | MTL_ENABLE_DEBUG_INFO = NO; 635 | MTL_FAST_MATH = YES; 636 | SDKROOT = iphoneos; 637 | SWIFT_COMPILATION_MODE = wholemodule; 638 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 639 | VALIDATE_PRODUCT = YES; 640 | VERSIONING_SYSTEM = "apple-generic"; 641 | VERSION_INFO_PREFIX = ""; 642 | }; 643 | name = Release; 644 | }; 645 | 42321C042226973500CE1DCB /* Debug */ = { 646 | isa = XCBuildConfiguration; 647 | buildSettings = { 648 | CLANG_ENABLE_MODULES = YES; 649 | CODE_SIGN_IDENTITY = ""; 650 | CODE_SIGN_STYLE = Automatic; 651 | DEFINES_MODULE = YES; 652 | DEVELOPMENT_TEAM = ""; 653 | DYLIB_COMPATIBILITY_VERSION = 1; 654 | DYLIB_CURRENT_VERSION = 1; 655 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 656 | INFOPLIST_FILE = Drawer/Info.plist; 657 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 658 | LD_RUNPATH_SEARCH_PATHS = ( 659 | "$(inherited)", 660 | "@executable_path/Frameworks", 661 | "@loader_path/Frameworks", 662 | ); 663 | MARKETING_VERSION = 1.0.4; 664 | PRODUCT_BUNDLE_IDENTIFIER = dk.nodes.Drawer; 665 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 666 | SKIP_INSTALL = YES; 667 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 668 | SWIFT_VERSION = 5.0; 669 | TARGETED_DEVICE_FAMILY = "1,2"; 670 | }; 671 | name = Debug; 672 | }; 673 | 42321C052226973500CE1DCB /* Release */ = { 674 | isa = XCBuildConfiguration; 675 | buildSettings = { 676 | CLANG_ENABLE_MODULES = YES; 677 | CODE_SIGN_IDENTITY = ""; 678 | CODE_SIGN_STYLE = Automatic; 679 | DEFINES_MODULE = YES; 680 | DEVELOPMENT_TEAM = ""; 681 | DYLIB_COMPATIBILITY_VERSION = 1; 682 | DYLIB_CURRENT_VERSION = 1; 683 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 684 | INFOPLIST_FILE = Drawer/Info.plist; 685 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 686 | LD_RUNPATH_SEARCH_PATHS = ( 687 | "$(inherited)", 688 | "@executable_path/Frameworks", 689 | "@loader_path/Frameworks", 690 | ); 691 | MARKETING_VERSION = 1.0.4; 692 | PRODUCT_BUNDLE_IDENTIFIER = dk.nodes.Drawer; 693 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 694 | SKIP_INSTALL = YES; 695 | SWIFT_VERSION = 5.0; 696 | TARGETED_DEVICE_FAMILY = "1,2"; 697 | }; 698 | name = Release; 699 | }; 700 | 42321C072226973500CE1DCB /* Debug */ = { 701 | isa = XCBuildConfiguration; 702 | buildSettings = { 703 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 704 | CODE_SIGN_STYLE = Automatic; 705 | DEVELOPMENT_TEAM = MC6WZHXVVH; 706 | INFOPLIST_FILE = DrawerTests/Info.plist; 707 | LD_RUNPATH_SEARCH_PATHS = ( 708 | "$(inherited)", 709 | "@executable_path/Frameworks", 710 | "@loader_path/Frameworks", 711 | ); 712 | PRODUCT_BUNDLE_IDENTIFIER = dk.nodes.DrawerTests; 713 | PRODUCT_NAME = "$(TARGET_NAME)"; 714 | SWIFT_VERSION = 4.2; 715 | TARGETED_DEVICE_FAMILY = "1,2"; 716 | }; 717 | name = Debug; 718 | }; 719 | 42321C082226973500CE1DCB /* Release */ = { 720 | isa = XCBuildConfiguration; 721 | buildSettings = { 722 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 723 | CODE_SIGN_STYLE = Automatic; 724 | DEVELOPMENT_TEAM = MC6WZHXVVH; 725 | INFOPLIST_FILE = DrawerTests/Info.plist; 726 | LD_RUNPATH_SEARCH_PATHS = ( 727 | "$(inherited)", 728 | "@executable_path/Frameworks", 729 | "@loader_path/Frameworks", 730 | ); 731 | PRODUCT_BUNDLE_IDENTIFIER = dk.nodes.DrawerTests; 732 | PRODUCT_NAME = "$(TARGET_NAME)"; 733 | SWIFT_VERSION = 4.2; 734 | TARGETED_DEVICE_FAMILY = "1,2"; 735 | }; 736 | name = Release; 737 | }; 738 | 42321CAF2226BD1100CE1DCB /* Debug */ = { 739 | isa = XCBuildConfiguration; 740 | buildSettings = { 741 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 742 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 743 | CODE_SIGN_STYLE = Automatic; 744 | DEVELOPMENT_TEAM = M92A6H7EPZ; 745 | INFOPLIST_FILE = Example/Info.plist; 746 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 747 | LD_RUNPATH_SEARCH_PATHS = ( 748 | "$(inherited)", 749 | "@executable_path/Frameworks", 750 | ); 751 | PRODUCT_BUNDLE_IDENTIFIER = dk.nodes.Example; 752 | PRODUCT_NAME = "$(TARGET_NAME)"; 753 | SWIFT_VERSION = 5.0; 754 | TARGETED_DEVICE_FAMILY = "1,2"; 755 | }; 756 | name = Debug; 757 | }; 758 | 42321CB02226BD1100CE1DCB /* Release */ = { 759 | isa = XCBuildConfiguration; 760 | buildSettings = { 761 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 762 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 763 | CODE_SIGN_STYLE = Automatic; 764 | DEVELOPMENT_TEAM = M92A6H7EPZ; 765 | INFOPLIST_FILE = Example/Info.plist; 766 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 767 | LD_RUNPATH_SEARCH_PATHS = ( 768 | "$(inherited)", 769 | "@executable_path/Frameworks", 770 | ); 771 | PRODUCT_BUNDLE_IDENTIFIER = dk.nodes.Example; 772 | PRODUCT_NAME = "$(TARGET_NAME)"; 773 | SWIFT_VERSION = 5.0; 774 | TARGETED_DEVICE_FAMILY = "1,2"; 775 | }; 776 | name = Release; 777 | }; 778 | 42321CB12226BD1100CE1DCB /* Debug */ = { 779 | isa = XCBuildConfiguration; 780 | buildSettings = { 781 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 782 | BUNDLE_LOADER = "$(TEST_HOST)"; 783 | CODE_SIGN_STYLE = Automatic; 784 | DEVELOPMENT_TEAM = MC6WZHXVVH; 785 | INFOPLIST_FILE = ExampleTests/Info.plist; 786 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 787 | LD_RUNPATH_SEARCH_PATHS = ( 788 | "$(inherited)", 789 | "@executable_path/Frameworks", 790 | "@loader_path/Frameworks", 791 | ); 792 | PRODUCT_BUNDLE_IDENTIFIER = dk.nodes.ExampleTests; 793 | PRODUCT_NAME = "$(TARGET_NAME)"; 794 | SWIFT_VERSION = 5.0; 795 | TARGETED_DEVICE_FAMILY = "1,2"; 796 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; 797 | }; 798 | name = Debug; 799 | }; 800 | 42321CB22226BD1100CE1DCB /* Release */ = { 801 | isa = XCBuildConfiguration; 802 | buildSettings = { 803 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 804 | BUNDLE_LOADER = "$(TEST_HOST)"; 805 | CODE_SIGN_STYLE = Automatic; 806 | DEVELOPMENT_TEAM = MC6WZHXVVH; 807 | INFOPLIST_FILE = ExampleTests/Info.plist; 808 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 809 | LD_RUNPATH_SEARCH_PATHS = ( 810 | "$(inherited)", 811 | "@executable_path/Frameworks", 812 | "@loader_path/Frameworks", 813 | ); 814 | PRODUCT_BUNDLE_IDENTIFIER = dk.nodes.ExampleTests; 815 | PRODUCT_NAME = "$(TARGET_NAME)"; 816 | SWIFT_VERSION = 5.0; 817 | TARGETED_DEVICE_FAMILY = "1,2"; 818 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; 819 | }; 820 | name = Release; 821 | }; 822 | /* End XCBuildConfiguration section */ 823 | 824 | /* Begin XCConfigurationList section */ 825 | 42321BE92226973500CE1DCB /* Build configuration list for PBXProject "Drawer" */ = { 826 | isa = XCConfigurationList; 827 | buildConfigurations = ( 828 | 42321C012226973500CE1DCB /* Debug */, 829 | 42321C022226973500CE1DCB /* Release */, 830 | ); 831 | defaultConfigurationIsVisible = 0; 832 | defaultConfigurationName = Release; 833 | }; 834 | 42321C032226973500CE1DCB /* Build configuration list for PBXNativeTarget "Drawer" */ = { 835 | isa = XCConfigurationList; 836 | buildConfigurations = ( 837 | 42321C042226973500CE1DCB /* Debug */, 838 | 42321C052226973500CE1DCB /* Release */, 839 | ); 840 | defaultConfigurationIsVisible = 0; 841 | defaultConfigurationName = Release; 842 | }; 843 | 42321C062226973500CE1DCB /* Build configuration list for PBXNativeTarget "DrawerTests" */ = { 844 | isa = XCConfigurationList; 845 | buildConfigurations = ( 846 | 42321C072226973500CE1DCB /* Debug */, 847 | 42321C082226973500CE1DCB /* Release */, 848 | ); 849 | defaultConfigurationIsVisible = 0; 850 | defaultConfigurationName = Release; 851 | }; 852 | 42321CB32226BD1100CE1DCB /* Build configuration list for PBXNativeTarget "Example" */ = { 853 | isa = XCConfigurationList; 854 | buildConfigurations = ( 855 | 42321CAF2226BD1100CE1DCB /* Debug */, 856 | 42321CB02226BD1100CE1DCB /* Release */, 857 | ); 858 | defaultConfigurationIsVisible = 0; 859 | defaultConfigurationName = Release; 860 | }; 861 | 42321CB42226BD1100CE1DCB /* Build configuration list for PBXNativeTarget "ExampleTests" */ = { 862 | isa = XCConfigurationList; 863 | buildConfigurations = ( 864 | 42321CB12226BD1100CE1DCB /* Debug */, 865 | 42321CB22226BD1100CE1DCB /* Release */, 866 | ); 867 | defaultConfigurationIsVisible = 0; 868 | defaultConfigurationName = Release; 869 | }; 870 | /* End XCConfigurationList section */ 871 | }; 872 | rootObject = 42321BE62226973500CE1DCB /* Project object */; 873 | } 874 | --------------------------------------------------------------------------------