├── Example
├── AppDelegate.swift
├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Base.lproj
│ └── LaunchScreen.storyboard
├── Info.plist
└── ViewController.swift
├── ImagePickerTrayController.xcodeproj
├── project.pbxproj
└── project.xcworkspace
│ └── contents.xcworkspacedata
├── ImagePickerTrayController
├── AnimationController.swift
├── Cells
│ ├── ActionCell.swift
│ ├── CameraCell.swift
│ ├── CameraOverlayView.swift
│ └── ImageCell.swift
├── Helpers
│ ├── UIEdgeInsetsExtensions.swift
│ └── UIImageExtensions.swift
├── ImagePickerAction.swift
├── ImagePickerTrayController.h
├── ImagePickerTrayController.swift
├── Images.xcassets
│ ├── ActionCell-Chevron.imageset
│ │ ├── ActionCell-Chevron@2x.png
│ │ ├── ActionCell-Chevron@3x.png
│ │ └── Contents.json
│ ├── CameraOverlayView-CameraFlip.imageset
│ │ ├── CameraOverlayView-CameraFlip@2x.png
│ │ ├── CameraOverlayView-CameraFlip@3x.png
│ │ └── Contents.json
│ ├── Contents.json
│ ├── ImageCell-Cloud.imageset
│ │ ├── Contents.json
│ │ ├── ImageCell-Cloud@2x.png
│ │ └── ImageCell-Cloud@3x.png
│ ├── ImageCell-Selected.imageset
│ │ ├── Contents.json
│ │ ├── ImageCell-Selected@2x.png
│ │ └── ImageCell-Selected@3x.png
│ ├── ImageCell-Shadow.imageset
│ │ ├── Contents.json
│ │ ├── ImageCell-Shadow@2x.png
│ │ └── ImageCell-Shadow@3x.png
│ ├── ImageCell-Unselected.imageset
│ │ ├── Contents.json
│ │ ├── ImageCell-Unselected@2x.png
│ │ └── ImageCell-Unselected@3x.png
│ ├── ImageCell-Video.imageset
│ │ ├── Contents.json
│ │ ├── ImageCell-Video@2x.png
│ │ └── ImageCell-Video@3x.png
│ ├── ImagePickerAction-Camera.imageset
│ │ ├── Contents.json
│ │ ├── ImagePickerAction-Camera@2x.png
│ │ └── ImagePickerAction-Camera@3x.png
│ └── ImagePickerAction-Library.imageset
│ │ ├── Contents.json
│ │ ├── ImagePickerAction-Library@2x.png
│ │ └── ImagePickerAction-Library@3x.png
├── Info.plist
└── TransitionController.swift
├── ImagePickerTrayControllerTests
├── ImagePickerTrayControllerTests.swift
└── Info.plist
├── LICENSE
├── README.md
└── Screenshots
└── Example.png
/Example/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Example
4 | //
5 | // Created by Laurin Brandner on 14.10.16.
6 | // Copyright © 2016 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow? = {
15 | let window = UIWindow(frame: UIScreen.main.bounds)
16 | window.backgroundColor = .white
17 |
18 | return window
19 | }()
20 |
21 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
22 | window?.rootViewController = UINavigationController(rootViewController: ViewController())
23 | window?.makeKeyAndVisible()
24 |
25 | return true
26 | }
27 |
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/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 | "info" : {
90 | "version" : 1,
91 | "author" : "xcode"
92 | }
93 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/Example/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | IPTC
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | NSCameraUsageDescription
26 | To take dem pictures
27 | NSPhotoLibraryUsageDescription
28 | This is just a demo you silly :)
29 | UILaunchStoryboardName
30 | LaunchScreen
31 | UIRequiredDeviceCapabilities
32 |
33 | armv7
34 |
35 | UISupportedInterfaceOrientations
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationLandscapeLeft
39 | UIInterfaceOrientationLandscapeRight
40 |
41 | UISupportedInterfaceOrientations~ipad
42 |
43 | UIInterfaceOrientationPortrait
44 | UIInterfaceOrientationPortraitUpsideDown
45 | UIInterfaceOrientationLandscapeLeft
46 | UIInterfaceOrientationLandscapeRight
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/Example/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Example
4 | //
5 | // Created by Laurin Brandner on 14.10.16.
6 | // Copyright © 2016 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import ImagePickerTrayController
11 |
12 | class ViewController: UIViewController {
13 |
14 | var rows: [Int] {
15 | return (0..<100).map { $0 }
16 | }
17 |
18 | fileprivate lazy var tableView: UITableView = {
19 | let tableView = UITableView()
20 | tableView.dataSource = self
21 | tableView.translatesAutoresizingMaskIntoConstraints = false
22 |
23 | return tableView
24 | }()
25 |
26 | // MARK: - View Lifecycle
27 |
28 | override func loadView() {
29 | super.loadView()
30 |
31 | view.addSubview(tableView)
32 | tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
33 | tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
34 | tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
35 | tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
36 | }
37 |
38 | override func viewDidLoad() {
39 | super.viewDidLoad()
40 |
41 | navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Images", style: .plain, target: self, action: #selector(toggleImagePickerTray(_:)))
42 |
43 | let cellClass = UITableViewCell.self
44 | tableView.register(cellClass, forCellReuseIdentifier: NSStringFromClass(cellClass))
45 |
46 | let center = NotificationCenter.default
47 | center.addObserver(self, selector: #selector(willShowImagePickerTray(notification:)), name: ImagePickerTrayWillShow, object: nil)
48 | center.addObserver(self, selector: #selector(willHideImagePickerTray(notification:)), name: ImagePickerTrayWillHide, object: nil)
49 | }
50 |
51 | // MARK: -
52 |
53 | @objc fileprivate func toggleImagePickerTray(_: UIBarButtonItem) {
54 | if presentedViewController != nil {
55 | hideImagePickerTray()
56 | }
57 | else {
58 | showImagePickerTray()
59 | }
60 | }
61 |
62 | fileprivate func showImagePickerTray() {
63 | let controller = ImagePickerTrayController()
64 | controller.add(action: .cameraAction { _ in
65 | print("Show Camera")
66 | })
67 | controller.add(action: .libraryAction { _ in
68 | print("Show Library")
69 | })
70 | present(controller, animated: true, completion: nil)
71 | }
72 |
73 | fileprivate func hideImagePickerTray() {
74 | dismiss(animated: true, completion: nil)
75 | }
76 |
77 | @objc fileprivate func willShowImagePickerTray(notification: Notification) {
78 | guard let userInfo = notification.userInfo,
79 | let frame = userInfo[ImagePickerTrayFrameUserInfoKey] as? CGRect else {
80 | return
81 | }
82 |
83 | let duration: TimeInterval = (userInfo[ImagePickerTrayAnimationDurationUserInfoKey] as? TimeInterval) ?? 0
84 | animateContentInset(inset: frame.height, duration: duration, curve: UIViewAnimationCurve(rawValue: 0)!)
85 | }
86 |
87 | @objc fileprivate func willHideImagePickerTray(notification: Notification) {
88 | guard let userInfo = notification.userInfo else {
89 | return
90 | }
91 |
92 | let duration: TimeInterval = (userInfo[ImagePickerTrayAnimationDurationUserInfoKey] as? TimeInterval) ?? 0
93 | animateContentInset(inset: 0, duration: duration, curve: UIViewAnimationCurve(rawValue: 0)!)
94 | }
95 |
96 | fileprivate func animateContentInset(inset bottomInset: CGFloat, duration: TimeInterval, curve: UIViewAnimationCurve) {
97 | var inset = tableView.contentInset
98 | inset.bottom = bottomInset
99 |
100 | var offset = tableView.contentOffset
101 | offset.y = max(0, offset.y - bottomInset)
102 |
103 | let options = UIViewAnimationOptions(rawValue: UInt(curve.rawValue) << 16)
104 | UIView.animate(withDuration: duration, delay: 0, options: options, animations: {
105 | self.tableView.contentInset = inset
106 | self.tableView.contentOffset = offset
107 | self.tableView.scrollIndicatorInsets = inset
108 | }, completion: nil)
109 | }
110 |
111 | }
112 |
113 | // MARK: - UITableViewDataSource
114 |
115 | extension ViewController: UITableViewDataSource {
116 |
117 | func numberOfSections(in tableView: UITableView) -> Int {
118 | return 1
119 | }
120 |
121 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
122 | return rows.count
123 | }
124 |
125 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
126 | let cell = tableView.dequeueReusableCell(withIdentifier: NSStringFromClass(UITableViewCell.self), for: indexPath)
127 | cell.textLabel?.text = String(rows[indexPath.row])
128 |
129 | return cell
130 | }
131 |
132 | }
133 |
--------------------------------------------------------------------------------
/ImagePickerTrayController.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 4909255A1DE4E600001027EC /* ActionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 490925571DE4E600001027EC /* ActionCell.swift */; };
11 | 4909255B1DE4E600001027EC /* CameraCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 490925581DE4E600001027EC /* CameraCell.swift */; };
12 | 4909255C1DE4E600001027EC /* ImageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 490925591DE4E600001027EC /* ImageCell.swift */; };
13 | 4909255E1DE4F089001027EC /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4909255D1DE4F089001027EC /* Images.xcassets */; };
14 | 491D5EB41DE451CD007EC01A /* ImagePickerAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491D5EB31DE451CD007EC01A /* ImagePickerAction.swift */; };
15 | 491E96B51EA2A62900603E56 /* AnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491E96B41EA2A62900603E56 /* AnimationController.swift */; };
16 | 492715FA1DE7835200A9DD5B /* UIImageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 492715F91DE7835200A9DD5B /* UIImageExtensions.swift */; };
17 | 4958FF381DEA33F7000E1201 /* CameraOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4958FF371DEA33F7000E1201 /* CameraOverlayView.swift */; };
18 | 4970E5261DB0DD400078ED23 /* ImagePickerTrayControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4970E5251DB0DD400078ED23 /* ImagePickerTrayControllerTests.swift */; };
19 | 4970E56D1DB0DE110078ED23 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4970E56C1DB0DE110078ED23 /* AppDelegate.swift */; };
20 | 4970E56F1DB0DE110078ED23 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4970E56E1DB0DE110078ED23 /* ViewController.swift */; };
21 | 4970E5741DB0DE110078ED23 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4970E5731DB0DE110078ED23 /* Assets.xcassets */; };
22 | 4970E5771DB0DE110078ED23 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4970E5751DB0DE110078ED23 /* LaunchScreen.storyboard */; };
23 | 4970E58C1DB0E0410078ED23 /* ImagePickerTrayController.h in Headers */ = {isa = PBXBuildFile; fileRef = 4970E58A1DB0E0410078ED23 /* ImagePickerTrayController.h */; settings = {ATTRIBUTES = (Public, ); }; };
24 | 4970E58F1DB0E0410078ED23 /* ImagePickerTrayController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4970E5881DB0E0410078ED23 /* ImagePickerTrayController.framework */; };
25 | 4970E5901DB0E0410078ED23 /* ImagePickerTrayController.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4970E5881DB0E0410078ED23 /* ImagePickerTrayController.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
26 | 4970E5961DB0E0550078ED23 /* ImagePickerTrayController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4970E5951DB0E0550078ED23 /* ImagePickerTrayController.swift */; };
27 | 49A9DB6C1EA4B84B008DD8FB /* TransitionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49A9DB6B1EA4B84B008DD8FB /* TransitionController.swift */; };
28 | 49FC9BD11DEB472900AC1AA8 /* UIEdgeInsetsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49FC9BD01DEB472900AC1AA8 /* UIEdgeInsetsExtensions.swift */; };
29 | /* End PBXBuildFile section */
30 |
31 | /* Begin PBXContainerItemProxy section */
32 | 4970E58D1DB0E0410078ED23 /* PBXContainerItemProxy */ = {
33 | isa = PBXContainerItemProxy;
34 | containerPortal = 4970E5051DB0DD400078ED23 /* Project object */;
35 | proxyType = 1;
36 | remoteGlobalIDString = 4970E5871DB0E0410078ED23;
37 | remoteInfo = ImagePickerTrayController;
38 | };
39 | /* End PBXContainerItemProxy section */
40 |
41 | /* Begin PBXCopyFilesBuildPhase section */
42 | 4970E5941DB0E0410078ED23 /* Embed Frameworks */ = {
43 | isa = PBXCopyFilesBuildPhase;
44 | buildActionMask = 2147483647;
45 | dstPath = "";
46 | dstSubfolderSpec = 10;
47 | files = (
48 | 4970E5901DB0E0410078ED23 /* ImagePickerTrayController.framework in Embed Frameworks */,
49 | );
50 | name = "Embed Frameworks";
51 | runOnlyForDeploymentPostprocessing = 0;
52 | };
53 | /* End PBXCopyFilesBuildPhase section */
54 |
55 | /* Begin PBXFileReference section */
56 | 490925571DE4E600001027EC /* ActionCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionCell.swift; sourceTree = ""; };
57 | 490925581DE4E600001027EC /* CameraCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraCell.swift; sourceTree = ""; };
58 | 490925591DE4E600001027EC /* ImageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCell.swift; sourceTree = ""; };
59 | 4909255D1DE4F089001027EC /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; };
60 | 491D5EB31DE451CD007EC01A /* ImagePickerAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagePickerAction.swift; sourceTree = ""; };
61 | 491E96B41EA2A62900603E56 /* AnimationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationController.swift; sourceTree = ""; };
62 | 492715F91DE7835200A9DD5B /* UIImageExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIImageExtensions.swift; sourceTree = ""; };
63 | 4958FF371DEA33F7000E1201 /* CameraOverlayView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraOverlayView.swift; sourceTree = ""; };
64 | 4970E5211DB0DD400078ED23 /* ImagePickerTrayControllerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ImagePickerTrayControllerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
65 | 4970E5251DB0DD400078ED23 /* ImagePickerTrayControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePickerTrayControllerTests.swift; sourceTree = ""; };
66 | 4970E5271DB0DD400078ED23 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
67 | 4970E56A1DB0DE110078ED23 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
68 | 4970E56C1DB0DE110078ED23 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
69 | 4970E56E1DB0DE110078ED23 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
70 | 4970E5731DB0DE110078ED23 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
71 | 4970E5761DB0DE110078ED23 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
72 | 4970E5781DB0DE110078ED23 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
73 | 4970E5881DB0E0410078ED23 /* ImagePickerTrayController.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ImagePickerTrayController.framework; sourceTree = BUILT_PRODUCTS_DIR; };
74 | 4970E58A1DB0E0410078ED23 /* ImagePickerTrayController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ImagePickerTrayController.h; sourceTree = ""; };
75 | 4970E58B1DB0E0410078ED23 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
76 | 4970E5951DB0E0550078ED23 /* ImagePickerTrayController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagePickerTrayController.swift; sourceTree = ""; };
77 | 49A9DB6B1EA4B84B008DD8FB /* TransitionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransitionController.swift; sourceTree = ""; };
78 | 49FC9BD01DEB472900AC1AA8 /* UIEdgeInsetsExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIEdgeInsetsExtensions.swift; sourceTree = ""; };
79 | /* End PBXFileReference section */
80 |
81 | /* Begin PBXFrameworksBuildPhase section */
82 | 4970E51E1DB0DD400078ED23 /* Frameworks */ = {
83 | isa = PBXFrameworksBuildPhase;
84 | buildActionMask = 2147483647;
85 | files = (
86 | );
87 | runOnlyForDeploymentPostprocessing = 0;
88 | };
89 | 4970E5671DB0DE110078ED23 /* Frameworks */ = {
90 | isa = PBXFrameworksBuildPhase;
91 | buildActionMask = 2147483647;
92 | files = (
93 | 4970E58F1DB0E0410078ED23 /* ImagePickerTrayController.framework in Frameworks */,
94 | );
95 | runOnlyForDeploymentPostprocessing = 0;
96 | };
97 | 4970E5841DB0E0410078ED23 /* Frameworks */ = {
98 | isa = PBXFrameworksBuildPhase;
99 | buildActionMask = 2147483647;
100 | files = (
101 | );
102 | runOnlyForDeploymentPostprocessing = 0;
103 | };
104 | /* End PBXFrameworksBuildPhase section */
105 |
106 | /* Begin PBXGroup section */
107 | 490925561DE4E600001027EC /* Cells */ = {
108 | isa = PBXGroup;
109 | children = (
110 | 490925571DE4E600001027EC /* ActionCell.swift */,
111 | 490925581DE4E600001027EC /* CameraCell.swift */,
112 | 4958FF371DEA33F7000E1201 /* CameraOverlayView.swift */,
113 | 490925591DE4E600001027EC /* ImageCell.swift */,
114 | );
115 | path = Cells;
116 | sourceTree = "";
117 | };
118 | 492715F81DE7835200A9DD5B /* Helpers */ = {
119 | isa = PBXGroup;
120 | children = (
121 | 492715F91DE7835200A9DD5B /* UIImageExtensions.swift */,
122 | 49FC9BD01DEB472900AC1AA8 /* UIEdgeInsetsExtensions.swift */,
123 | );
124 | path = Helpers;
125 | sourceTree = "";
126 | };
127 | 4970E5041DB0DD400078ED23 = {
128 | isa = PBXGroup;
129 | children = (
130 | 4970E5891DB0E0410078ED23 /* ImagePickerTrayController */,
131 | 4970E5241DB0DD400078ED23 /* ImagePickerTrayControllerTests */,
132 | 4970E56B1DB0DE110078ED23 /* Example */,
133 | 4970E50E1DB0DD400078ED23 /* Products */,
134 | );
135 | sourceTree = "";
136 | };
137 | 4970E50E1DB0DD400078ED23 /* Products */ = {
138 | isa = PBXGroup;
139 | children = (
140 | 4970E5211DB0DD400078ED23 /* ImagePickerTrayControllerTests.xctest */,
141 | 4970E56A1DB0DE110078ED23 /* Example.app */,
142 | 4970E5881DB0E0410078ED23 /* ImagePickerTrayController.framework */,
143 | );
144 | name = Products;
145 | sourceTree = "";
146 | };
147 | 4970E5241DB0DD400078ED23 /* ImagePickerTrayControllerTests */ = {
148 | isa = PBXGroup;
149 | children = (
150 | 4970E5251DB0DD400078ED23 /* ImagePickerTrayControllerTests.swift */,
151 | 4970E5271DB0DD400078ED23 /* Info.plist */,
152 | );
153 | path = ImagePickerTrayControllerTests;
154 | sourceTree = "";
155 | };
156 | 4970E56B1DB0DE110078ED23 /* Example */ = {
157 | isa = PBXGroup;
158 | children = (
159 | 4970E56C1DB0DE110078ED23 /* AppDelegate.swift */,
160 | 4970E56E1DB0DE110078ED23 /* ViewController.swift */,
161 | 4970E57C1DB0DE210078ED23 /* Supporting Files */,
162 | );
163 | path = Example;
164 | sourceTree = "";
165 | };
166 | 4970E57C1DB0DE210078ED23 /* Supporting Files */ = {
167 | isa = PBXGroup;
168 | children = (
169 | 4970E5731DB0DE110078ED23 /* Assets.xcassets */,
170 | 4970E5751DB0DE110078ED23 /* LaunchScreen.storyboard */,
171 | 4970E5781DB0DE110078ED23 /* Info.plist */,
172 | );
173 | name = "Supporting Files";
174 | sourceTree = "";
175 | };
176 | 4970E5891DB0E0410078ED23 /* ImagePickerTrayController */ = {
177 | isa = PBXGroup;
178 | children = (
179 | 491E96B41EA2A62900603E56 /* AnimationController.swift */,
180 | 49A9DB6B1EA4B84B008DD8FB /* TransitionController.swift */,
181 | 491D5EB31DE451CD007EC01A /* ImagePickerAction.swift */,
182 | 4970E5951DB0E0550078ED23 /* ImagePickerTrayController.swift */,
183 | 490925561DE4E600001027EC /* Cells */,
184 | 492715F81DE7835200A9DD5B /* Helpers */,
185 | 4909255D1DE4F089001027EC /* Images.xcassets */,
186 | 4970E5971DB0E0620078ED23 /* Supporting Files */,
187 | );
188 | path = ImagePickerTrayController;
189 | sourceTree = "";
190 | };
191 | 4970E5971DB0E0620078ED23 /* Supporting Files */ = {
192 | isa = PBXGroup;
193 | children = (
194 | 4970E58A1DB0E0410078ED23 /* ImagePickerTrayController.h */,
195 | 4970E58B1DB0E0410078ED23 /* Info.plist */,
196 | );
197 | name = "Supporting Files";
198 | sourceTree = "";
199 | };
200 | /* End PBXGroup section */
201 |
202 | /* Begin PBXHeadersBuildPhase section */
203 | 4970E5851DB0E0410078ED23 /* Headers */ = {
204 | isa = PBXHeadersBuildPhase;
205 | buildActionMask = 2147483647;
206 | files = (
207 | 4970E58C1DB0E0410078ED23 /* ImagePickerTrayController.h in Headers */,
208 | );
209 | runOnlyForDeploymentPostprocessing = 0;
210 | };
211 | /* End PBXHeadersBuildPhase section */
212 |
213 | /* Begin PBXNativeTarget section */
214 | 4970E5201DB0DD400078ED23 /* ImagePickerTrayControllerTests */ = {
215 | isa = PBXNativeTarget;
216 | buildConfigurationList = 4970E52D1DB0DD400078ED23 /* Build configuration list for PBXNativeTarget "ImagePickerTrayControllerTests" */;
217 | buildPhases = (
218 | 4970E51D1DB0DD400078ED23 /* Sources */,
219 | 4970E51E1DB0DD400078ED23 /* Frameworks */,
220 | 4970E51F1DB0DD400078ED23 /* Resources */,
221 | );
222 | buildRules = (
223 | );
224 | dependencies = (
225 | );
226 | name = ImagePickerTrayControllerTests;
227 | productName = ImagePickerTrayControllerTests;
228 | productReference = 4970E5211DB0DD400078ED23 /* ImagePickerTrayControllerTests.xctest */;
229 | productType = "com.apple.product-type.bundle.unit-test";
230 | };
231 | 4970E5691DB0DE110078ED23 /* Example */ = {
232 | isa = PBXNativeTarget;
233 | buildConfigurationList = 4970E5791DB0DE110078ED23 /* Build configuration list for PBXNativeTarget "Example" */;
234 | buildPhases = (
235 | 4970E5661DB0DE110078ED23 /* Sources */,
236 | 4970E5671DB0DE110078ED23 /* Frameworks */,
237 | 4970E5681DB0DE110078ED23 /* Resources */,
238 | 4970E5941DB0E0410078ED23 /* Embed Frameworks */,
239 | );
240 | buildRules = (
241 | );
242 | dependencies = (
243 | 4970E58E1DB0E0410078ED23 /* PBXTargetDependency */,
244 | );
245 | name = Example;
246 | productName = Example;
247 | productReference = 4970E56A1DB0DE110078ED23 /* Example.app */;
248 | productType = "com.apple.product-type.application";
249 | };
250 | 4970E5871DB0E0410078ED23 /* ImagePickerTrayController */ = {
251 | isa = PBXNativeTarget;
252 | buildConfigurationList = 4970E5911DB0E0410078ED23 /* Build configuration list for PBXNativeTarget "ImagePickerTrayController" */;
253 | buildPhases = (
254 | 4970E5831DB0E0410078ED23 /* Sources */,
255 | 4970E5841DB0E0410078ED23 /* Frameworks */,
256 | 4970E5851DB0E0410078ED23 /* Headers */,
257 | 4970E5861DB0E0410078ED23 /* Resources */,
258 | );
259 | buildRules = (
260 | );
261 | dependencies = (
262 | );
263 | name = ImagePickerTrayController;
264 | productName = ImagePickerTrayController;
265 | productReference = 4970E5881DB0E0410078ED23 /* ImagePickerTrayController.framework */;
266 | productType = "com.apple.product-type.framework";
267 | };
268 | /* End PBXNativeTarget section */
269 |
270 | /* Begin PBXProject section */
271 | 4970E5051DB0DD400078ED23 /* Project object */ = {
272 | isa = PBXProject;
273 | attributes = {
274 | LastSwiftUpdateCheck = 0800;
275 | LastUpgradeCheck = 0810;
276 | ORGANIZATIONNAME = "Laurin Brandner";
277 | TargetAttributes = {
278 | 4970E5201DB0DD400078ED23 = {
279 | CreatedOnToolsVersion = 8.0;
280 | ProvisioningStyle = Automatic;
281 | TestTargetID = 4970E50C1DB0DD400078ED23;
282 | };
283 | 4970E5691DB0DE110078ED23 = {
284 | CreatedOnToolsVersion = 8.0;
285 | DevelopmentTeam = 5953RHWYWT;
286 | ProvisioningStyle = Automatic;
287 | };
288 | 4970E5871DB0E0410078ED23 = {
289 | CreatedOnToolsVersion = 8.0;
290 | LastSwiftMigration = 0800;
291 | ProvisioningStyle = Automatic;
292 | };
293 | };
294 | };
295 | buildConfigurationList = 4970E5081DB0DD400078ED23 /* Build configuration list for PBXProject "ImagePickerTrayController" */;
296 | compatibilityVersion = "Xcode 3.2";
297 | developmentRegion = English;
298 | hasScannedForEncodings = 0;
299 | knownRegions = (
300 | en,
301 | Base,
302 | );
303 | mainGroup = 4970E5041DB0DD400078ED23;
304 | productRefGroup = 4970E50E1DB0DD400078ED23 /* Products */;
305 | projectDirPath = "";
306 | projectRoot = "";
307 | targets = (
308 | 4970E5871DB0E0410078ED23 /* ImagePickerTrayController */,
309 | 4970E5201DB0DD400078ED23 /* ImagePickerTrayControllerTests */,
310 | 4970E5691DB0DE110078ED23 /* Example */,
311 | );
312 | };
313 | /* End PBXProject section */
314 |
315 | /* Begin PBXResourcesBuildPhase section */
316 | 4970E51F1DB0DD400078ED23 /* Resources */ = {
317 | isa = PBXResourcesBuildPhase;
318 | buildActionMask = 2147483647;
319 | files = (
320 | );
321 | runOnlyForDeploymentPostprocessing = 0;
322 | };
323 | 4970E5681DB0DE110078ED23 /* Resources */ = {
324 | isa = PBXResourcesBuildPhase;
325 | buildActionMask = 2147483647;
326 | files = (
327 | 4970E5771DB0DE110078ED23 /* LaunchScreen.storyboard in Resources */,
328 | 4970E5741DB0DE110078ED23 /* Assets.xcassets in Resources */,
329 | );
330 | runOnlyForDeploymentPostprocessing = 0;
331 | };
332 | 4970E5861DB0E0410078ED23 /* Resources */ = {
333 | isa = PBXResourcesBuildPhase;
334 | buildActionMask = 2147483647;
335 | files = (
336 | 4909255E1DE4F089001027EC /* Images.xcassets in Resources */,
337 | );
338 | runOnlyForDeploymentPostprocessing = 0;
339 | };
340 | /* End PBXResourcesBuildPhase section */
341 |
342 | /* Begin PBXSourcesBuildPhase section */
343 | 4970E51D1DB0DD400078ED23 /* Sources */ = {
344 | isa = PBXSourcesBuildPhase;
345 | buildActionMask = 2147483647;
346 | files = (
347 | 4970E5261DB0DD400078ED23 /* ImagePickerTrayControllerTests.swift in Sources */,
348 | );
349 | runOnlyForDeploymentPostprocessing = 0;
350 | };
351 | 4970E5661DB0DE110078ED23 /* Sources */ = {
352 | isa = PBXSourcesBuildPhase;
353 | buildActionMask = 2147483647;
354 | files = (
355 | 4970E56F1DB0DE110078ED23 /* ViewController.swift in Sources */,
356 | 4970E56D1DB0DE110078ED23 /* AppDelegate.swift in Sources */,
357 | );
358 | runOnlyForDeploymentPostprocessing = 0;
359 | };
360 | 4970E5831DB0E0410078ED23 /* Sources */ = {
361 | isa = PBXSourcesBuildPhase;
362 | buildActionMask = 2147483647;
363 | files = (
364 | 4958FF381DEA33F7000E1201 /* CameraOverlayView.swift in Sources */,
365 | 4909255A1DE4E600001027EC /* ActionCell.swift in Sources */,
366 | 492715FA1DE7835200A9DD5B /* UIImageExtensions.swift in Sources */,
367 | 491D5EB41DE451CD007EC01A /* ImagePickerAction.swift in Sources */,
368 | 49A9DB6C1EA4B84B008DD8FB /* TransitionController.swift in Sources */,
369 | 4970E5961DB0E0550078ED23 /* ImagePickerTrayController.swift in Sources */,
370 | 4909255B1DE4E600001027EC /* CameraCell.swift in Sources */,
371 | 4909255C1DE4E600001027EC /* ImageCell.swift in Sources */,
372 | 491E96B51EA2A62900603E56 /* AnimationController.swift in Sources */,
373 | 49FC9BD11DEB472900AC1AA8 /* UIEdgeInsetsExtensions.swift in Sources */,
374 | );
375 | runOnlyForDeploymentPostprocessing = 0;
376 | };
377 | /* End PBXSourcesBuildPhase section */
378 |
379 | /* Begin PBXTargetDependency section */
380 | 4970E58E1DB0E0410078ED23 /* PBXTargetDependency */ = {
381 | isa = PBXTargetDependency;
382 | target = 4970E5871DB0E0410078ED23 /* ImagePickerTrayController */;
383 | targetProxy = 4970E58D1DB0E0410078ED23 /* PBXContainerItemProxy */;
384 | };
385 | /* End PBXTargetDependency section */
386 |
387 | /* Begin PBXVariantGroup section */
388 | 4970E5751DB0DE110078ED23 /* LaunchScreen.storyboard */ = {
389 | isa = PBXVariantGroup;
390 | children = (
391 | 4970E5761DB0DE110078ED23 /* Base */,
392 | );
393 | name = LaunchScreen.storyboard;
394 | sourceTree = "";
395 | };
396 | /* End PBXVariantGroup section */
397 |
398 | /* Begin XCBuildConfiguration section */
399 | 4970E5281DB0DD400078ED23 /* Debug */ = {
400 | isa = XCBuildConfiguration;
401 | buildSettings = {
402 | ALWAYS_SEARCH_USER_PATHS = NO;
403 | CLANG_ANALYZER_NONNULL = YES;
404 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
405 | CLANG_CXX_LIBRARY = "libc++";
406 | CLANG_ENABLE_MODULES = YES;
407 | CLANG_ENABLE_OBJC_ARC = YES;
408 | CLANG_WARN_BOOL_CONVERSION = YES;
409 | CLANG_WARN_CONSTANT_CONVERSION = YES;
410 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
411 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
412 | CLANG_WARN_EMPTY_BODY = YES;
413 | CLANG_WARN_ENUM_CONVERSION = YES;
414 | CLANG_WARN_INFINITE_RECURSION = YES;
415 | CLANG_WARN_INT_CONVERSION = YES;
416 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
417 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
418 | CLANG_WARN_SUSPICIOUS_MOVES = YES;
419 | CLANG_WARN_UNREACHABLE_CODE = YES;
420 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
421 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
422 | COPY_PHASE_STRIP = NO;
423 | DEBUG_INFORMATION_FORMAT = dwarf;
424 | ENABLE_STRICT_OBJC_MSGSEND = YES;
425 | ENABLE_TESTABILITY = YES;
426 | GCC_C_LANGUAGE_STANDARD = gnu99;
427 | GCC_DYNAMIC_NO_PIC = NO;
428 | GCC_NO_COMMON_BLOCKS = YES;
429 | GCC_OPTIMIZATION_LEVEL = 0;
430 | GCC_PREPROCESSOR_DEFINITIONS = (
431 | "DEBUG=1",
432 | "$(inherited)",
433 | );
434 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
435 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
436 | GCC_WARN_UNDECLARED_SELECTOR = YES;
437 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
438 | GCC_WARN_UNUSED_FUNCTION = YES;
439 | GCC_WARN_UNUSED_VARIABLE = YES;
440 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
441 | MTL_ENABLE_DEBUG_INFO = YES;
442 | ONLY_ACTIVE_ARCH = YES;
443 | SDKROOT = iphoneos;
444 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
445 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
446 | TARGETED_DEVICE_FAMILY = "1,2";
447 | };
448 | name = Debug;
449 | };
450 | 4970E5291DB0DD400078ED23 /* Release */ = {
451 | isa = XCBuildConfiguration;
452 | buildSettings = {
453 | ALWAYS_SEARCH_USER_PATHS = NO;
454 | CLANG_ANALYZER_NONNULL = YES;
455 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
456 | CLANG_CXX_LIBRARY = "libc++";
457 | CLANG_ENABLE_MODULES = YES;
458 | CLANG_ENABLE_OBJC_ARC = YES;
459 | CLANG_WARN_BOOL_CONVERSION = YES;
460 | CLANG_WARN_CONSTANT_CONVERSION = YES;
461 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
462 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
463 | CLANG_WARN_EMPTY_BODY = YES;
464 | CLANG_WARN_ENUM_CONVERSION = YES;
465 | CLANG_WARN_INFINITE_RECURSION = YES;
466 | CLANG_WARN_INT_CONVERSION = YES;
467 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
468 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
469 | CLANG_WARN_SUSPICIOUS_MOVES = YES;
470 | CLANG_WARN_UNREACHABLE_CODE = YES;
471 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
472 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
473 | COPY_PHASE_STRIP = NO;
474 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
475 | ENABLE_NS_ASSERTIONS = NO;
476 | ENABLE_STRICT_OBJC_MSGSEND = YES;
477 | GCC_C_LANGUAGE_STANDARD = gnu99;
478 | GCC_NO_COMMON_BLOCKS = YES;
479 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
480 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
481 | GCC_WARN_UNDECLARED_SELECTOR = YES;
482 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
483 | GCC_WARN_UNUSED_FUNCTION = YES;
484 | GCC_WARN_UNUSED_VARIABLE = YES;
485 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
486 | MTL_ENABLE_DEBUG_INFO = NO;
487 | SDKROOT = iphoneos;
488 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
489 | TARGETED_DEVICE_FAMILY = "1,2";
490 | VALIDATE_PRODUCT = YES;
491 | };
492 | name = Release;
493 | };
494 | 4970E52E1DB0DD400078ED23 /* Debug */ = {
495 | isa = XCBuildConfiguration;
496 | buildSettings = {
497 | BUNDLE_LOADER = "$(TEST_HOST)";
498 | INFOPLIST_FILE = ImagePickerTrayControllerTests/Info.plist;
499 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
500 | PRODUCT_BUNDLE_IDENTIFIER = ch.laurinbrandner.ImagePickerTrayControllerTests;
501 | PRODUCT_NAME = "$(TARGET_NAME)";
502 | SWIFT_VERSION = 3.0;
503 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ImagePickerTrayController.app/ImagePickerTrayController";
504 | };
505 | name = Debug;
506 | };
507 | 4970E52F1DB0DD400078ED23 /* Release */ = {
508 | isa = XCBuildConfiguration;
509 | buildSettings = {
510 | BUNDLE_LOADER = "$(TEST_HOST)";
511 | INFOPLIST_FILE = ImagePickerTrayControllerTests/Info.plist;
512 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
513 | PRODUCT_BUNDLE_IDENTIFIER = ch.laurinbrandner.ImagePickerTrayControllerTests;
514 | PRODUCT_NAME = "$(TARGET_NAME)";
515 | SWIFT_VERSION = 3.0;
516 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ImagePickerTrayController.app/ImagePickerTrayController";
517 | };
518 | name = Release;
519 | };
520 | 4970E57A1DB0DE110078ED23 /* Debug */ = {
521 | isa = XCBuildConfiguration;
522 | buildSettings = {
523 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
524 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
525 | DEVELOPMENT_TEAM = 5953RHWYWT;
526 | INFOPLIST_FILE = Example/Info.plist;
527 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
528 | PRODUCT_BUNDLE_IDENTIFIER = ch.laurinbrandner.Example;
529 | PRODUCT_NAME = "$(TARGET_NAME)";
530 | SWIFT_VERSION = 3.0;
531 | };
532 | name = Debug;
533 | };
534 | 4970E57B1DB0DE110078ED23 /* Release */ = {
535 | isa = XCBuildConfiguration;
536 | buildSettings = {
537 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
538 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
539 | DEVELOPMENT_TEAM = 5953RHWYWT;
540 | INFOPLIST_FILE = Example/Info.plist;
541 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
542 | PRODUCT_BUNDLE_IDENTIFIER = ch.laurinbrandner.Example;
543 | PRODUCT_NAME = "$(TARGET_NAME)";
544 | SWIFT_VERSION = 3.0;
545 | };
546 | name = Release;
547 | };
548 | 4970E5921DB0E0410078ED23 /* Debug */ = {
549 | isa = XCBuildConfiguration;
550 | buildSettings = {
551 | CLANG_ENABLE_MODULES = YES;
552 | CODE_SIGN_IDENTITY = "";
553 | CURRENT_PROJECT_VERSION = 1;
554 | DEFINES_MODULE = YES;
555 | DYLIB_COMPATIBILITY_VERSION = 1;
556 | DYLIB_CURRENT_VERSION = 1;
557 | DYLIB_INSTALL_NAME_BASE = "@rpath";
558 | INFOPLIST_FILE = ImagePickerTrayController/Info.plist;
559 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
560 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
561 | PRODUCT_BUNDLE_IDENTIFIER = ch.laurinbrandner.ImagePickerTrayController;
562 | PRODUCT_NAME = "$(TARGET_NAME)";
563 | SKIP_INSTALL = YES;
564 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
565 | SWIFT_VERSION = 3.0;
566 | VERSIONING_SYSTEM = "apple-generic";
567 | VERSION_INFO_PREFIX = "";
568 | };
569 | name = Debug;
570 | };
571 | 4970E5931DB0E0410078ED23 /* Release */ = {
572 | isa = XCBuildConfiguration;
573 | buildSettings = {
574 | CLANG_ENABLE_MODULES = YES;
575 | CODE_SIGN_IDENTITY = "";
576 | CURRENT_PROJECT_VERSION = 1;
577 | DEFINES_MODULE = YES;
578 | DYLIB_COMPATIBILITY_VERSION = 1;
579 | DYLIB_CURRENT_VERSION = 1;
580 | DYLIB_INSTALL_NAME_BASE = "@rpath";
581 | INFOPLIST_FILE = ImagePickerTrayController/Info.plist;
582 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
583 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
584 | PRODUCT_BUNDLE_IDENTIFIER = ch.laurinbrandner.ImagePickerTrayController;
585 | PRODUCT_NAME = "$(TARGET_NAME)";
586 | SKIP_INSTALL = YES;
587 | SWIFT_VERSION = 3.0;
588 | VERSIONING_SYSTEM = "apple-generic";
589 | VERSION_INFO_PREFIX = "";
590 | };
591 | name = Release;
592 | };
593 | /* End XCBuildConfiguration section */
594 |
595 | /* Begin XCConfigurationList section */
596 | 4970E5081DB0DD400078ED23 /* Build configuration list for PBXProject "ImagePickerTrayController" */ = {
597 | isa = XCConfigurationList;
598 | buildConfigurations = (
599 | 4970E5281DB0DD400078ED23 /* Debug */,
600 | 4970E5291DB0DD400078ED23 /* Release */,
601 | );
602 | defaultConfigurationIsVisible = 0;
603 | defaultConfigurationName = Release;
604 | };
605 | 4970E52D1DB0DD400078ED23 /* Build configuration list for PBXNativeTarget "ImagePickerTrayControllerTests" */ = {
606 | isa = XCConfigurationList;
607 | buildConfigurations = (
608 | 4970E52E1DB0DD400078ED23 /* Debug */,
609 | 4970E52F1DB0DD400078ED23 /* Release */,
610 | );
611 | defaultConfigurationIsVisible = 0;
612 | defaultConfigurationName = Release;
613 | };
614 | 4970E5791DB0DE110078ED23 /* Build configuration list for PBXNativeTarget "Example" */ = {
615 | isa = XCConfigurationList;
616 | buildConfigurations = (
617 | 4970E57A1DB0DE110078ED23 /* Debug */,
618 | 4970E57B1DB0DE110078ED23 /* Release */,
619 | );
620 | defaultConfigurationIsVisible = 0;
621 | defaultConfigurationName = Release;
622 | };
623 | 4970E5911DB0E0410078ED23 /* Build configuration list for PBXNativeTarget "ImagePickerTrayController" */ = {
624 | isa = XCConfigurationList;
625 | buildConfigurations = (
626 | 4970E5921DB0E0410078ED23 /* Debug */,
627 | 4970E5931DB0E0410078ED23 /* Release */,
628 | );
629 | defaultConfigurationIsVisible = 0;
630 | defaultConfigurationName = Release;
631 | };
632 | /* End XCConfigurationList section */
633 | };
634 | rootObject = 4970E5051DB0DD400078ED23 /* Project object */;
635 | }
636 |
--------------------------------------------------------------------------------
/ImagePickerTrayController.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ImagePickerTrayController/AnimationController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AnimationController.swift
3 | // ImagePickerTrayController
4 | //
5 | // Created by Laurin Brandner on 15.04.17.
6 | // Copyright © 2017 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class AnimationController: NSObject {
12 |
13 | enum Transition {
14 | case presentation(UIPanGestureRecognizer)
15 | case dismissal
16 | }
17 |
18 | fileprivate let transition: Transition
19 |
20 | init(transition: Transition) {
21 | self.transition = transition
22 | super.init()
23 | }
24 |
25 | }
26 |
27 | extension AnimationController: UIViewControllerAnimatedTransitioning {
28 |
29 | func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
30 | return 0.25
31 | }
32 |
33 | func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
34 | switch transition {
35 | case .presentation(let gestureRecognizer):
36 | present(with: gestureRecognizer, using: transitionContext)
37 | case .dismissal:
38 | dismiss(using: transitionContext)
39 | }
40 | }
41 |
42 | private func present(with gestureRecognizer: UIPanGestureRecognizer, using transitionContext: UIViewControllerContextTransitioning) {
43 | guard let to = transitionContext.viewController(forKey: .to) as? ImagePickerTrayController else {
44 | transitionContext.completeTransition(false)
45 | return
46 | }
47 |
48 | let container = transitionContext.containerView
49 | container.window?.addGestureRecognizer(gestureRecognizer)
50 |
51 | container.addSubview(to.view)
52 | container.frame = CGRect(x: 0, y: container.bounds.height-to.trayHeight, width: container.bounds.width, height: to.trayHeight)
53 | to.view.transform = CGAffineTransform(translationX: 0, y: to.trayHeight)
54 |
55 | let duration = transitionDuration(using: transitionContext)
56 | UIView.animate(withDuration: duration, delay: 0, options: .allowUserInteraction, animations: {
57 | to.view.transform = .identity
58 | }, completion: { _ in
59 | transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
60 | })
61 | }
62 |
63 | private func dismiss(using transitionContext: UIViewControllerContextTransitioning) {
64 | guard let from = transitionContext.viewController(forKey: .from) as? ImagePickerTrayController else {
65 | transitionContext.completeTransition(false)
66 | return
67 | }
68 |
69 | let duration = transitionDuration(using: transitionContext)
70 | UIView.animate(withDuration: duration, delay: 0, options: .allowUserInteraction, animations: {
71 | from.view.frame.origin.y += from.trayHeight
72 | }, completion: { _ in
73 | if !transitionContext.transitionWasCancelled {
74 | // from.view.removeFromSuperview()
75 | }
76 | transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
77 | })
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/ImagePickerTrayController/Cells/ActionCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ActionCell.swift
3 | // ImagePickerTrayController
4 | //
5 | // Created by Laurin Brandner on 22.11.16.
6 | // Copyright © 2016 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | let spacing = CGPoint(x: 26, y: 14)
12 | fileprivate let stackViewOffset: CGFloat = 6
13 |
14 | class ActionCell: UICollectionViewCell {
15 |
16 | fileprivate let stackView: UIStackView = {
17 | let stackView = UIStackView()
18 | stackView.axis = .vertical
19 | stackView.distribution = .fillEqually
20 | stackView.spacing = spacing.x/2
21 |
22 | return stackView
23 | }()
24 |
25 | fileprivate let chevronImageView: UIImageView = {
26 | let bundle = Bundle(for: ImagePickerTrayController.self)
27 | let image = UIImage(named: "ActionCell-Chevron", in: bundle, compatibleWith: nil)
28 | let imageView = UIImageView(image: image)
29 | imageView.alpha = 0.5
30 |
31 | return imageView
32 | }()
33 |
34 | var actions = [ImagePickerAction]() {
35 | // It is sufficient to compare the length of the array
36 | // as actions can only be added but not removed
37 |
38 | willSet {
39 | if newValue.count != actions.count {
40 | stackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
41 | }
42 | }
43 | didSet {
44 | if stackView.arrangedSubviews.count != actions.count {
45 | actions.map { ActionButton(action: $0, target: self, selector: #selector(callAction(sender:))) }
46 | .forEach { stackView.addArrangedSubview($0) }
47 | }
48 | }
49 | }
50 |
51 | var disclosureProcess: CGFloat = 0 {
52 | didSet {
53 | setNeedsLayout()
54 | }
55 | }
56 |
57 | // MARK: - Initialization
58 |
59 | override init(frame: CGRect) {
60 | super.init(frame: frame)
61 |
62 | initialize()
63 | }
64 |
65 | required init?(coder aDecoder: NSCoder) {
66 | super.init(coder: aDecoder)
67 |
68 | initialize()
69 | }
70 |
71 | fileprivate func initialize() {
72 | contentView.addSubview(stackView)
73 | contentView.addSubview(chevronImageView)
74 | }
75 |
76 | // MARK: - Layout
77 |
78 | override func layoutSubviews() {
79 | super.layoutSubviews()
80 |
81 | let progress = max(disclosureProcess, 0)
82 | stackView.frame = bounds.insetBy(dx: spacing.x, dy: spacing.y).offsetBy(dx: -progress * stackViewOffset, dy: 0)
83 |
84 | chevronImageView.alpha = progress/2
85 |
86 | let chevronOffset = (1-progress) * (spacing.x + stackViewOffset)
87 | let chevronCenterX = max(bounds.maxX - spacing.x/2 + chevronOffset, stackView.frame.maxX + spacing.x/2)
88 | chevronImageView.center = CGPoint(x: chevronCenterX, y: bounds.midY)
89 | }
90 |
91 | // MARK: -
92 |
93 | @objc fileprivate func callAction(sender: UIButton) {
94 | if let index = stackView.arrangedSubviews.index(of: sender) {
95 | actions[index].call()
96 | }
97 | }
98 |
99 | }
100 |
101 | fileprivate class ActionButton: UIButton {
102 |
103 | // MARK: - Initialization
104 |
105 | init(action: ImagePickerAction, target: Any, selector: Selector) {
106 | super.init(frame: .zero)
107 |
108 | setTitle(action.title, for: .normal)
109 | setTitleColor(.black, for: .normal)
110 | setImage(action.image.withRenderingMode(.alwaysTemplate), for: .normal)
111 |
112 | imageView?.tintColor = .black
113 | imageView?.contentMode = .bottom
114 |
115 | titleLabel?.textAlignment = .center
116 | titleLabel?.font = .systemFont(ofSize: 14)
117 |
118 | backgroundColor = .white
119 | layer.masksToBounds = true
120 | layer.cornerRadius = 11.0
121 | addTarget(target, action: selector, for: .touchUpInside)
122 | }
123 |
124 | required init?(coder aDecoder: NSCoder) {
125 | fatalError("init(coder:) has not been implemented")
126 | }
127 |
128 | // MARK: - Layout
129 |
130 | fileprivate override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
131 | return contentRect.divided(atDistance: contentRect.midX, from: .minYEdge).slice
132 | }
133 |
134 | fileprivate override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
135 | return contentRect.divided(atDistance: contentRect.midX, from: .minYEdge).remainder
136 | }
137 |
138 | }
139 |
--------------------------------------------------------------------------------
/ImagePickerTrayController/Cells/CameraCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CameraCell.swift
3 | // ImagePickerTrayController
4 | //
5 | // Created by Laurin Brandner on 15.10.16.
6 | // Copyright © 2016 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class CameraCell: UICollectionViewCell {
12 |
13 | var cameraView: UIView? {
14 | willSet {
15 | cameraView?.removeFromSuperview()
16 | }
17 | didSet {
18 | if let cameraView = cameraView {
19 | contentView.addSubview(cameraView)
20 | }
21 | }
22 | }
23 |
24 | var cameraOverlayView: UIView? {
25 | didSet {
26 | setNeedsLayout()
27 | }
28 | }
29 |
30 | // MARK: - Layout
31 |
32 | override func layoutSubviews() {
33 | super.layoutSubviews()
34 |
35 | cameraView?.frame = bounds
36 | cameraOverlayView?.frame = bounds
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/ImagePickerTrayController/Cells/CameraOverlayView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CameraOverlayView.swift
3 | // ImagePickerTrayController
4 | //
5 | // Created by Laurin Brandner on 26.11.16.
6 | // Copyright © 2016 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class CameraOverlayView: UIButton {
12 |
13 | let flipCameraButton: UIButton = {
14 | let button = FlipCameraButton()
15 | button.setImage(UIImage(bundledName: "CameraOverlayView-CameraFlip"), for: .normal)
16 |
17 | return button
18 | }()
19 |
20 | fileprivate let shutterButtonView = ShutterButtonView()
21 |
22 | override var isHighlighted: Bool {
23 | didSet {
24 | shutterButtonView.isHighlighted = isHighlighted
25 | }
26 | }
27 |
28 | // MARK: - Initialization
29 |
30 | override init(frame: CGRect) {
31 | super.init(frame: frame)
32 |
33 | initialize()
34 | }
35 |
36 | required init?(coder aDecoder: NSCoder) {
37 | super.init(coder: aDecoder)
38 |
39 | initialize()
40 | }
41 |
42 | fileprivate func initialize() {
43 | addSubview(flipCameraButton)
44 | addSubview(shutterButtonView)
45 | }
46 |
47 | // MARK: - Layout
48 |
49 | override func layoutSubviews() {
50 | super.layoutSubviews()
51 |
52 | let flipCameraButtonSize = CGSize(width: 44, height: 44)
53 | let flipCameraButtonOrigin = CGPoint(x: bounds.maxX - flipCameraButtonSize.width, y: bounds.minY)
54 | flipCameraButton.frame = CGRect(origin: flipCameraButtonOrigin, size: flipCameraButtonSize)
55 |
56 | let shutterButtonViewSize = CGSize(width: 29, height: 29)
57 | let shutterButtonViewOrigin = CGPoint(x: bounds.midX - shutterButtonViewSize.width/2, y: bounds.maxY - shutterButtonViewSize.height-4)
58 | shutterButtonView.frame = CGRect(origin: shutterButtonViewOrigin, size: shutterButtonViewSize)
59 | }
60 |
61 | }
62 |
63 | fileprivate class FlipCameraButton: UIButton {
64 |
65 | fileprivate override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
66 | let imageSize = super.imageRect(forContentRect: contentRect).size
67 | let imageOrigin = CGPoint(x: contentRect.maxX-imageSize.width-10, y: contentRect.minY+10)
68 |
69 | return CGRect(origin: imageOrigin, size: imageSize)
70 | }
71 |
72 | }
73 |
74 | fileprivate class ShutterButtonView: UIView {
75 |
76 | let bezelLayer: CALayer = {
77 | let layer = CALayer()
78 | layer.masksToBounds = true
79 | layer.borderColor = UIColor.white.cgColor
80 | layer.borderWidth = 2
81 |
82 | return layer
83 | }()
84 |
85 | let knobLayer: CALayer = {
86 | let layer = CALayer()
87 | layer.masksToBounds = true
88 | layer.backgroundColor = UIColor.white.cgColor
89 |
90 | return layer
91 | }()
92 |
93 | var isHighlighted = false {
94 | didSet {
95 | knobLayer.backgroundColor = (isHighlighted) ? UIColor.lightGray.cgColor : UIColor.white.cgColor
96 | }
97 | }
98 |
99 | // MARK: - Initialization
100 |
101 | override init(frame: CGRect) {
102 | super.init(frame: frame)
103 |
104 | initialize()
105 | }
106 |
107 | required init?(coder aDecoder: NSCoder) {
108 | super.init(coder: aDecoder)
109 |
110 | initialize()
111 | }
112 |
113 | fileprivate func initialize() {
114 | layer.addSublayer(bezelLayer)
115 | layer.addSublayer(knobLayer)
116 | }
117 |
118 | // MARK: - Layout
119 |
120 | override func layoutSubviews() {
121 | super.layoutSubviews()
122 |
123 | bezelLayer.frame = bounds
124 | bezelLayer.cornerRadius = bounds.width/2
125 |
126 | let knobInset = bezelLayer.borderWidth + 1.5
127 | knobLayer.frame = bounds.insetBy(dx: knobInset, dy: knobInset)
128 | knobLayer.cornerRadius = knobLayer.bounds.height/2
129 | }
130 |
131 | }
132 |
--------------------------------------------------------------------------------
/ImagePickerTrayController/Cells/ImageCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageCell.swift
3 | // ImagePickerTrayController
4 | //
5 | // Created by Laurin Brandner on 15.10.16.
6 | // Copyright © 2016 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ImageCell: UICollectionViewCell {
12 |
13 | let imageView: UIImageView = {
14 | let imageView = UIImageView()
15 | imageView.contentMode = .scaleAspectFill
16 | imageView.clipsToBounds = true
17 |
18 | return imageView
19 | }()
20 |
21 | fileprivate let shadowView = UIImageView(image: UIImage(bundledName: "ImageCell-Shadow"))
22 |
23 | fileprivate let videoIndicatorView = UIImageView(image: UIImage(bundledName: "ImageCell-Video"))
24 |
25 | fileprivate let cloudIndicatorView = UIImageView(image: UIImage(bundledName: "ImageCell-Cloud"))
26 |
27 | fileprivate let checkmarkView = UIImageView(image: UIImage(bundledName: "ImageCell-Selected"))
28 |
29 | var isVideo = false {
30 | didSet {
31 | reloadAccessoryViews()
32 | }
33 | }
34 |
35 | var isRemote = false {
36 | didSet {
37 | reloadAccessoryViews()
38 | }
39 | }
40 |
41 | override var isSelected: Bool {
42 | didSet {
43 | reloadCheckmarkView()
44 | }
45 | }
46 |
47 | // MARK: - Initialization
48 |
49 | override init(frame: CGRect) {
50 | super.init(frame: frame)
51 |
52 | initialize()
53 | }
54 |
55 | required init?(coder aDecoder: NSCoder) {
56 | super.init(coder: aDecoder)
57 |
58 | initialize()
59 | }
60 |
61 | fileprivate func initialize() {
62 | contentView.addSubview(imageView)
63 | contentView.addSubview(shadowView)
64 | contentView.addSubview(videoIndicatorView)
65 | contentView.addSubview(cloudIndicatorView)
66 | contentView.addSubview(checkmarkView)
67 |
68 | reloadAccessoryViews()
69 | reloadCheckmarkView()
70 | }
71 |
72 | // MARK: - Other Methods
73 |
74 | fileprivate func reloadAccessoryViews() {
75 | videoIndicatorView.isHidden = !isVideo
76 | cloudIndicatorView.isHidden = !isRemote
77 | shadowView.isHidden = videoIndicatorView.isHidden && cloudIndicatorView.isHidden
78 | }
79 |
80 | fileprivate func reloadCheckmarkView() {
81 | checkmarkView.isHidden = !isSelected
82 | }
83 |
84 | override func prepareForReuse() {
85 | super.prepareForReuse()
86 |
87 | imageView.image = nil
88 | isVideo = false
89 | isRemote = false
90 | }
91 |
92 | // MARK: - Layout
93 |
94 | override func layoutSubviews() {
95 | super.layoutSubviews()
96 |
97 | imageView.frame = bounds
98 | let inset: CGFloat = 8
99 |
100 | let shadowHeight = shadowView.image?.size.height ?? 0
101 | shadowView.frame = CGRect(origin: CGPoint(x: bounds.minX, y: bounds.maxY-shadowHeight), size: CGSize(width: bounds.width, height: shadowHeight))
102 |
103 | let videoIndicatorViewSize = videoIndicatorView.image?.size ?? .zero
104 | let videoIndicatorViewOrigin = CGPoint(x: bounds.minX + inset, y: bounds.maxY - inset - videoIndicatorViewSize.height)
105 | videoIndicatorView.frame = CGRect(origin: videoIndicatorViewOrigin, size: videoIndicatorViewSize)
106 |
107 | let cloudIndicatorViewSize = cloudIndicatorView.image?.size ?? .zero
108 | let cloudIndicatorViewOrigin = CGPoint(x: bounds.maxX - inset - cloudIndicatorViewSize.width, y: bounds.maxY - inset - cloudIndicatorViewSize.height)
109 | cloudIndicatorView.frame = CGRect(origin: cloudIndicatorViewOrigin, size: cloudIndicatorViewSize)
110 |
111 | let checkmarkSize = checkmarkView.frame.size
112 | checkmarkView.center = CGPoint(x: bounds.maxX-checkmarkSize.width/2-4, y: bounds.maxY-checkmarkSize.height/2-4)
113 | }
114 |
115 | }
116 |
--------------------------------------------------------------------------------
/ImagePickerTrayController/Helpers/UIEdgeInsetsExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIEdgeInsetsExtensions.swift
3 | // ImagePickerTrayController
4 | //
5 | // Created by Laurin Brandner on 27.11.16.
6 | // Copyright © 2016 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIEdgeInsets {
12 |
13 | var vertical: CGFloat {
14 | return top + bottom
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/ImagePickerTrayController/Helpers/UIImageExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImageExtensions.swift
3 | // ImagePickerTrayController
4 | //
5 | // Created by Laurin Brandner on 24.11.16.
6 | // Copyright © 2016 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIImage {
12 |
13 | convenience init?(bundledName name: String) {
14 | let bundle = Bundle(for: ImagePickerTrayController.self)
15 | self.init(named: name, in: bundle, compatibleWith:nil)
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/ImagePickerTrayController/ImagePickerAction.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImagePickerAction.swift
3 | // ImagePickerTrayController
4 | //
5 | // Created by Laurin Brandner on 22.11.16.
6 | // Copyright © 2016 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct ImagePickerAction {
12 |
13 | public typealias Callback = (ImagePickerAction) -> ()
14 |
15 | public var title: String
16 | public var image: UIImage
17 | public var callback: Callback
18 |
19 | public static func cameraAction(with callback: @escaping Callback) -> ImagePickerAction {
20 | let image = UIImage(bundledName: "ImagePickerAction-Camera")!
21 |
22 | return ImagePickerAction(title: NSLocalizedString("Camera", comment: "Image Picker Camera Action"), image: image, callback: callback)
23 | }
24 |
25 | public static func libraryAction(with callback: @escaping Callback) -> ImagePickerAction {
26 | let image = UIImage(bundledName: "ImagePickerAction-Library")!
27 |
28 | return ImagePickerAction(title: NSLocalizedString("Photo Library", comment: "Image Picker Photo Library Action"), image: image, callback: callback)
29 | }
30 |
31 | func call() {
32 | callback(self)
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/ImagePickerTrayController/ImagePickerTrayController.h:
--------------------------------------------------------------------------------
1 | //
2 | // ImagePickerTrayController.h
3 | // ImagePickerTrayController
4 | //
5 | // Created by Laurin Brandner on 14.10.16.
6 | // Copyright © 2016 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for ImagePickerTrayController.
12 | FOUNDATION_EXPORT double ImagePickerTrayControllerVersionNumber;
13 |
14 | //! Project version string for ImagePickerTrayController.
15 | FOUNDATION_EXPORT const unsigned char ImagePickerTrayControllerVersionString[];
16 |
--------------------------------------------------------------------------------
/ImagePickerTrayController/ImagePickerTrayController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImagePickerTrayController.swift
3 | // ImagePickerTrayController
4 | //
5 | // Created by Laurin Brandner on 14.10.16.
6 | // Copyright © 2016 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Photos
11 |
12 | fileprivate let itemSpacing: CGFloat = 1
13 |
14 | /// The media type an instance of ImagePickerSheetController can display
15 | public enum ImagePickerMediaType {
16 | case image
17 | case video
18 | case imageAndVideo
19 | }
20 |
21 | @objc public protocol ImagePickerTrayControllerDelegate {
22 |
23 | @objc optional func controller(_ controller: ImagePickerTrayController, willSelectAsset asset: PHAsset)
24 | @objc optional func controller(_ controller: ImagePickerTrayController, didSelectAsset asset: PHAsset)
25 |
26 | @objc optional func controller(_ controller: ImagePickerTrayController, willDeselectAsset asset: PHAsset)
27 | @objc optional func controller(_ controller: ImagePickerTrayController, didDeselectAsset asset: PHAsset)
28 |
29 | @objc optional func controller(_ controller: ImagePickerTrayController, didTakeImage image:UIImage)
30 |
31 | }
32 |
33 | public let ImagePickerTrayWillShow: Notification.Name = Notification.Name(rawValue: "ch.laurinbrandner.ImagePickerTrayWillShow")
34 | public let ImagePickerTrayDidShow: Notification.Name = Notification.Name(rawValue: "ch.laurinbrandner.ImagePickerTrayDidShow")
35 |
36 | public let ImagePickerTrayWillHide: Notification.Name = Notification.Name(rawValue: "ch.laurinbrandner.ImagePickerTrayWillHide")
37 | public let ImagePickerTrayDidHide: Notification.Name = Notification.Name(rawValue: "ch.laurinbrandner.ImagePickerTrayDidHide")
38 |
39 | public let ImagePickerTrayFrameUserInfoKey = "ImagePickerTrayFrame"
40 | public let ImagePickerTrayAnimationDurationUserInfoKey = "ImagePickerTrayAnimationDuration"
41 |
42 | fileprivate let animationDuration: TimeInterval = 0.2
43 |
44 | public class ImagePickerTrayController: UIViewController {
45 |
46 | fileprivate(set) lazy var collectionView: UICollectionView = {
47 | let layout = UICollectionViewFlowLayout()
48 | layout.scrollDirection = .horizontal
49 | layout.minimumInteritemSpacing = itemSpacing
50 | layout.minimumLineSpacing = itemSpacing
51 |
52 | let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
53 | collectionView.contentInset = UIEdgeInsets(top: 1, left: 0, bottom: 2, right: 1)
54 | collectionView.translatesAutoresizingMaskIntoConstraints = false
55 | collectionView.backgroundColor = UIColor(red: 209.0/255.0, green: 213.0/255.0, blue: 218.0/255.0, alpha: 1.0)
56 | collectionView.dataSource = self
57 | collectionView.delegate = self
58 | collectionView.showsHorizontalScrollIndicator = false
59 | collectionView.alwaysBounceHorizontal = true
60 |
61 | collectionView.register(ActionCell.self, forCellWithReuseIdentifier: NSStringFromClass(ActionCell.self))
62 | collectionView.register(CameraCell.self, forCellWithReuseIdentifier: NSStringFromClass(CameraCell.self))
63 | collectionView.register(ImageCell.self, forCellWithReuseIdentifier: NSStringFromClass(ImageCell.self))
64 |
65 | return collectionView
66 | }()
67 |
68 | fileprivate lazy var cameraController: UIImagePickerController = {
69 | let controller = UIImagePickerController()
70 | controller.delegate = self
71 | controller.sourceType = .camera
72 | controller.showsCameraControls = false
73 | controller.allowsEditing = false
74 | controller.cameraFlashMode = .off
75 |
76 | let view = CameraOverlayView()
77 | view.addTarget(self, action: #selector(takePicture), for: .touchUpInside)
78 | view.flipCameraButton.addTarget(self, action: #selector(flipCamera), for: .touchUpInside)
79 | controller.cameraOverlayView = view
80 |
81 | return controller
82 | }()
83 |
84 | fileprivate let imageManager = PHCachingImageManager()
85 | fileprivate var assets = [PHAsset]()
86 | fileprivate lazy var requestOptions: PHImageRequestOptions = {
87 | let options = PHImageRequestOptions()
88 | options.deliveryMode = .highQualityFormat
89 | options.resizeMode = .fast
90 |
91 | return options
92 | }()
93 |
94 |
95 | public var allowsMultipleSelection = true {
96 | didSet {
97 | if isViewLoaded {
98 | collectionView.allowsMultipleSelection = allowsMultipleSelection
99 | }
100 | }
101 | }
102 |
103 | fileprivate var imageSize: CGSize = .zero
104 | let trayHeight: CGFloat
105 |
106 | fileprivate let actionCellWidth: CGFloat = 162
107 | fileprivate weak var actionCell: ActionCell?
108 |
109 | public fileprivate(set) var actions = [ImagePickerAction]()
110 |
111 | fileprivate var sections: [Int] {
112 | let actionSection = (actions.count > 0) ? 1 : 0
113 | let cameraSection = UIImagePickerController.isSourceTypeAvailable(.camera) ? 1 : 0
114 | let assetSection = assets.count
115 |
116 | return [actionSection, cameraSection, assetSection]
117 | }
118 |
119 | public var delegate: ImagePickerTrayControllerDelegate?
120 |
121 | /// If set to `true` the tray can be dragged down in order to dismiss it
122 | /// Defaults to `true`
123 | public var allowsInteractivePresentation: Bool {
124 | get {
125 | return transitionController?.allowsInteractiveTransition ?? false
126 | }
127 | set {
128 | transitionController?.allowsInteractiveTransition = newValue
129 | }
130 | }
131 | private var transitionController: TransitionController?
132 |
133 | // MARK: - Initialization
134 |
135 | public init() {
136 | self.trayHeight = 216
137 |
138 | super.init(nibName: nil, bundle: nil)
139 |
140 | transitionController = TransitionController(trayController: self)
141 | modalPresentationStyle = .custom
142 | transitioningDelegate = transitionController
143 | }
144 |
145 | public required init?(coder aDecoder: NSCoder) {
146 | fatalError("init(coder:) has not been implemented")
147 | }
148 |
149 | // MARK: - View Lifecycle
150 |
151 | public override func loadView() {
152 | super.loadView()
153 |
154 | view.addSubview(collectionView)
155 | collectionView.heightAnchor.constraint(equalToConstant: trayHeight).isActive = true
156 | collectionView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
157 | collectionView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
158 | collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
159 | collectionView.allowsMultipleSelection = allowsMultipleSelection
160 |
161 | let numberOfRows = (UIDevice.current.userInterfaceIdiom == .pad) ? 3 : 2
162 | let totalItemSpacing = CGFloat(numberOfRows-1)*itemSpacing + collectionView.contentInset.vertical
163 | let side = round((self.trayHeight-totalItemSpacing)/CGFloat(numberOfRows))
164 | self.imageSize = CGSize(width: side, height: side)
165 | }
166 |
167 | public override func viewWillAppear(_ animated: Bool) {
168 | super.viewWillAppear(animated)
169 |
170 | fetchAssets()
171 | }
172 |
173 | public override func viewDidLayoutSubviews() {
174 | super.viewDidLayoutSubviews()
175 |
176 | collectionView.setContentOffset(CGPoint(x: actionCellWidth - spacing.x, y: 0), animated: false)
177 | reloadActionCellDisclosureProgress()
178 | }
179 |
180 | // MARK: - Action
181 |
182 | public func add(action: ImagePickerAction) {
183 | actions.append(action)
184 | }
185 |
186 | // MARK: - Images
187 |
188 | fileprivate func prepareAssets() {
189 | fetchAssets()
190 | }
191 |
192 | fileprivate func fetchAssets() {
193 | let options = PHFetchOptions()
194 | options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
195 | options.fetchLimit = 100
196 |
197 | let result = PHAsset.fetchAssets(with: options)
198 | result.enumerateObjects({ asset, index, stop in
199 | self.assets.append(asset)
200 | })
201 | }
202 |
203 | fileprivate func requestImage(for asset: PHAsset, completion: @escaping (_ image: UIImage?) -> ()) {
204 | requestOptions.isSynchronous = true
205 | let size = scale(imageSize: imageSize)
206 |
207 | // Workaround because PHImageManager.requestImageForAsset doesn't work for burst images
208 | if asset.representsBurst {
209 | imageManager.requestImageData(for: asset, options: requestOptions) { data, _, _, _ in
210 | let image = data.flatMap { UIImage(data: $0) }
211 | completion(image)
212 | }
213 | }
214 | else {
215 | imageManager.requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: requestOptions) { image, _ in
216 | completion(image)
217 | }
218 | }
219 | }
220 |
221 | fileprivate func prefetchImages(for asset: PHAsset) {
222 | let size = scale(imageSize: imageSize)
223 | imageManager.startCachingImages(for: [asset], targetSize: size, contentMode: .aspectFill, options: requestOptions)
224 | }
225 |
226 | fileprivate func scale(imageSize size: CGSize) -> CGSize {
227 | let scale = UIScreen.main.scale
228 | return CGSize(width: size.width * scale, height: size.height * scale)
229 | }
230 |
231 | // MARK: - Camera
232 |
233 | @objc fileprivate func flipCamera() {
234 | cameraController.cameraDevice = (cameraController.cameraDevice == .rear) ? .front : .rear
235 | }
236 |
237 | @objc fileprivate func takePicture() {
238 | cameraController.takePicture()
239 | }
240 |
241 | // MARK: -
242 |
243 | fileprivate func reloadActionCellDisclosureProgress() {
244 | if sections[0] > 0 {
245 | actionCell?.disclosureProcess = (collectionView.contentOffset.x / (actionCellWidth/2))
246 | }
247 | }
248 |
249 | fileprivate func post(name: Notification.Name, frame: CGRect, duration: TimeInterval?) {
250 | var userInfo: [AnyHashable: Any] = [ImagePickerTrayFrameUserInfoKey: frame]
251 | if let duration = duration {
252 | userInfo[ImagePickerTrayAnimationDurationUserInfoKey] = duration
253 | }
254 |
255 | NotificationCenter.default.post(name: name, object: self, userInfo: userInfo)
256 | }
257 |
258 | }
259 |
260 | // MARK: - UICollectionViewDataSource
261 |
262 | extension ImagePickerTrayController: UICollectionViewDataSource {
263 |
264 | public func numberOfSections(in collectionView: UICollectionView) -> Int {
265 | return sections.count
266 | }
267 |
268 | public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
269 | return sections[section]
270 | }
271 |
272 | public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
273 | switch indexPath.section {
274 | case 0:
275 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NSStringFromClass(ActionCell.self), for: indexPath) as! ActionCell
276 | cell.actions = actions
277 | actionCell = cell
278 | reloadActionCellDisclosureProgress()
279 |
280 | return cell
281 | case 1:
282 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NSStringFromClass(CameraCell.self), for: indexPath) as! CameraCell
283 | cell.cameraView = cameraController.view
284 | cell.cameraOverlayView = cameraController.cameraOverlayView
285 |
286 | return cell
287 | case 2:
288 | let asset = assets[indexPath.item]
289 |
290 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NSStringFromClass(ImageCell.self), for: indexPath) as! ImageCell
291 | cell.isVideo = (asset.mediaType == .video)
292 | cell.isRemote = (asset.sourceType != .typeUserLibrary)
293 | requestImage(for: asset) { cell.imageView.image = $0 }
294 |
295 | return cell
296 | default:
297 | fatalError("More than 3 sections is invalid.")
298 | }
299 | }
300 |
301 | }
302 |
303 | // MARK: - UICollectionViewDelegate
304 |
305 | extension ImagePickerTrayController: UICollectionViewDelegate {
306 |
307 | public func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
308 | guard indexPath.section == sections.count - 1 else {
309 | return false
310 | }
311 |
312 | delegate?.controller?(self, willSelectAsset: assets[indexPath.item])
313 |
314 | return true
315 | }
316 |
317 | public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
318 | delegate?.controller?(self, didSelectAsset: assets[indexPath.item])
319 | }
320 |
321 | public func collectionView(_ collectionView: UICollectionView, shouldDeselectItemAt indexPath: IndexPath) -> Bool {
322 | delegate?.controller?(self, willDeselectAsset: assets[indexPath.item])
323 |
324 | return true
325 | }
326 |
327 | public func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
328 | delegate?.controller?(self, didDeselectAsset: assets[indexPath.item])
329 | }
330 |
331 | }
332 |
333 | // MARK: - UICollectionViewDelegateFlowLayout
334 |
335 | extension ImagePickerTrayController: UICollectionViewDelegateFlowLayout {
336 |
337 | public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
338 | let maxItemHeight = collectionView.frame.height-collectionView.contentInset.vertical
339 |
340 | switch indexPath.section {
341 | case 0:
342 | return CGSize(width: actionCellWidth, height: maxItemHeight)
343 | case 1:
344 | return CGSize(width: 150, height: maxItemHeight)
345 | case 2:
346 | return imageSize
347 | default:
348 | return .zero
349 | }
350 | }
351 |
352 | public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
353 | guard section == 1 else {
354 | return UIEdgeInsets()
355 | }
356 |
357 | return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 6)
358 | }
359 |
360 | }
361 |
362 | // MARK: - UIScrollViewDelegate
363 |
364 | extension ImagePickerTrayController: UIScrollViewDelegate {
365 |
366 | public func scrollViewDidScroll(_ scrollView: UIScrollView) {
367 | reloadActionCellDisclosureProgress()
368 | }
369 | }
370 |
371 | // MARK: - UIImagePickerControllerDelegate
372 |
373 | extension ImagePickerTrayController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
374 |
375 | public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
376 | if let image = info[UIImagePickerControllerOriginalImage] as? UIImage {
377 | delegate?.controller?(self, didTakeImage: image)
378 | }
379 | }
380 |
381 | }
382 |
--------------------------------------------------------------------------------
/ImagePickerTrayController/Images.xcassets/ActionCell-Chevron.imageset/ActionCell-Chevron@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerTrayController/09a6e26d16c2633ff068cc0539b3fe388077f6e5/ImagePickerTrayController/Images.xcassets/ActionCell-Chevron.imageset/ActionCell-Chevron@2x.png
--------------------------------------------------------------------------------
/ImagePickerTrayController/Images.xcassets/ActionCell-Chevron.imageset/ActionCell-Chevron@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerTrayController/09a6e26d16c2633ff068cc0539b3fe388077f6e5/ImagePickerTrayController/Images.xcassets/ActionCell-Chevron.imageset/ActionCell-Chevron@3x.png
--------------------------------------------------------------------------------
/ImagePickerTrayController/Images.xcassets/ActionCell-Chevron.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "ActionCell-Chevron@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "filename" : "ActionCell-Chevron@3x.png",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/ImagePickerTrayController/Images.xcassets/CameraOverlayView-CameraFlip.imageset/CameraOverlayView-CameraFlip@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerTrayController/09a6e26d16c2633ff068cc0539b3fe388077f6e5/ImagePickerTrayController/Images.xcassets/CameraOverlayView-CameraFlip.imageset/CameraOverlayView-CameraFlip@2x.png
--------------------------------------------------------------------------------
/ImagePickerTrayController/Images.xcassets/CameraOverlayView-CameraFlip.imageset/CameraOverlayView-CameraFlip@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerTrayController/09a6e26d16c2633ff068cc0539b3fe388077f6e5/ImagePickerTrayController/Images.xcassets/CameraOverlayView-CameraFlip.imageset/CameraOverlayView-CameraFlip@3x.png
--------------------------------------------------------------------------------
/ImagePickerTrayController/Images.xcassets/CameraOverlayView-CameraFlip.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "CameraOverlayView-CameraFlip@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "filename" : "CameraOverlayView-CameraFlip@3x.png",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/ImagePickerTrayController/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/ImagePickerTrayController/Images.xcassets/ImageCell-Cloud.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "ImageCell-Cloud@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "filename" : "ImageCell-Cloud@3x.png",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/ImagePickerTrayController/Images.xcassets/ImageCell-Cloud.imageset/ImageCell-Cloud@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerTrayController/09a6e26d16c2633ff068cc0539b3fe388077f6e5/ImagePickerTrayController/Images.xcassets/ImageCell-Cloud.imageset/ImageCell-Cloud@2x.png
--------------------------------------------------------------------------------
/ImagePickerTrayController/Images.xcassets/ImageCell-Cloud.imageset/ImageCell-Cloud@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerTrayController/09a6e26d16c2633ff068cc0539b3fe388077f6e5/ImagePickerTrayController/Images.xcassets/ImageCell-Cloud.imageset/ImageCell-Cloud@3x.png
--------------------------------------------------------------------------------
/ImagePickerTrayController/Images.xcassets/ImageCell-Selected.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "ImageCell-Selected@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "filename" : "ImageCell-Selected@3x.png",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/ImagePickerTrayController/Images.xcassets/ImageCell-Selected.imageset/ImageCell-Selected@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerTrayController/09a6e26d16c2633ff068cc0539b3fe388077f6e5/ImagePickerTrayController/Images.xcassets/ImageCell-Selected.imageset/ImageCell-Selected@2x.png
--------------------------------------------------------------------------------
/ImagePickerTrayController/Images.xcassets/ImageCell-Selected.imageset/ImageCell-Selected@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerTrayController/09a6e26d16c2633ff068cc0539b3fe388077f6e5/ImagePickerTrayController/Images.xcassets/ImageCell-Selected.imageset/ImageCell-Selected@3x.png
--------------------------------------------------------------------------------
/ImagePickerTrayController/Images.xcassets/ImageCell-Shadow.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "ImageCell-Shadow@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "filename" : "ImageCell-Shadow@3x.png",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/ImagePickerTrayController/Images.xcassets/ImageCell-Shadow.imageset/ImageCell-Shadow@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerTrayController/09a6e26d16c2633ff068cc0539b3fe388077f6e5/ImagePickerTrayController/Images.xcassets/ImageCell-Shadow.imageset/ImageCell-Shadow@2x.png
--------------------------------------------------------------------------------
/ImagePickerTrayController/Images.xcassets/ImageCell-Shadow.imageset/ImageCell-Shadow@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerTrayController/09a6e26d16c2633ff068cc0539b3fe388077f6e5/ImagePickerTrayController/Images.xcassets/ImageCell-Shadow.imageset/ImageCell-Shadow@3x.png
--------------------------------------------------------------------------------
/ImagePickerTrayController/Images.xcassets/ImageCell-Unselected.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "ImageCell-Unselected@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "filename" : "ImageCell-Unselected@3x.png",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/ImagePickerTrayController/Images.xcassets/ImageCell-Unselected.imageset/ImageCell-Unselected@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerTrayController/09a6e26d16c2633ff068cc0539b3fe388077f6e5/ImagePickerTrayController/Images.xcassets/ImageCell-Unselected.imageset/ImageCell-Unselected@2x.png
--------------------------------------------------------------------------------
/ImagePickerTrayController/Images.xcassets/ImageCell-Unselected.imageset/ImageCell-Unselected@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerTrayController/09a6e26d16c2633ff068cc0539b3fe388077f6e5/ImagePickerTrayController/Images.xcassets/ImageCell-Unselected.imageset/ImageCell-Unselected@3x.png
--------------------------------------------------------------------------------
/ImagePickerTrayController/Images.xcassets/ImageCell-Video.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "ImageCell-Video@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "filename" : "ImageCell-Video@3x.png",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/ImagePickerTrayController/Images.xcassets/ImageCell-Video.imageset/ImageCell-Video@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerTrayController/09a6e26d16c2633ff068cc0539b3fe388077f6e5/ImagePickerTrayController/Images.xcassets/ImageCell-Video.imageset/ImageCell-Video@2x.png
--------------------------------------------------------------------------------
/ImagePickerTrayController/Images.xcassets/ImageCell-Video.imageset/ImageCell-Video@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerTrayController/09a6e26d16c2633ff068cc0539b3fe388077f6e5/ImagePickerTrayController/Images.xcassets/ImageCell-Video.imageset/ImageCell-Video@3x.png
--------------------------------------------------------------------------------
/ImagePickerTrayController/Images.xcassets/ImagePickerAction-Camera.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "ImagePickerAction-Camera@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "filename" : "ImagePickerAction-Camera@3x.png",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/ImagePickerTrayController/Images.xcassets/ImagePickerAction-Camera.imageset/ImagePickerAction-Camera@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerTrayController/09a6e26d16c2633ff068cc0539b3fe388077f6e5/ImagePickerTrayController/Images.xcassets/ImagePickerAction-Camera.imageset/ImagePickerAction-Camera@2x.png
--------------------------------------------------------------------------------
/ImagePickerTrayController/Images.xcassets/ImagePickerAction-Camera.imageset/ImagePickerAction-Camera@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerTrayController/09a6e26d16c2633ff068cc0539b3fe388077f6e5/ImagePickerTrayController/Images.xcassets/ImagePickerAction-Camera.imageset/ImagePickerAction-Camera@3x.png
--------------------------------------------------------------------------------
/ImagePickerTrayController/Images.xcassets/ImagePickerAction-Library.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "ImagePickerAction-Library@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "filename" : "ImagePickerAction-Library@3x.png",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/ImagePickerTrayController/Images.xcassets/ImagePickerAction-Library.imageset/ImagePickerAction-Library@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerTrayController/09a6e26d16c2633ff068cc0539b3fe388077f6e5/ImagePickerTrayController/Images.xcassets/ImagePickerAction-Library.imageset/ImagePickerAction-Library@2x.png
--------------------------------------------------------------------------------
/ImagePickerTrayController/Images.xcassets/ImagePickerAction-Library.imageset/ImagePickerAction-Library@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerTrayController/09a6e26d16c2633ff068cc0539b3fe388077f6e5/ImagePickerTrayController/Images.xcassets/ImagePickerAction-Library.imageset/ImagePickerAction-Library@3x.png
--------------------------------------------------------------------------------
/ImagePickerTrayController/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
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 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/ImagePickerTrayController/TransitionController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TransitionController.swift
3 | // ImagePickerTrayController
4 | //
5 | // Created by Laurin Brandner on 17.04.17.
6 | // Copyright © 2017 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class TransitionController: NSObject {
12 |
13 | fileprivate weak var trayController: ImagePickerTrayController?
14 |
15 | var allowsInteractiveTransition = true
16 |
17 | fileprivate let gestureRecognizer = UIPanGestureRecognizer()
18 | fileprivate var interactiveTransition: UIPercentDrivenInteractiveTransition?
19 | fileprivate var panDirection: CGFloat = 0
20 |
21 | init(trayController: ImagePickerTrayController) {
22 | self.trayController = trayController
23 | super.init()
24 |
25 | gestureRecognizer.addTarget(self, action: #selector(didRecognizePan(gestureRecognizer:)))
26 | gestureRecognizer.delegate = self
27 | }
28 |
29 | fileprivate func cancel() {
30 | interactiveTransition?.cancel()
31 | interactiveTransition = nil
32 | gestureRecognizer.cancel()
33 | }
34 |
35 | fileprivate func finish() {
36 | interactiveTransition?.finish()
37 | interactiveTransition = nil
38 | gestureRecognizer.cancel()
39 | }
40 |
41 | }
42 |
43 | // MARK: - UIViewControllerTransitioningDelegate
44 |
45 | extension TransitionController: UIViewControllerTransitioningDelegate {
46 |
47 | func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
48 | return AnimationController(transition: .presentation(gestureRecognizer))
49 | }
50 |
51 | func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
52 | return AnimationController(transition: .dismissal)
53 | }
54 |
55 | func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
56 | return interactiveTransition
57 | }
58 |
59 | }
60 |
61 | // MARK: - UIGestureRecognizerDelegate
62 |
63 | extension TransitionController: UIGestureRecognizerDelegate {
64 |
65 | func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
66 | return allowsInteractiveTransition
67 | }
68 |
69 | func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
70 | return true
71 | }
72 |
73 | @objc fileprivate func didRecognizePan(gestureRecognizer: UIPanGestureRecognizer) {
74 | guard let trayController = trayController,
75 | let view = gestureRecognizer.view else {
76 | cancel()
77 | return
78 | }
79 |
80 | if gestureRecognizer.state == .began {
81 | gestureRecognizer.setTranslation(.zero, in: gestureRecognizer.view)
82 | }
83 | else if gestureRecognizer.state == .changed {
84 | let end = gestureRecognizer.location(in: gestureRecognizer.view).y
85 | let translation = gestureRecognizer.translation(in: gestureRecognizer.view)
86 | let start = end - translation.y
87 | let threshold = view.frame.maxY - trayController.trayHeight
88 | if let transition = interactiveTransition {
89 | let progress = end-threshold
90 | transition.update(progress/trayController.trayHeight)
91 | }
92 | if start < threshold && end >= threshold && interactiveTransition == nil {
93 | interactiveTransition = UIPercentDrivenInteractiveTransition()
94 | trayController.dismiss(animated: true, completion: nil)
95 | }
96 | }
97 | else if gestureRecognizer.state == .cancelled {
98 | interactiveTransition?.completionSpeed = 0.95
99 | cancel()
100 | }
101 | else if gestureRecognizer.state == .ended {
102 | let velocity = gestureRecognizer.velocity(in: gestureRecognizer.view).y
103 |
104 | // This needs to be set, otherwise the cancel animation glitches from time to time
105 | // If you figure out how to fix this, let me know
106 | interactiveTransition?.completionSpeed = 0.95
107 |
108 | if velocity <= 0 {
109 | cancel()
110 | }
111 | else {
112 | finish()
113 | }
114 | }
115 | }
116 |
117 | }
118 |
119 | fileprivate extension UIPanGestureRecognizer {
120 |
121 | fileprivate func cancel() {
122 | isEnabled = false
123 | isEnabled = true
124 | }
125 |
126 | }
127 |
--------------------------------------------------------------------------------
/ImagePickerTrayControllerTests/ImagePickerTrayControllerTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImagePickerTrayControllerTests.swift
3 | // ImagePickerTrayControllerTests
4 | //
5 | // Created by Laurin Brandner on 14.10.16.
6 | // Copyright © 2016 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ImagePickerTrayController
11 |
12 | class ImagePickerTrayControllerTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testExample() {
25 | // This is an example of a functional test case.
26 | // Use XCTAssert and related functions to verify your tests produce the correct results.
27 | }
28 |
29 | func testPerformanceExample() {
30 | // This is an example of a performance test case.
31 | self.measure {
32 | // Put the code you want to measure the time of here.
33 | }
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/ImagePickerTrayControllerTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
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 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Laurin Brandner
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ImagePickerTrayController
2 |
3 | [](https://twitter.com/lbrndnr)
4 | [](https://github.com/lbrndnr/ImagePickerTrayController/blob/master/LICENSE)
5 |
6 | ## About
7 | ImagePickerTrayController is a component that replicates the custom photo action sheet in iMessage. It's the iOS 10 version of [ImagePickerSheetController](https://github.com/lbrndnr/ImagePickerSheetController).
8 | ⚠️Note that this library is still WIP⚠️
9 |
10 |
11 |
12 | ### Example
13 |
14 | ```swift
15 | let controller = ImagePickerTrayController()
16 | controller.add(action: .cameraAction { _ in
17 | print("Show Camera")
18 | })
19 | controller.add(action: .libraryAction { _ in
20 | print("Show Library")
21 | })
22 | controller.show(in: view)
23 | imagePickerTrayController = controller
24 | ```
25 |
26 | ## Installation
27 |
28 | I wouldn't recommend using this library just yet. It's still WIP⚠️.
29 |
30 | ## Requirements
31 | `ImagePickerTrayController` is written in Swift and links against `Photos.framework`. It therefore requires iOS 8 or later.
32 |
33 | ## Author
34 | I'm Laurin Brandner, I'm on [Twitter](https://twitter.com/lbrndnr).
35 |
36 | ## License
37 | `ImagePickerTrayController` is licensed under the [MIT License](http://opensource.org/licenses/mit-license.php).
38 |
--------------------------------------------------------------------------------
/Screenshots/Example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerTrayController/09a6e26d16c2633ff068cc0539b3fe388077f6e5/Screenshots/Example.png
--------------------------------------------------------------------------------