├── .gitignore
├── Cartfile.private
├── Cartfile.resolved
├── ImagePickerSheetController.podspec
├── ImagePickerSheetController
├── Example
│ ├── AppDelegate.swift
│ ├── Base.lproj
│ │ ├── LaunchScreen.xib
│ │ ├── Localizable.strings
│ │ └── Localizable.stringsdict
│ ├── Info.plist
│ └── ViewController.swift
├── ImagePickerSheetController.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ ├── ImagePickerSheetController.xccheckout
│ │ │ └── WorkspaceSettings.xcsettings
│ ├── xcshareddata
│ │ └── xcschemes
│ │ │ └── ImagePickerSheetController.xcscheme
│ └── xcuserdata
│ │ ├── Laurin.xcuserdatad
│ │ └── xcschemes
│ │ │ ├── Example.xcscheme
│ │ │ └── xcschememanagement.plist
│ │ └── patrickbalestra.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
├── ImagePickerSheetController
│ ├── AnimationController.swift
│ ├── ImagePickerAction.swift
│ ├── ImagePickerSheetController.h
│ ├── ImagePickerSheetController.swift
│ ├── Images.xcassets
│ │ ├── Contents.json
│ │ ├── PreviewCollectionViewCell-video.imageset
│ │ │ ├── Contents.json
│ │ │ ├── PreviewCollectionViewCell-video.png
│ │ │ ├── PreviewCollectionViewCell-video@2x.png
│ │ │ └── PreviewCollectionViewCell-video@3x.png
│ │ ├── PreviewSupplementaryView-Checkmark-Selected.imageset
│ │ │ ├── Contents.json
│ │ │ ├── PreviewSupplementaryView-Checkmark-Selected.png
│ │ │ ├── PreviewSupplementaryView-Checkmark-Selected@2x.png
│ │ │ └── PreviewSupplementaryView-Checkmark-Selected@3x.png
│ │ └── PreviewSupplementaryView-Checkmark.imageset
│ │ │ ├── Contents.json
│ │ │ ├── PreviewSupplementaryView-Checkmark.png
│ │ │ ├── PreviewSupplementaryView-Checkmark@2x.png
│ │ │ └── PreviewSupplementaryView-Checkmark@3x.png
│ ├── Info.plist
│ └── Sheet
│ │ ├── Preview
│ │ ├── PreviewCollectionView.swift
│ │ ├── PreviewCollectionViewCell.swift
│ │ ├── PreviewCollectionViewLayout.swift
│ │ └── PreviewSupplementaryView.swift
│ │ ├── SheetActionCollectionViewCell.swift
│ │ ├── SheetCollectionViewCell.swift
│ │ ├── SheetCollectionViewLayout.swift
│ │ ├── SheetController.swift
│ │ └── SheetPreviewCollectionViewCell.swift
└── ImagePickerSheetControllerTests
│ ├── ActionHandlingTests.swift
│ ├── AddingActionTests.swift
│ ├── DismissalTests.swift
│ ├── ImagePickerSheetControllerTests.swift
│ ├── ImageSelectionTests.swift
│ ├── Info.plist
│ ├── KIFExtensions.swift
│ └── PresentationTests.swift
├── LICENSE
├── README.md
└── Screenshots
├── GoT.gif
└── Nature.png
/.gitignore:
--------------------------------------------------------------------------------
1 | ImagePickerSheetController/ImagePickerSheetController.xcodeproj/project.xcworkspace/xcuserdata
2 | ImagePickerSheetController/ImagePickerSheetController.xcodeproj/xcuserdata/
3 |
--------------------------------------------------------------------------------
/Cartfile.private:
--------------------------------------------------------------------------------
1 | github "Quick/Nimble" "v2.0.0-rc.3"
2 | github "lbrndnr/KIF" "c6a3ce99baab225848e6ab975956454e1d49ee6a"
--------------------------------------------------------------------------------
/Cartfile.resolved:
--------------------------------------------------------------------------------
1 | github "larcus94/KIF" "c6a3ce99baab225848e6ab975956454e1d49ee6a"
2 | github "Quick/Nimble" "v2.0.0-rc.3"
3 |
--------------------------------------------------------------------------------
/ImagePickerSheetController.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod spec lint ImagePickerSheetController.podspec' to ensure this is a
3 | # valid spec and to remove all comments including this before submitting the spec.
4 | #
5 | # To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html
6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/
7 | #
8 |
9 | Pod::Spec.new do |s|
10 |
11 | # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
12 | #
13 | # These will help people to find your library, and whilst it
14 | # can feel like a chore to fill in it's definitely to your advantage. The
15 | # summary should be tweet-length, and the description more in depth.
16 | #
17 |
18 | s.name = "ImagePickerSheetController"
19 | s.version = "0.9.3"
20 | s.summary = "ImagePickerSheetController is like the custom photo action sheet found in the iOS 8 and 9 version of iMessage"
21 |
22 | s.description = <<-DESC
23 | ImagePickerSheetController is a component that replicates the custom photo action sheet in iMessage. It's very similar to UIAlertController which makes its usage simple and concise.
24 | DESC
25 |
26 | s.homepage = "https://github.com/lbrndnr/ImagePickerSheetController"
27 | s.screenshot = "https://raw.githubusercontent.com/lbrndnr/ImagePickerSheetController/master/Screenshots/Nature.png"
28 |
29 |
30 | # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
31 | #
32 | # Licensing your code is important. See http://choosealicense.com for more info.
33 | # CocoaPods will detect a license file if there is a named LICENSE*
34 | # Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'.
35 | #
36 |
37 | s.license = { :type => "MIT", :file => "LICENSE" }
38 |
39 |
40 | # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
41 | #
42 | # Specify the authors of the library, with email addresses. Email addresses
43 | # of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also
44 | # accepts just a name if you'd rather not provide an email address.
45 | #
46 | # Specify a social_media_url where others can refer to, for example a twitter
47 | # profile URL.
48 | #
49 |
50 | s.author = { "Laurin Brandner" => "hello@laurinbrandner.ch" }
51 | s.social_media_url = "http://twitter.com/lbrndnr"
52 |
53 | # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
54 | #
55 | # If this Pod runs only on iOS or OS X, then specify the platform and
56 | # the deployment target. You can optionally include the target after the platform.
57 | #
58 |
59 | s.platform = :ios, "9.0"
60 |
61 | # When using multiple platforms
62 | s.ios.deployment_target = "9.0"
63 | # s.osx.deployment_target = "10.7"
64 |
65 |
66 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
67 | #
68 | # Specify the location from where the source should be retrieved.
69 | # Supports git, hg, bzr, svn and HTTP.
70 | #
71 |
72 | s.source = { :git => "https://github.com/lbrndnr/ImagePickerSheetController.git", :tag => s.version }
73 |
74 |
75 | # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
76 | #
77 | # CocoaPods is smart about how it includes source code. For source files
78 | # giving a folder will include any swift, h, m, mm, c & cpp files.
79 | # For header files it will include any header in the folder.
80 | # Not including the public_header_files will make all headers public.
81 | #
82 |
83 | s.source_files = "ImagePickerSheetController/ImagePickerSheetController/**/*.swift"
84 |
85 |
86 | # ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
87 | #
88 | # A list of resources included with the Pod. These are copied into the
89 | # target bundle with a build phase script. Anything else will be cleaned.
90 | # You can preserve files from being cleaned, please don't preserve
91 | # non-essential files like tests, examples and documentation.
92 | #
93 |
94 | s.resource = "ImagePickerSheetController/ImagePickerSheetController/Images.xcassets"
95 | # s.resources = "Resources/*.png"
96 | # s.resource_bundle = {"Images" => ["ImagePickerSheetController/ImagePickerSheetController/Images.xcassets"]}
97 |
98 | # s.preserve_paths = "FilesToSave", "MoreFilesToSave"
99 |
100 |
101 | # ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
102 | #
103 | # Link your library with frameworks, or libraries. Libraries do not include
104 | # the lib prefix of their name.
105 | #
106 |
107 | s.framework = "Photos"
108 |
109 |
110 | # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
111 | #
112 | # If your library depends on compiler flags you can set them in the xcconfig hash
113 | # where they will only apply to your library. If you depend on other Podspecs
114 | # you can include multiple dependencies to ensure it works.
115 |
116 | s.requires_arc = true
117 |
118 |
119 | end
120 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/Example/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Example
4 | //
5 | // Created by Laurin Brandner on 26/05/15.
6 | // Copyright (c) 2015 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: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
22 | window?.rootViewController = ViewController()
23 | window?.makeKeyAndVisible()
24 |
25 | return true
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/Example/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
20 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/Example/Base.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | BRNImagePickerSheet
4 |
5 | Created by Laurin Brandner on 11/11/14.
6 | Copyright (c) 2014 Laurin Brandner. All rights reserved.
7 | */
8 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/Example/Base.lproj/Localizable.stringsdict:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ImagePickerSheet.button1.Send %lu Photo
6 |
7 | NSStringLocalizedFormatKey
8 | Send %#@photos@
9 | photos
10 |
11 | NSStringFormatSpecTypeKey
12 | NSStringPluralRuleType
13 | NSStringFormatValueTypeKey
14 | lu
15 | one
16 | %lu Photo
17 | other
18 | %lu Photos
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/Example/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 | IPSC Example
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | NSPhotoLibraryUsageDescription
38 | Please allow access to browse pictures.
39 |
40 |
41 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/Example/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Example
4 | //
5 | // Created by Laurin Brandner on 26/05/15.
6 | // Copyright (c) 2015 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Photos
11 | import ImagePickerSheetController
12 |
13 | class ViewController: UIViewController {
14 |
15 | // MARK: - View Lifecycle
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 |
20 | let button = UIButton(type: .system)
21 | button.setTitle("Tap Me!", for: [])
22 | button.translatesAutoresizingMaskIntoConstraints = false
23 | view.addSubview(button)
24 | button.heightAnchor.constraint(equalToConstant: 40).isActive = true
25 | button.widthAnchor.constraint(equalToConstant: 150).isActive = true
26 | button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
27 | button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
28 | button.addTarget(self, action: #selector(presentImagePickerSheet(gestureRecognizer:)), for: .touchUpInside)
29 | }
30 |
31 | // MARK: - Other Methods
32 |
33 | @objc func presentImagePickerSheet(gestureRecognizer: UITapGestureRecognizer) {
34 | let presentImagePickerController: (UIImagePickerController.SourceType) -> () = { source in
35 | let controller = UIImagePickerController()
36 | controller.delegate = self
37 | var sourceType = source
38 | if (!UIImagePickerController.isSourceTypeAvailable(sourceType)) {
39 | sourceType = .photoLibrary
40 | print("Fallback to camera roll as a source since the simulator doesn't support taking pictures")
41 | }
42 | controller.sourceType = sourceType
43 |
44 | self.present(controller, animated: true, completion: nil)
45 | }
46 |
47 | let controller = ImagePickerSheetController(mediaType: .imageAndVideo)
48 | controller.maximumSelection = 1
49 | controller.delegate = self
50 |
51 | controller.addAction(ImagePickerAction(title: NSLocalizedString("Take Photo Or Video", comment: "Action Title"), secondaryTitle: NSLocalizedString("Add comment", comment: "Action Title"), handler: { _ in
52 | presentImagePickerController(.camera)
53 | }, secondaryHandler: { _, numberOfPhotos in
54 | print("Comment \(numberOfPhotos) photos")
55 | }))
56 | controller.addAction(ImagePickerAction(title: NSLocalizedString("Photo Library", comment: "Action Title"), secondaryTitle: { NSString.localizedStringWithFormat(NSLocalizedString("ImagePickerSheet.button1.Send %lu Photo", comment: "Action Title") as NSString, $0) as String}, handler: { _ in
57 | presentImagePickerController(.photoLibrary)
58 | }, secondaryHandler: { _, numberOfPhotos in
59 | print("Send \(controller.selectedAssets)")
60 | }))
61 | controller.addAction(ImagePickerAction(cancelTitle: NSLocalizedString("Cancel", comment: "Action Title")))
62 |
63 | if UIDevice.current.userInterfaceIdiom == .pad {
64 | controller.modalPresentationStyle = .popover
65 | controller.popoverPresentationController?.sourceView = view
66 | controller.popoverPresentationController?.sourceRect = CGRect(origin: view.center, size: CGSize())
67 | }
68 |
69 | present(controller, animated: true, completion: nil)
70 | }
71 |
72 | }
73 |
74 | // MARK: - UIImagePickerControllerDelegate
75 | extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
76 |
77 | func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
78 | dismiss(animated: true, completion: nil)
79 | }
80 | }
81 |
82 | // MARK: - ImagePickerSheetControllerDelegate
83 | extension ViewController: ImagePickerSheetControllerDelegate {
84 |
85 | func controllerWillEnlargePreview(_ controller: ImagePickerSheetController) {
86 | print("Will enlarge the preview")
87 | }
88 |
89 | func controllerDidEnlargePreview(_ controller: ImagePickerSheetController) {
90 | print("Did enlarge the preview")
91 | }
92 |
93 | func controller(_ controller: ImagePickerSheetController, willSelectAsset asset: PHAsset) {
94 | print("Will select an asset")
95 | }
96 |
97 | func controller(_ controller: ImagePickerSheetController, didSelectAsset asset: PHAsset) {
98 | print("Did select an asset")
99 | }
100 |
101 | func controller(_ controller: ImagePickerSheetController, willDeselectAsset asset: PHAsset) {
102 | print("Will deselect an asset")
103 | }
104 |
105 | func controller(_ controller: ImagePickerSheetController, didDeselectAsset asset: PHAsset) {
106 | print("Did deselect an asset")
107 | }
108 |
109 | }
110 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 4944CA851B8DB7F3007EA349 /* SheetActionCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4944CA841B8DB7F3007EA349 /* SheetActionCollectionViewCell.swift */; };
11 | 494505AC1B2201AF00EF9ADC /* KIFExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 494505AB1B2201AF00EF9ADC /* KIFExtensions.swift */; };
12 | 494505AE1B22022F00EF9ADC /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49A408461B21854300D6005C /* CoreGraphics.framework */; };
13 | 494505B11B22026600EF9ADC /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49A408481B2186D500D6005C /* IOKit.framework */; };
14 | 494B3B711B8EF3CB004B467C /* ImagePickerSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 494B3B701B8EF3CB004B467C /* ImagePickerSheetController.swift */; };
15 | 495E5F551B9DE84D004729E6 /* KIF.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49A408441B21853E00D6005C /* KIF.framework */; };
16 | 495F89A91B394288006B9BA4 /* Nimble.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 49DC4AAD1B14FB9A00B4E78E /* Nimble.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
17 | 49665AD71B8F2DC6005AA666 /* SheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49665AD61B8F2DC6005AA666 /* SheetController.swift */; };
18 | 4981CB031B8B246000A09750 /* SheetPreviewCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4981CAFD1B8B246000A09750 /* SheetPreviewCollectionViewCell.swift */; };
19 | 4981CB041B8B246000A09750 /* PreviewCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4981CAFF1B8B246000A09750 /* PreviewCollectionViewCell.swift */; };
20 | 4981CB051B8B246000A09750 /* PreviewCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4981CB001B8B246000A09750 /* PreviewCollectionView.swift */; };
21 | 4981CB061B8B246000A09750 /* PreviewCollectionViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4981CB011B8B246000A09750 /* PreviewCollectionViewLayout.swift */; };
22 | 4981CB071B8B246000A09750 /* PreviewSupplementaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4981CB021B8B246000A09750 /* PreviewSupplementaryView.swift */; };
23 | 4981CB091B8B255A00A09750 /* SheetCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4981CB081B8B255A00A09750 /* SheetCollectionViewCell.swift */; };
24 | 4989232F1B9B05CB00D68C8E /* KIF.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 49A408441B21853E00D6005C /* KIF.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
25 | 499E65CF1B8E317C005B807F /* SheetCollectionViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 499E65CE1B8E317C005B807F /* SheetCollectionViewLayout.swift */; };
26 | 49A93AE81B9CC0F100EE8A40 /* PresentationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49A93AE71B9CC0F100EE8A40 /* PresentationTests.swift */; };
27 | 49A93AF11B9CC15A00EE8A40 /* DismissalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49A93AF01B9CC15A00EE8A40 /* DismissalTests.swift */; };
28 | 49A93AF31B9CCB3900EE8A40 /* AddingActionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49A93AF21B9CCB3900EE8A40 /* AddingActionTests.swift */; };
29 | 49A93AF51B9CCBA900EE8A40 /* ActionHandlingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49A93AF41B9CCBA900EE8A40 /* ActionHandlingTests.swift */; };
30 | 49A93AF71B9CCC9500EE8A40 /* ImageSelectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49A93AF61B9CCC9500EE8A40 /* ImageSelectionTests.swift */; };
31 | 49DC4A571B14F1BC00B4E78E /* ImagePickerSheetController.h in Headers */ = {isa = PBXBuildFile; fileRef = 49DC4A561B14F1BC00B4E78E /* ImagePickerSheetController.h */; settings = {ATTRIBUTES = (Public, ); }; };
32 | 49DC4A5D1B14F1BC00B4E78E /* ImagePickerSheetController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49DC4A511B14F1BC00B4E78E /* ImagePickerSheetController.framework */; };
33 | 49DC4A641B14F1BC00B4E78E /* ImagePickerSheetControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49DC4A631B14F1BC00B4E78E /* ImagePickerSheetControllerTests.swift */; };
34 | 49DC4A751B14F1F600B4E78E /* AnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49DC4A6D1B14F1F600B4E78E /* AnimationController.swift */; };
35 | 49DC4A761B14F1F600B4E78E /* ImagePickerAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49DC4A6E1B14F1F600B4E78E /* ImagePickerAction.swift */; };
36 | 49DC4A7E1B14F22200B4E78E /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 49DC4A7D1B14F22200B4E78E /* Images.xcassets */; };
37 | 49DC4A801B14F2EF00B4E78E /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49DC4A7F1B14F2EF00B4E78E /* Photos.framework */; };
38 | 49DC4A8B1B14F31500B4E78E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49DC4A8A1B14F31500B4E78E /* AppDelegate.swift */; };
39 | 49DC4A8D1B14F31500B4E78E /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49DC4A8C1B14F31500B4E78E /* ViewController.swift */; };
40 | 49DC4A951B14F31500B4E78E /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 49DC4A931B14F31500B4E78E /* LaunchScreen.xib */; };
41 | 49DC4AA81B14F3B800B4E78E /* ImagePickerSheetController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49DC4A511B14F1BC00B4E78E /* ImagePickerSheetController.framework */; };
42 | 49DC4AA91B14F3B800B4E78E /* ImagePickerSheetController.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 49DC4A511B14F1BC00B4E78E /* ImagePickerSheetController.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
43 | 49DC4AAF1B14FB9A00B4E78E /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49DC4AAD1B14FB9A00B4E78E /* Nimble.framework */; };
44 | 49F3118A1B15074100ACD6A7 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 49F3118C1B15074100ACD6A7 /* Localizable.stringsdict */; };
45 | 49F3118D1B15074400ACD6A7 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 49F3118F1B15074400ACD6A7 /* Localizable.strings */; };
46 | /* End PBXBuildFile section */
47 |
48 | /* Begin PBXContainerItemProxy section */
49 | 49DC4A5E1B14F1BC00B4E78E /* PBXContainerItemProxy */ = {
50 | isa = PBXContainerItemProxy;
51 | containerPortal = 49DC4A481B14F1BC00B4E78E /* Project object */;
52 | proxyType = 1;
53 | remoteGlobalIDString = 49DC4A501B14F1BC00B4E78E;
54 | remoteInfo = ImagePickerSheetController;
55 | };
56 | 49DC4AAA1B14F3B800B4E78E /* PBXContainerItemProxy */ = {
57 | isa = PBXContainerItemProxy;
58 | containerPortal = 49DC4A481B14F1BC00B4E78E /* Project object */;
59 | proxyType = 1;
60 | remoteGlobalIDString = 49DC4A501B14F1BC00B4E78E;
61 | remoteInfo = ImagePickerSheetController;
62 | };
63 | 49F3117A1B14FEFE00ACD6A7 /* PBXContainerItemProxy */ = {
64 | isa = PBXContainerItemProxy;
65 | containerPortal = 49DC4A481B14F1BC00B4E78E /* Project object */;
66 | proxyType = 1;
67 | remoteGlobalIDString = 49DC4A851B14F31500B4E78E;
68 | remoteInfo = Example;
69 | };
70 | /* End PBXContainerItemProxy section */
71 |
72 | /* Begin PBXCopyFilesBuildPhase section */
73 | 495F89A61B394277006B9BA4 /* CopyFiles */ = {
74 | isa = PBXCopyFilesBuildPhase;
75 | buildActionMask = 2147483647;
76 | dstPath = "";
77 | dstSubfolderSpec = 10;
78 | files = (
79 | 4989232F1B9B05CB00D68C8E /* KIF.framework in CopyFiles */,
80 | 495F89A91B394288006B9BA4 /* Nimble.framework in CopyFiles */,
81 | );
82 | runOnlyForDeploymentPostprocessing = 0;
83 | };
84 | 49DC4AAC1B14F3B800B4E78E /* Embed Frameworks */ = {
85 | isa = PBXCopyFilesBuildPhase;
86 | buildActionMask = 2147483647;
87 | dstPath = "";
88 | dstSubfolderSpec = 10;
89 | files = (
90 | 49DC4AA91B14F3B800B4E78E /* ImagePickerSheetController.framework in Embed Frameworks */,
91 | );
92 | name = "Embed Frameworks";
93 | runOnlyForDeploymentPostprocessing = 0;
94 | };
95 | /* End PBXCopyFilesBuildPhase section */
96 |
97 | /* Begin PBXFileReference section */
98 | 4944CA841B8DB7F3007EA349 /* SheetActionCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SheetActionCollectionViewCell.swift; sourceTree = ""; };
99 | 494505AB1B2201AF00EF9ADC /* KIFExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KIFExtensions.swift; sourceTree = ""; };
100 | 494B3B701B8EF3CB004B467C /* ImagePickerSheetController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagePickerSheetController.swift; sourceTree = ""; };
101 | 49665AD61B8F2DC6005AA666 /* SheetController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SheetController.swift; sourceTree = ""; };
102 | 496DC6D11B86F6E90011AF73 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; };
103 | 4981CAFD1B8B246000A09750 /* SheetPreviewCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = SheetPreviewCollectionViewCell.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
104 | 4981CAFF1B8B246000A09750 /* PreviewCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewCollectionViewCell.swift; sourceTree = ""; };
105 | 4981CB001B8B246000A09750 /* PreviewCollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewCollectionView.swift; sourceTree = ""; };
106 | 4981CB011B8B246000A09750 /* PreviewCollectionViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewCollectionViewLayout.swift; sourceTree = ""; };
107 | 4981CB021B8B246000A09750 /* PreviewSupplementaryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewSupplementaryView.swift; sourceTree = ""; };
108 | 4981CB081B8B255A00A09750 /* SheetCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SheetCollectionViewCell.swift; sourceTree = ""; };
109 | 499E65CE1B8E317C005B807F /* SheetCollectionViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SheetCollectionViewLayout.swift; sourceTree = ""; };
110 | 49A408441B21853E00D6005C /* KIF.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = KIF.framework; path = ../Carthage/Build/iOS/KIF.framework; sourceTree = ""; };
111 | 49A408461B21854300D6005C /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
112 | 49A408481B2186D500D6005C /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/IOKit.framework; sourceTree = DEVELOPER_DIR; };
113 | 49A93AE71B9CC0F100EE8A40 /* PresentationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresentationTests.swift; sourceTree = ""; };
114 | 49A93AF01B9CC15A00EE8A40 /* DismissalTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DismissalTests.swift; sourceTree = ""; };
115 | 49A93AF21B9CCB3900EE8A40 /* AddingActionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddingActionTests.swift; sourceTree = ""; };
116 | 49A93AF41B9CCBA900EE8A40 /* ActionHandlingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionHandlingTests.swift; sourceTree = ""; };
117 | 49A93AF61B9CCC9500EE8A40 /* ImageSelectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageSelectionTests.swift; sourceTree = ""; };
118 | 49DC4A511B14F1BC00B4E78E /* ImagePickerSheetController.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ImagePickerSheetController.framework; sourceTree = BUILT_PRODUCTS_DIR; };
119 | 49DC4A551B14F1BC00B4E78E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
120 | 49DC4A561B14F1BC00B4E78E /* ImagePickerSheetController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ImagePickerSheetController.h; sourceTree = ""; };
121 | 49DC4A5C1B14F1BC00B4E78E /* ImagePickerSheetControllerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ImagePickerSheetControllerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
122 | 49DC4A621B14F1BC00B4E78E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
123 | 49DC4A631B14F1BC00B4E78E /* ImagePickerSheetControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePickerSheetControllerTests.swift; sourceTree = ""; };
124 | 49DC4A6D1B14F1F600B4E78E /* AnimationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationController.swift; sourceTree = ""; };
125 | 49DC4A6E1B14F1F600B4E78E /* ImagePickerAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagePickerAction.swift; sourceTree = ""; };
126 | 49DC4A7D1B14F22200B4E78E /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; };
127 | 49DC4A7F1B14F2EF00B4E78E /* Photos.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Photos.framework; path = System/Library/Frameworks/Photos.framework; sourceTree = SDKROOT; };
128 | 49DC4A861B14F31500B4E78E /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
129 | 49DC4A891B14F31500B4E78E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
130 | 49DC4A8A1B14F31500B4E78E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
131 | 49DC4A8C1B14F31500B4E78E /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
132 | 49DC4A941B14F31500B4E78E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; };
133 | 49DC4AAD1B14FB9A00B4E78E /* Nimble.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nimble.framework; path = ../Carthage/Build/iOS/Nimble.framework; sourceTree = ""; };
134 | 49F3118B1B15074100ACD6A7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = Base; path = Base.lproj/Localizable.stringsdict; sourceTree = ""; };
135 | 49F3118E1B15074400ACD6A7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; };
136 | /* End PBXFileReference section */
137 |
138 | /* Begin PBXFrameworksBuildPhase section */
139 | 49DC4A4D1B14F1BC00B4E78E /* Frameworks */ = {
140 | isa = PBXFrameworksBuildPhase;
141 | buildActionMask = 2147483647;
142 | files = (
143 | 49DC4A801B14F2EF00B4E78E /* Photos.framework in Frameworks */,
144 | );
145 | runOnlyForDeploymentPostprocessing = 0;
146 | };
147 | 49DC4A591B14F1BC00B4E78E /* Frameworks */ = {
148 | isa = PBXFrameworksBuildPhase;
149 | buildActionMask = 2147483647;
150 | files = (
151 | 495E5F551B9DE84D004729E6 /* KIF.framework in Frameworks */,
152 | 49DC4A5D1B14F1BC00B4E78E /* ImagePickerSheetController.framework in Frameworks */,
153 | 494505AE1B22022F00EF9ADC /* CoreGraphics.framework in Frameworks */,
154 | 494505B11B22026600EF9ADC /* IOKit.framework in Frameworks */,
155 | 49DC4AAF1B14FB9A00B4E78E /* Nimble.framework in Frameworks */,
156 | );
157 | runOnlyForDeploymentPostprocessing = 0;
158 | };
159 | 49DC4A831B14F31500B4E78E /* Frameworks */ = {
160 | isa = PBXFrameworksBuildPhase;
161 | buildActionMask = 2147483647;
162 | files = (
163 | 49DC4AA81B14F3B800B4E78E /* ImagePickerSheetController.framework in Frameworks */,
164 | );
165 | runOnlyForDeploymentPostprocessing = 0;
166 | };
167 | /* End PBXFrameworksBuildPhase section */
168 |
169 | /* Begin PBXGroup section */
170 | 4981CAFC1B8B246000A09750 /* Sheet */ = {
171 | isa = PBXGroup;
172 | children = (
173 | 49665AD61B8F2DC6005AA666 /* SheetController.swift */,
174 | 499E65CE1B8E317C005B807F /* SheetCollectionViewLayout.swift */,
175 | 4981CB081B8B255A00A09750 /* SheetCollectionViewCell.swift */,
176 | 4981CAFD1B8B246000A09750 /* SheetPreviewCollectionViewCell.swift */,
177 | 4944CA841B8DB7F3007EA349 /* SheetActionCollectionViewCell.swift */,
178 | 4981CAFE1B8B246000A09750 /* Preview */,
179 | );
180 | path = Sheet;
181 | sourceTree = "";
182 | };
183 | 4981CAFE1B8B246000A09750 /* Preview */ = {
184 | isa = PBXGroup;
185 | children = (
186 | 4981CB001B8B246000A09750 /* PreviewCollectionView.swift */,
187 | 4981CB011B8B246000A09750 /* PreviewCollectionViewLayout.swift */,
188 | 4981CAFF1B8B246000A09750 /* PreviewCollectionViewCell.swift */,
189 | 4981CB021B8B246000A09750 /* PreviewSupplementaryView.swift */,
190 | );
191 | path = Preview;
192 | sourceTree = "";
193 | };
194 | 49DC4A471B14F1BC00B4E78E = {
195 | isa = PBXGroup;
196 | children = (
197 | 49DC4A531B14F1BC00B4E78E /* ImagePickerSheetController */,
198 | 49DC4A601B14F1BC00B4E78E /* ImagePickerSheetControllerTests */,
199 | 49DC4A871B14F31500B4E78E /* Example */,
200 | 49DC4AB11B14FBAD00B4E78E /* Frameworks */,
201 | 49DC4A521B14F1BC00B4E78E /* Products */,
202 | );
203 | sourceTree = "";
204 | };
205 | 49DC4A521B14F1BC00B4E78E /* Products */ = {
206 | isa = PBXGroup;
207 | children = (
208 | 49DC4A511B14F1BC00B4E78E /* ImagePickerSheetController.framework */,
209 | 49DC4A5C1B14F1BC00B4E78E /* ImagePickerSheetControllerTests.xctest */,
210 | 49DC4A861B14F31500B4E78E /* Example.app */,
211 | );
212 | name = Products;
213 | sourceTree = "";
214 | };
215 | 49DC4A531B14F1BC00B4E78E /* ImagePickerSheetController */ = {
216 | isa = PBXGroup;
217 | children = (
218 | 49DC4A6E1B14F1F600B4E78E /* ImagePickerAction.swift */,
219 | 49DC4A6D1B14F1F600B4E78E /* AnimationController.swift */,
220 | 494B3B701B8EF3CB004B467C /* ImagePickerSheetController.swift */,
221 | 4981CAFC1B8B246000A09750 /* Sheet */,
222 | 49DC4A7D1B14F22200B4E78E /* Images.xcassets */,
223 | 49DC4A541B14F1BC00B4E78E /* Supporting Files */,
224 | );
225 | path = ImagePickerSheetController;
226 | sourceTree = "";
227 | };
228 | 49DC4A541B14F1BC00B4E78E /* Supporting Files */ = {
229 | isa = PBXGroup;
230 | children = (
231 | 49DC4A561B14F1BC00B4E78E /* ImagePickerSheetController.h */,
232 | 49DC4A551B14F1BC00B4E78E /* Info.plist */,
233 | );
234 | name = "Supporting Files";
235 | sourceTree = "";
236 | };
237 | 49DC4A601B14F1BC00B4E78E /* ImagePickerSheetControllerTests */ = {
238 | isa = PBXGroup;
239 | children = (
240 | 49A93AE71B9CC0F100EE8A40 /* PresentationTests.swift */,
241 | 49A93AF01B9CC15A00EE8A40 /* DismissalTests.swift */,
242 | 49A93AF21B9CCB3900EE8A40 /* AddingActionTests.swift */,
243 | 49A93AF41B9CCBA900EE8A40 /* ActionHandlingTests.swift */,
244 | 49A93AF61B9CCC9500EE8A40 /* ImageSelectionTests.swift */,
245 | 49DC4A631B14F1BC00B4E78E /* ImagePickerSheetControllerTests.swift */,
246 | 494505AB1B2201AF00EF9ADC /* KIFExtensions.swift */,
247 | 49DC4A611B14F1BC00B4E78E /* Supporting Files */,
248 | );
249 | path = ImagePickerSheetControllerTests;
250 | sourceTree = "";
251 | };
252 | 49DC4A611B14F1BC00B4E78E /* Supporting Files */ = {
253 | isa = PBXGroup;
254 | children = (
255 | 49DC4A621B14F1BC00B4E78E /* Info.plist */,
256 | );
257 | name = "Supporting Files";
258 | sourceTree = "";
259 | };
260 | 49DC4A871B14F31500B4E78E /* Example */ = {
261 | isa = PBXGroup;
262 | children = (
263 | 49DC4A8A1B14F31500B4E78E /* AppDelegate.swift */,
264 | 49DC4A8C1B14F31500B4E78E /* ViewController.swift */,
265 | 49DC4A881B14F31500B4E78E /* Supporting Files */,
266 | );
267 | path = Example;
268 | sourceTree = "";
269 | };
270 | 49DC4A881B14F31500B4E78E /* Supporting Files */ = {
271 | isa = PBXGroup;
272 | children = (
273 | 49F3118F1B15074400ACD6A7 /* Localizable.strings */,
274 | 49F3118C1B15074100ACD6A7 /* Localizable.stringsdict */,
275 | 49DC4A931B14F31500B4E78E /* LaunchScreen.xib */,
276 | 49DC4A891B14F31500B4E78E /* Info.plist */,
277 | );
278 | name = "Supporting Files";
279 | sourceTree = "";
280 | };
281 | 49DC4AB11B14FBAD00B4E78E /* Frameworks */ = {
282 | isa = PBXGroup;
283 | children = (
284 | 496DC6D11B86F6E90011AF73 /* CoreMedia.framework */,
285 | 49A408481B2186D500D6005C /* IOKit.framework */,
286 | 49A408461B21854300D6005C /* CoreGraphics.framework */,
287 | 49A408441B21853E00D6005C /* KIF.framework */,
288 | 49DC4A7F1B14F2EF00B4E78E /* Photos.framework */,
289 | 49DC4AAD1B14FB9A00B4E78E /* Nimble.framework */,
290 | );
291 | name = Frameworks;
292 | sourceTree = "";
293 | };
294 | /* End PBXGroup section */
295 |
296 | /* Begin PBXHeadersBuildPhase section */
297 | 49DC4A4E1B14F1BC00B4E78E /* Headers */ = {
298 | isa = PBXHeadersBuildPhase;
299 | buildActionMask = 2147483647;
300 | files = (
301 | 49DC4A571B14F1BC00B4E78E /* ImagePickerSheetController.h in Headers */,
302 | );
303 | runOnlyForDeploymentPostprocessing = 0;
304 | };
305 | /* End PBXHeadersBuildPhase section */
306 |
307 | /* Begin PBXNativeTarget section */
308 | 49DC4A501B14F1BC00B4E78E /* ImagePickerSheetController */ = {
309 | isa = PBXNativeTarget;
310 | buildConfigurationList = 49DC4A671B14F1BC00B4E78E /* Build configuration list for PBXNativeTarget "ImagePickerSheetController" */;
311 | buildPhases = (
312 | 49DC4A4C1B14F1BC00B4E78E /* Sources */,
313 | 49DC4A4D1B14F1BC00B4E78E /* Frameworks */,
314 | 49DC4A4E1B14F1BC00B4E78E /* Headers */,
315 | 49DC4A4F1B14F1BC00B4E78E /* Resources */,
316 | );
317 | buildRules = (
318 | );
319 | dependencies = (
320 | );
321 | name = ImagePickerSheetController;
322 | productName = ImagePickerSheetController;
323 | productReference = 49DC4A511B14F1BC00B4E78E /* ImagePickerSheetController.framework */;
324 | productType = "com.apple.product-type.framework";
325 | };
326 | 49DC4A5B1B14F1BC00B4E78E /* ImagePickerSheetControllerTests */ = {
327 | isa = PBXNativeTarget;
328 | buildConfigurationList = 49DC4A6A1B14F1BC00B4E78E /* Build configuration list for PBXNativeTarget "ImagePickerSheetControllerTests" */;
329 | buildPhases = (
330 | 49DC4A581B14F1BC00B4E78E /* Sources */,
331 | 49DC4A591B14F1BC00B4E78E /* Frameworks */,
332 | 49DC4A5A1B14F1BC00B4E78E /* Resources */,
333 | 495F89A61B394277006B9BA4 /* CopyFiles */,
334 | );
335 | buildRules = (
336 | );
337 | dependencies = (
338 | 49DC4A5F1B14F1BC00B4E78E /* PBXTargetDependency */,
339 | 49F3117B1B14FEFE00ACD6A7 /* PBXTargetDependency */,
340 | );
341 | name = ImagePickerSheetControllerTests;
342 | productName = ImagePickerSheetControllerTests;
343 | productReference = 49DC4A5C1B14F1BC00B4E78E /* ImagePickerSheetControllerTests.xctest */;
344 | productType = "com.apple.product-type.bundle.unit-test";
345 | };
346 | 49DC4A851B14F31500B4E78E /* Example */ = {
347 | isa = PBXNativeTarget;
348 | buildConfigurationList = 49DC4AA21B14F31500B4E78E /* Build configuration list for PBXNativeTarget "Example" */;
349 | buildPhases = (
350 | 49DC4A821B14F31500B4E78E /* Sources */,
351 | 49DC4A831B14F31500B4E78E /* Frameworks */,
352 | 49DC4A841B14F31500B4E78E /* Resources */,
353 | 49DC4AAC1B14F3B800B4E78E /* Embed Frameworks */,
354 | );
355 | buildRules = (
356 | );
357 | dependencies = (
358 | 49DC4AAB1B14F3B800B4E78E /* PBXTargetDependency */,
359 | );
360 | name = Example;
361 | productName = Example;
362 | productReference = 49DC4A861B14F31500B4E78E /* Example.app */;
363 | productType = "com.apple.product-type.application";
364 | };
365 | /* End PBXNativeTarget section */
366 |
367 | /* Begin PBXProject section */
368 | 49DC4A481B14F1BC00B4E78E /* Project object */ = {
369 | isa = PBXProject;
370 | attributes = {
371 | LastSwiftUpdateCheck = 0700;
372 | LastUpgradeCheck = 1010;
373 | ORGANIZATIONNAME = "Laurin Brandner";
374 | TargetAttributes = {
375 | 49DC4A501B14F1BC00B4E78E = {
376 | CreatedOnToolsVersion = 6.3.1;
377 | LastSwiftMigration = 0800;
378 | };
379 | 49DC4A5B1B14F1BC00B4E78E = {
380 | CreatedOnToolsVersion = 6.3.1;
381 | LastSwiftMigration = 0800;
382 | TestTargetID = 49DC4A851B14F31500B4E78E;
383 | };
384 | 49DC4A851B14F31500B4E78E = {
385 | CreatedOnToolsVersion = 6.3.1;
386 | DevelopmentTeam = 5953RHWYWT;
387 | LastSwiftMigration = 0800;
388 | };
389 | };
390 | };
391 | buildConfigurationList = 49DC4A4B1B14F1BC00B4E78E /* Build configuration list for PBXProject "ImagePickerSheetController" */;
392 | compatibilityVersion = "Xcode 3.2";
393 | developmentRegion = English;
394 | hasScannedForEncodings = 0;
395 | knownRegions = (
396 | en,
397 | Base,
398 | );
399 | mainGroup = 49DC4A471B14F1BC00B4E78E;
400 | productRefGroup = 49DC4A521B14F1BC00B4E78E /* Products */;
401 | projectDirPath = "";
402 | projectRoot = "";
403 | targets = (
404 | 49DC4A501B14F1BC00B4E78E /* ImagePickerSheetController */,
405 | 49DC4A5B1B14F1BC00B4E78E /* ImagePickerSheetControllerTests */,
406 | 49DC4A851B14F31500B4E78E /* Example */,
407 | );
408 | };
409 | /* End PBXProject section */
410 |
411 | /* Begin PBXResourcesBuildPhase section */
412 | 49DC4A4F1B14F1BC00B4E78E /* Resources */ = {
413 | isa = PBXResourcesBuildPhase;
414 | buildActionMask = 2147483647;
415 | files = (
416 | 49DC4A7E1B14F22200B4E78E /* Images.xcassets in Resources */,
417 | );
418 | runOnlyForDeploymentPostprocessing = 0;
419 | };
420 | 49DC4A5A1B14F1BC00B4E78E /* Resources */ = {
421 | isa = PBXResourcesBuildPhase;
422 | buildActionMask = 2147483647;
423 | files = (
424 | );
425 | runOnlyForDeploymentPostprocessing = 0;
426 | };
427 | 49DC4A841B14F31500B4E78E /* Resources */ = {
428 | isa = PBXResourcesBuildPhase;
429 | buildActionMask = 2147483647;
430 | files = (
431 | 49DC4A951B14F31500B4E78E /* LaunchScreen.xib in Resources */,
432 | 49F3118D1B15074400ACD6A7 /* Localizable.strings in Resources */,
433 | 49F3118A1B15074100ACD6A7 /* Localizable.stringsdict in Resources */,
434 | );
435 | runOnlyForDeploymentPostprocessing = 0;
436 | };
437 | /* End PBXResourcesBuildPhase section */
438 |
439 | /* Begin PBXSourcesBuildPhase section */
440 | 49DC4A4C1B14F1BC00B4E78E /* Sources */ = {
441 | isa = PBXSourcesBuildPhase;
442 | buildActionMask = 2147483647;
443 | files = (
444 | 4981CB041B8B246000A09750 /* PreviewCollectionViewCell.swift in Sources */,
445 | 49DC4A751B14F1F600B4E78E /* AnimationController.swift in Sources */,
446 | 4981CB061B8B246000A09750 /* PreviewCollectionViewLayout.swift in Sources */,
447 | 49DC4A761B14F1F600B4E78E /* ImagePickerAction.swift in Sources */,
448 | 494B3B711B8EF3CB004B467C /* ImagePickerSheetController.swift in Sources */,
449 | 49665AD71B8F2DC6005AA666 /* SheetController.swift in Sources */,
450 | 499E65CF1B8E317C005B807F /* SheetCollectionViewLayout.swift in Sources */,
451 | 4981CB031B8B246000A09750 /* SheetPreviewCollectionViewCell.swift in Sources */,
452 | 4981CB091B8B255A00A09750 /* SheetCollectionViewCell.swift in Sources */,
453 | 4981CB071B8B246000A09750 /* PreviewSupplementaryView.swift in Sources */,
454 | 4981CB051B8B246000A09750 /* PreviewCollectionView.swift in Sources */,
455 | 4944CA851B8DB7F3007EA349 /* SheetActionCollectionViewCell.swift in Sources */,
456 | );
457 | runOnlyForDeploymentPostprocessing = 0;
458 | };
459 | 49DC4A581B14F1BC00B4E78E /* Sources */ = {
460 | isa = PBXSourcesBuildPhase;
461 | buildActionMask = 2147483647;
462 | files = (
463 | 494505AC1B2201AF00EF9ADC /* KIFExtensions.swift in Sources */,
464 | 49A93AE81B9CC0F100EE8A40 /* PresentationTests.swift in Sources */,
465 | 49A93AF51B9CCBA900EE8A40 /* ActionHandlingTests.swift in Sources */,
466 | 49DC4A641B14F1BC00B4E78E /* ImagePickerSheetControllerTests.swift in Sources */,
467 | 49A93AF11B9CC15A00EE8A40 /* DismissalTests.swift in Sources */,
468 | 49A93AF31B9CCB3900EE8A40 /* AddingActionTests.swift in Sources */,
469 | 49A93AF71B9CCC9500EE8A40 /* ImageSelectionTests.swift in Sources */,
470 | );
471 | runOnlyForDeploymentPostprocessing = 0;
472 | };
473 | 49DC4A821B14F31500B4E78E /* Sources */ = {
474 | isa = PBXSourcesBuildPhase;
475 | buildActionMask = 2147483647;
476 | files = (
477 | 49DC4A8D1B14F31500B4E78E /* ViewController.swift in Sources */,
478 | 49DC4A8B1B14F31500B4E78E /* AppDelegate.swift in Sources */,
479 | );
480 | runOnlyForDeploymentPostprocessing = 0;
481 | };
482 | /* End PBXSourcesBuildPhase section */
483 |
484 | /* Begin PBXTargetDependency section */
485 | 49DC4A5F1B14F1BC00B4E78E /* PBXTargetDependency */ = {
486 | isa = PBXTargetDependency;
487 | target = 49DC4A501B14F1BC00B4E78E /* ImagePickerSheetController */;
488 | targetProxy = 49DC4A5E1B14F1BC00B4E78E /* PBXContainerItemProxy */;
489 | };
490 | 49DC4AAB1B14F3B800B4E78E /* PBXTargetDependency */ = {
491 | isa = PBXTargetDependency;
492 | target = 49DC4A501B14F1BC00B4E78E /* ImagePickerSheetController */;
493 | targetProxy = 49DC4AAA1B14F3B800B4E78E /* PBXContainerItemProxy */;
494 | };
495 | 49F3117B1B14FEFE00ACD6A7 /* PBXTargetDependency */ = {
496 | isa = PBXTargetDependency;
497 | target = 49DC4A851B14F31500B4E78E /* Example */;
498 | targetProxy = 49F3117A1B14FEFE00ACD6A7 /* PBXContainerItemProxy */;
499 | };
500 | /* End PBXTargetDependency section */
501 |
502 | /* Begin PBXVariantGroup section */
503 | 49DC4A931B14F31500B4E78E /* LaunchScreen.xib */ = {
504 | isa = PBXVariantGroup;
505 | children = (
506 | 49DC4A941B14F31500B4E78E /* Base */,
507 | );
508 | name = LaunchScreen.xib;
509 | sourceTree = "";
510 | };
511 | 49F3118C1B15074100ACD6A7 /* Localizable.stringsdict */ = {
512 | isa = PBXVariantGroup;
513 | children = (
514 | 49F3118B1B15074100ACD6A7 /* Base */,
515 | );
516 | name = Localizable.stringsdict;
517 | sourceTree = "";
518 | };
519 | 49F3118F1B15074400ACD6A7 /* Localizable.strings */ = {
520 | isa = PBXVariantGroup;
521 | children = (
522 | 49F3118E1B15074400ACD6A7 /* Base */,
523 | );
524 | name = Localizable.strings;
525 | sourceTree = "";
526 | };
527 | /* End PBXVariantGroup section */
528 |
529 | /* Begin XCBuildConfiguration section */
530 | 49DC4A651B14F1BC00B4E78E /* Debug */ = {
531 | isa = XCBuildConfiguration;
532 | buildSettings = {
533 | ALWAYS_SEARCH_USER_PATHS = NO;
534 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
535 | CLANG_CXX_LIBRARY = "libc++";
536 | CLANG_ENABLE_MODULES = YES;
537 | CLANG_ENABLE_OBJC_ARC = YES;
538 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
539 | CLANG_WARN_BOOL_CONVERSION = YES;
540 | CLANG_WARN_COMMA = YES;
541 | CLANG_WARN_CONSTANT_CONVERSION = YES;
542 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
543 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
544 | CLANG_WARN_EMPTY_BODY = YES;
545 | CLANG_WARN_ENUM_CONVERSION = YES;
546 | CLANG_WARN_INFINITE_RECURSION = YES;
547 | CLANG_WARN_INT_CONVERSION = YES;
548 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
549 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
550 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
551 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
552 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
553 | CLANG_WARN_STRICT_PROTOTYPES = YES;
554 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
555 | CLANG_WARN_UNREACHABLE_CODE = YES;
556 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
557 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
558 | COPY_PHASE_STRIP = NO;
559 | CURRENT_PROJECT_VERSION = 1;
560 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
561 | ENABLE_STRICT_OBJC_MSGSEND = YES;
562 | ENABLE_TESTABILITY = YES;
563 | GCC_C_LANGUAGE_STANDARD = gnu99;
564 | GCC_DYNAMIC_NO_PIC = NO;
565 | GCC_NO_COMMON_BLOCKS = YES;
566 | GCC_OPTIMIZATION_LEVEL = 0;
567 | GCC_PREPROCESSOR_DEFINITIONS = (
568 | "DEBUG=1",
569 | "$(inherited)",
570 | );
571 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
572 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
573 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
574 | GCC_WARN_UNDECLARED_SELECTOR = YES;
575 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
576 | GCC_WARN_UNUSED_FUNCTION = YES;
577 | GCC_WARN_UNUSED_VARIABLE = YES;
578 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
579 | MTL_ENABLE_DEBUG_INFO = YES;
580 | ONLY_ACTIVE_ARCH = YES;
581 | SDKROOT = iphoneos;
582 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
583 | SWIFT_VERSION = 4.2;
584 | TARGETED_DEVICE_FAMILY = "1,2";
585 | VERSIONING_SYSTEM = "apple-generic";
586 | VERSION_INFO_PREFIX = "";
587 | };
588 | name = Debug;
589 | };
590 | 49DC4A661B14F1BC00B4E78E /* Release */ = {
591 | isa = XCBuildConfiguration;
592 | buildSettings = {
593 | ALWAYS_SEARCH_USER_PATHS = NO;
594 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
595 | CLANG_CXX_LIBRARY = "libc++";
596 | CLANG_ENABLE_MODULES = YES;
597 | CLANG_ENABLE_OBJC_ARC = YES;
598 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
599 | CLANG_WARN_BOOL_CONVERSION = YES;
600 | CLANG_WARN_COMMA = YES;
601 | CLANG_WARN_CONSTANT_CONVERSION = YES;
602 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
603 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
604 | CLANG_WARN_EMPTY_BODY = YES;
605 | CLANG_WARN_ENUM_CONVERSION = YES;
606 | CLANG_WARN_INFINITE_RECURSION = YES;
607 | CLANG_WARN_INT_CONVERSION = YES;
608 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
609 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
610 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
611 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
612 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
613 | CLANG_WARN_STRICT_PROTOTYPES = YES;
614 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
615 | CLANG_WARN_UNREACHABLE_CODE = YES;
616 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
617 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
618 | COPY_PHASE_STRIP = NO;
619 | CURRENT_PROJECT_VERSION = 1;
620 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
621 | ENABLE_NS_ASSERTIONS = NO;
622 | ENABLE_STRICT_OBJC_MSGSEND = YES;
623 | GCC_C_LANGUAGE_STANDARD = gnu99;
624 | GCC_NO_COMMON_BLOCKS = YES;
625 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
626 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
627 | GCC_WARN_UNDECLARED_SELECTOR = YES;
628 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
629 | GCC_WARN_UNUSED_FUNCTION = YES;
630 | GCC_WARN_UNUSED_VARIABLE = YES;
631 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
632 | MTL_ENABLE_DEBUG_INFO = NO;
633 | SDKROOT = iphoneos;
634 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
635 | SWIFT_VERSION = 4.2;
636 | TARGETED_DEVICE_FAMILY = "1,2";
637 | VALIDATE_PRODUCT = YES;
638 | VERSIONING_SYSTEM = "apple-generic";
639 | VERSION_INFO_PREFIX = "";
640 | };
641 | name = Release;
642 | };
643 | 49DC4A681B14F1BC00B4E78E /* Debug */ = {
644 | isa = XCBuildConfiguration;
645 | buildSettings = {
646 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
647 | DEFINES_MODULE = YES;
648 | DYLIB_COMPATIBILITY_VERSION = 1;
649 | DYLIB_CURRENT_VERSION = 1;
650 | DYLIB_INSTALL_NAME_BASE = "@rpath";
651 | FRAMEWORK_SEARCH_PATHS = "";
652 | INFOPLIST_FILE = ImagePickerSheetController/Info.plist;
653 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
654 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
655 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
656 | PRODUCT_BUNDLE_IDENTIFIER = "ch.laurinbrandner.$(PRODUCT_NAME:rfc1034identifier)";
657 | PRODUCT_NAME = "$(TARGET_NAME)";
658 | SKIP_INSTALL = YES;
659 | SWIFT_VERSION = 4.2;
660 | };
661 | name = Debug;
662 | };
663 | 49DC4A691B14F1BC00B4E78E /* Release */ = {
664 | isa = XCBuildConfiguration;
665 | buildSettings = {
666 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
667 | DEFINES_MODULE = YES;
668 | DYLIB_COMPATIBILITY_VERSION = 1;
669 | DYLIB_CURRENT_VERSION = 1;
670 | DYLIB_INSTALL_NAME_BASE = "@rpath";
671 | FRAMEWORK_SEARCH_PATHS = "";
672 | INFOPLIST_FILE = ImagePickerSheetController/Info.plist;
673 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
674 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
675 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
676 | PRODUCT_BUNDLE_IDENTIFIER = "ch.laurinbrandner.$(PRODUCT_NAME:rfc1034identifier)";
677 | PRODUCT_NAME = "$(TARGET_NAME)";
678 | SKIP_INSTALL = YES;
679 | SWIFT_VERSION = 4.2;
680 | };
681 | name = Release;
682 | };
683 | 49DC4A6B1B14F1BC00B4E78E /* Debug */ = {
684 | isa = XCBuildConfiguration;
685 | buildSettings = {
686 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
687 | DEVELOPMENT_TEAM = "";
688 | FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../Carthage/Build/iOS";
689 | GCC_PREPROCESSOR_DEFINITIONS = (
690 | "DEBUG=1",
691 | "$(inherited)",
692 | );
693 | INFOPLIST_FILE = ImagePickerSheetControllerTests/Info.plist;
694 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
695 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
696 | PRODUCT_BUNDLE_IDENTIFIER = "ch.laurinbrandner.$(PRODUCT_NAME:rfc1034identifier)";
697 | PRODUCT_NAME = "$(TARGET_NAME)";
698 | SWIFT_VERSION = 3.0;
699 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example";
700 | };
701 | name = Debug;
702 | };
703 | 49DC4A6C1B14F1BC00B4E78E /* Release */ = {
704 | isa = XCBuildConfiguration;
705 | buildSettings = {
706 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
707 | DEVELOPMENT_TEAM = "";
708 | FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../Carthage/Build/iOS";
709 | INFOPLIST_FILE = ImagePickerSheetControllerTests/Info.plist;
710 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
711 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
712 | PRODUCT_BUNDLE_IDENTIFIER = "ch.laurinbrandner.$(PRODUCT_NAME:rfc1034identifier)";
713 | PRODUCT_NAME = "$(TARGET_NAME)";
714 | SWIFT_VERSION = 3.0;
715 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example";
716 | };
717 | name = Release;
718 | };
719 | 49DC4AA31B14F31500B4E78E /* Debug */ = {
720 | isa = XCBuildConfiguration;
721 | buildSettings = {
722 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
723 | CODE_SIGN_IDENTITY = "iPhone Developer";
724 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
725 | GCC_PREPROCESSOR_DEFINITIONS = (
726 | "DEBUG=1",
727 | "$(inherited)",
728 | );
729 | INFOPLIST_FILE = Example/Info.plist;
730 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
731 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
732 | PRODUCT_BUNDLE_IDENTIFIER = "ch.laurinbrandner.$(PRODUCT_NAME:rfc1034identifier)";
733 | PRODUCT_NAME = "$(TARGET_NAME)";
734 | PROVISIONING_PROFILE = "";
735 | SWIFT_VERSION = 4.2;
736 | };
737 | name = Debug;
738 | };
739 | 49DC4AA41B14F31500B4E78E /* Release */ = {
740 | isa = XCBuildConfiguration;
741 | buildSettings = {
742 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
743 | CODE_SIGN_IDENTITY = "iPhone Developer";
744 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
745 | INFOPLIST_FILE = Example/Info.plist;
746 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
747 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
748 | PRODUCT_BUNDLE_IDENTIFIER = "ch.laurinbrandner.$(PRODUCT_NAME:rfc1034identifier)";
749 | PRODUCT_NAME = "$(TARGET_NAME)";
750 | PROVISIONING_PROFILE = "";
751 | SWIFT_VERSION = 4.2;
752 | };
753 | name = Release;
754 | };
755 | /* End XCBuildConfiguration section */
756 |
757 | /* Begin XCConfigurationList section */
758 | 49DC4A4B1B14F1BC00B4E78E /* Build configuration list for PBXProject "ImagePickerSheetController" */ = {
759 | isa = XCConfigurationList;
760 | buildConfigurations = (
761 | 49DC4A651B14F1BC00B4E78E /* Debug */,
762 | 49DC4A661B14F1BC00B4E78E /* Release */,
763 | );
764 | defaultConfigurationIsVisible = 0;
765 | defaultConfigurationName = Release;
766 | };
767 | 49DC4A671B14F1BC00B4E78E /* Build configuration list for PBXNativeTarget "ImagePickerSheetController" */ = {
768 | isa = XCConfigurationList;
769 | buildConfigurations = (
770 | 49DC4A681B14F1BC00B4E78E /* Debug */,
771 | 49DC4A691B14F1BC00B4E78E /* Release */,
772 | );
773 | defaultConfigurationIsVisible = 0;
774 | defaultConfigurationName = Release;
775 | };
776 | 49DC4A6A1B14F1BC00B4E78E /* Build configuration list for PBXNativeTarget "ImagePickerSheetControllerTests" */ = {
777 | isa = XCConfigurationList;
778 | buildConfigurations = (
779 | 49DC4A6B1B14F1BC00B4E78E /* Debug */,
780 | 49DC4A6C1B14F1BC00B4E78E /* Release */,
781 | );
782 | defaultConfigurationIsVisible = 0;
783 | defaultConfigurationName = Release;
784 | };
785 | 49DC4AA21B14F31500B4E78E /* Build configuration list for PBXNativeTarget "Example" */ = {
786 | isa = XCConfigurationList;
787 | buildConfigurations = (
788 | 49DC4AA31B14F31500B4E78E /* Debug */,
789 | 49DC4AA41B14F31500B4E78E /* Release */,
790 | );
791 | defaultConfigurationIsVisible = 0;
792 | defaultConfigurationName = Release;
793 | };
794 | /* End XCConfigurationList section */
795 | };
796 | rootObject = 49DC4A481B14F1BC00B4E78E /* Project object */;
797 | }
798 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController.xcodeproj/project.xcworkspace/xcshareddata/ImagePickerSheetController.xccheckout:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDESourceControlProjectFavoriteDictionaryKey
6 |
7 | IDESourceControlProjectIdentifier
8 | 9BC4CC99-E09A-4C7E-8C5C-CDAA4C28FBEB
9 | IDESourceControlProjectName
10 | ImagePickerSheetController
11 | IDESourceControlProjectOriginsDictionary
12 |
13 | 81B80757B952CD09B96E04F606EF5F58B23F2659
14 | https://github.com/larcus94/ImagePickerSheetController.git
15 |
16 | IDESourceControlProjectPath
17 | ImagePickerSheetController/ImagePickerSheetController.xcodeproj
18 | IDESourceControlProjectRelativeInstallPathDictionary
19 |
20 | 81B80757B952CD09B96E04F606EF5F58B23F2659
21 | ../../..
22 |
23 | IDESourceControlProjectURL
24 | https://github.com/larcus94/ImagePickerSheetController.git
25 | IDESourceControlProjectVersion
26 | 111
27 | IDESourceControlProjectWCCIdentifier
28 | 81B80757B952CD09B96E04F606EF5F58B23F2659
29 | IDESourceControlProjectWCConfigurations
30 |
31 |
32 | IDESourceControlRepositoryExtensionIdentifierKey
33 | public.vcs.git
34 | IDESourceControlWCCIdentifierKey
35 | 81B80757B952CD09B96E04F606EF5F58B23F2659
36 | IDESourceControlWCCName
37 | ImagePickerSheetController
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController.xcodeproj/xcshareddata/xcschemes/ImagePickerSheetController.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
43 |
49 |
50 |
51 |
52 |
53 |
59 |
60 |
62 |
68 |
69 |
70 |
71 |
72 |
78 |
79 |
80 |
81 |
82 |
83 |
93 |
94 |
100 |
101 |
102 |
103 |
104 |
105 |
111 |
112 |
118 |
119 |
120 |
121 |
123 |
124 |
127 |
128 |
129 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController.xcodeproj/xcuserdata/Laurin.xcuserdatad/xcschemes/Example.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
65 |
67 |
73 |
74 |
75 |
76 |
77 |
78 |
84 |
86 |
92 |
93 |
94 |
95 |
97 |
98 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController.xcodeproj/xcuserdata/Laurin.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | Example.xcscheme
8 |
9 | orderHint
10 | 1
11 |
12 | ImagePickerSheetController.xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 0
16 |
17 |
18 | SuppressBuildableAutocreation
19 |
20 | 49DC4A501B14F1BC00B4E78E
21 |
22 | primary
23 |
24 |
25 | 49DC4A5B1B14F1BC00B4E78E
26 |
27 | primary
28 |
29 |
30 | 49DC4A851B14F31500B4E78E
31 |
32 | primary
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController.xcodeproj/xcuserdata/patrickbalestra.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | Example.xcscheme
8 |
9 | orderHint
10 | 1
11 |
12 | ImagePickerSheetController.xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 0
16 |
17 |
18 | SuppressBuildableAutocreation
19 |
20 | 49DC4A501B14F1BC00B4E78E
21 |
22 | primary
23 |
24 |
25 | 49DC4A5B1B14F1BC00B4E78E
26 |
27 | primary
28 |
29 |
30 | 49DC4A851B14F31500B4E78E
31 |
32 | primary
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController/AnimationController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AnimationController.swift
3 | // ImagePickerSheet
4 | //
5 | // Created by Laurin Brandner on 25/05/15.
6 | // Copyright (c) 2015 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class AnimationController: NSObject {
12 |
13 | let imagePickerSheetController: ImagePickerSheetController
14 | let presenting: Bool
15 |
16 | // MARK: - Initialization
17 |
18 | init(imagePickerSheetController: ImagePickerSheetController, presenting: Bool) {
19 | self.imagePickerSheetController = imagePickerSheetController
20 | self.presenting = presenting
21 | }
22 |
23 | // MARK: - Animation
24 |
25 | fileprivate func animatePresentation(_ context: UIViewControllerContextTransitioning) {
26 | let containerView = context.containerView
27 | containerView.addSubview(imagePickerSheetController.view)
28 |
29 | let sheetOriginY = imagePickerSheetController.sheetCollectionView.frame.origin.y
30 | imagePickerSheetController.sheetCollectionView.frame.origin.y = containerView.bounds.maxY
31 | imagePickerSheetController.backgroundView.alpha = 0
32 |
33 | UIView.animate(withDuration: transitionDuration(using: context), delay: 0, options: .curveEaseOut, animations: { () -> Void in
34 | self.imagePickerSheetController.sheetCollectionView.frame.origin.y = sheetOriginY
35 | self.imagePickerSheetController.backgroundView.alpha = 1
36 | }, completion: { _ in
37 | context.completeTransition(true)
38 | })
39 | }
40 |
41 | fileprivate func animateDismissal(_ context: UIViewControllerContextTransitioning) {
42 | let containerView = context.containerView
43 |
44 | UIView.animate(withDuration: transitionDuration(using: context), delay: 0, options: .curveEaseIn, animations: { () -> Void in
45 | self.imagePickerSheetController.sheetCollectionView.frame.origin.y = containerView.bounds.maxY
46 | self.imagePickerSheetController.backgroundView.alpha = 0
47 | }, completion: { _ in
48 | self.imagePickerSheetController.view.removeFromSuperview()
49 | context.completeTransition(true)
50 | })
51 | }
52 |
53 | }
54 |
55 | // MARK: - UIViewControllerAnimatedTransitioning
56 | extension AnimationController: UIViewControllerAnimatedTransitioning {
57 |
58 | func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
59 | return 0.25
60 | }
61 |
62 | func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
63 | if presenting {
64 | animatePresentation(transitionContext)
65 | }
66 | else {
67 | animateDismissal(transitionContext)
68 | }
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController/ImagePickerAction.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImagePickerAction.swift
3 | // ImagePickerSheet
4 | //
5 | // Created by Laurin Brandner on 24/05/15.
6 | // Copyright (c) 2015 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public enum ImagePickerActionStyle {
12 | case `default`
13 | case cancel
14 | }
15 |
16 | open class ImagePickerAction {
17 |
18 | public typealias Title = (Int) -> String
19 | public typealias Handler = (ImagePickerAction) -> ()
20 | public typealias SecondaryHandler = (ImagePickerAction, Int) -> ()
21 |
22 | /// The title of the action's button.
23 | public let title: String
24 |
25 | /// The title of the action's button when more than one image is selected.
26 | public let secondaryTitle: Title
27 |
28 | /// The style of the action. This is used to call a cancel handler when dismissing the controller by tapping the background.
29 | public let style: ImagePickerActionStyle
30 |
31 | fileprivate let handler: Handler?
32 | fileprivate let secondaryHandler: SecondaryHandler?
33 |
34 | /// Initializes a new cancel ImagePickerAction
35 | public init(cancelTitle: String) {
36 | self.title = cancelTitle
37 | self.secondaryTitle = { _ in cancelTitle }
38 | self.style = .cancel
39 | self.handler = nil
40 | self.secondaryHandler = nil
41 | }
42 |
43 | /// Initializes a new ImagePickerAction. The secondary title and handler are used when at least 1 image has been selected.
44 | /// Secondary title defaults to title if not specified.
45 | /// Secondary handler defaults to handler if not specified.
46 | public convenience init(title: String, secondaryTitle rawSecondaryTitle: String? = nil, style: ImagePickerActionStyle = .default, handler: @escaping Handler, secondaryHandler: SecondaryHandler? = nil) {
47 | let secondaryTitle: Title? = rawSecondaryTitle.map { string in
48 | return { _ in string }
49 | }
50 | self.init(title: title, secondaryTitle: secondaryTitle, style: style, handler: handler, secondaryHandler: secondaryHandler)
51 | }
52 |
53 | /// Initializes a new ImagePickerAction. The secondary title and handler are used when at least 1 image has been selected.
54 | /// Secondary title defaults to title if not specified. Use the closure to format a title according to the selection.
55 | /// Secondary handler defaults to handler if not specified
56 | public init(title: String, secondaryTitle: Title?, style: ImagePickerActionStyle = .default, handler: @escaping Handler, secondaryHandler secondaryHandlerOrNil: SecondaryHandler? = nil) {
57 | var secondaryHandler = secondaryHandlerOrNil
58 | if secondaryHandler == nil {
59 | secondaryHandler = { action, _ in
60 | handler(action)
61 | }
62 | }
63 |
64 | self.title = title
65 | self.secondaryTitle = secondaryTitle ?? { _ in title }
66 | self.style = style
67 | self.handler = handler
68 | self.secondaryHandler = secondaryHandler
69 | }
70 |
71 | func handle(_ numberOfImages: Int = 0) {
72 | if numberOfImages > 0 {
73 | secondaryHandler?(self, numberOfImages)
74 | }
75 | else {
76 | handler?(self)
77 | }
78 | }
79 |
80 | }
81 |
82 | func ?? (left: ImagePickerAction.Title?, right: @escaping ImagePickerAction.Title) -> ImagePickerAction.Title {
83 | if let left = left {
84 | return left
85 | }
86 |
87 | return right
88 | }
89 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController/ImagePickerSheetController.h:
--------------------------------------------------------------------------------
1 | //
2 | // ImagePickerSheetController.h
3 | // ImagePickerSheetController
4 | //
5 | // Created by Laurin Brandner on 26/05/15.
6 | // Copyright (c) 2015 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for ImagePickerSheetController.
12 | FOUNDATION_EXPORT double ImagePickerSheetControllerVersionNumber;
13 |
14 | //! Project version string for ImagePickerSheetController.
15 | FOUNDATION_EXPORT const unsigned char ImagePickerSheetControllerVersionString[];
16 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController/ImagePickerSheetController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImagePickerController.swift
3 | // ImagePickerSheet
4 | //
5 | // Created by Laurin Brandner on 24/05/15.
6 | // Copyright (c) 2015 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Photos
11 |
12 | let previewInset: CGFloat = 5
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 ImagePickerSheetControllerDelegate {
22 |
23 | @objc optional func controllerWillEnlargePreview(_ controller: ImagePickerSheetController)
24 | @objc optional func controllerDidEnlargePreview(_ controller: ImagePickerSheetController)
25 |
26 | @objc optional func controller(_ controller: ImagePickerSheetController, willSelectAsset asset: PHAsset)
27 | @objc optional func controller(_ controller: ImagePickerSheetController, didSelectAsset asset: PHAsset)
28 |
29 | @objc optional func controller(_ controller: ImagePickerSheetController, willDeselectAsset asset: PHAsset)
30 | @objc optional func controller(_ controller: ImagePickerSheetController, didDeselectAsset asset: PHAsset)
31 |
32 | }
33 |
34 | @available(iOS 9.0, *)
35 | public final class ImagePickerSheetController: UIViewController {
36 |
37 | fileprivate lazy var sheetController: SheetController = {
38 | let controller = SheetController(previewCollectionView: self.previewCollectionView)
39 | controller.actionHandlingCallback = { [weak self] in
40 | // Possible retain cycle when action handlers hold a reference to the IPSC
41 | // Remove all actions to break it
42 | self?.dismiss(animated: true, completion: {
43 | controller.removeAllActions()
44 | })
45 | }
46 |
47 | return controller
48 | }()
49 |
50 | // self?.dismiss(animated: true, completion: { _ in
51 | // // Possible retain cycle when action handlers hold a reference to the IPSC
52 | // // Remove all actions to break it
53 | // controller.removeAllActions()
54 | // })
55 | var sheetCollectionView: UICollectionView {
56 | return sheetController.sheetCollectionView
57 | }
58 |
59 | fileprivate(set) lazy var previewCollectionView: PreviewCollectionView = {
60 | let collectionView = PreviewCollectionView()
61 | collectionView.accessibilityIdentifier = "ImagePickerSheetPreview"
62 | collectionView.backgroundColor = .clear
63 | collectionView.allowsMultipleSelection = true
64 | collectionView.imagePreviewLayout.sectionInset = UIEdgeInsets(top: previewInset, left: previewInset, bottom: previewInset, right: previewInset)
65 | collectionView.imagePreviewLayout.showsSupplementaryViews = false
66 | collectionView.dataSource = self
67 | collectionView.delegate = self
68 | collectionView.showsHorizontalScrollIndicator = false
69 | collectionView.alwaysBounceHorizontal = true
70 | collectionView.register(PreviewCollectionViewCell.self, forCellWithReuseIdentifier: NSStringFromClass(PreviewCollectionViewCell.self))
71 | collectionView.register(PreviewSupplementaryView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: NSStringFromClass(PreviewSupplementaryView.self))
72 |
73 | return collectionView
74 | }()
75 |
76 | fileprivate var supplementaryViews = [Int: PreviewSupplementaryView]()
77 |
78 | lazy var backgroundView: UIView = {
79 | let view = UIView()
80 | view.accessibilityIdentifier = "ImagePickerSheetBackground"
81 | view.backgroundColor = UIColor(white: 0.0, alpha: 0.3961)
82 | view.addGestureRecognizer(UITapGestureRecognizer(target: self.sheetController, action: #selector(SheetController.handleCancelAction)))
83 |
84 | return view
85 | }()
86 |
87 | public var delegate: ImagePickerSheetControllerDelegate?
88 |
89 | /// All the actions. The first action is shown at the top.
90 | public var actions: [ImagePickerAction] {
91 | return sheetController.actions
92 | }
93 |
94 | /// Maximum selection of images.
95 | public var maximumSelection: Int?
96 |
97 | fileprivate var selectedAssetIndices = [Int]() {
98 | didSet {
99 | sheetController.numberOfSelectedAssets = selectedAssetIndices.count
100 | }
101 | }
102 |
103 | /// The selected image assets
104 | public var selectedAssets: [PHAsset] {
105 | return selectedAssetIndices.map { self.assets[$0] }
106 | }
107 |
108 | /// The media type of the displayed assets
109 | public let mediaType: ImagePickerMediaType
110 |
111 | fileprivate var assets = [PHAsset]()
112 |
113 | fileprivate lazy var requestOptions: PHImageRequestOptions = {
114 | let options = PHImageRequestOptions()
115 | options.deliveryMode = .highQualityFormat
116 | options.resizeMode = .fast
117 |
118 | return options
119 | }()
120 |
121 | fileprivate let imageManager = PHCachingImageManager()
122 |
123 | /// Whether the image preview has been elarged. This is the case when at least once
124 | /// image has been selected.
125 | public fileprivate(set) var enlargedPreviews = false
126 |
127 | fileprivate let minimumPreviewHeight: CGFloat = 129
128 | fileprivate var maximumPreviewHeight: CGFloat = 129
129 |
130 | fileprivate var previewCheckmarkInset: CGFloat {
131 | return 12.5
132 | }
133 |
134 | // MARK: - Initialization
135 |
136 | public init(mediaType: ImagePickerMediaType) {
137 | self.mediaType = mediaType
138 | super.init(nibName: nil, bundle: nil)
139 | initialize()
140 | }
141 |
142 | public required init?(coder aDecoder: NSCoder) {
143 | self.mediaType = .imageAndVideo
144 | super.init(coder: aDecoder)
145 | initialize()
146 | }
147 |
148 | fileprivate func initialize() {
149 | modalPresentationStyle = .custom
150 | transitioningDelegate = self
151 |
152 | NotificationCenter.default.addObserver(sheetController, selector: #selector(sheetController.handleCancelAction), name: UIApplication.didEnterBackgroundNotification, object: nil)
153 | }
154 |
155 | @objc deinit {
156 | NotificationCenter.default.removeObserver(sheetController, name: UIApplication.didEnterBackgroundNotification, object: nil)
157 | }
158 |
159 | // MARK: - View Lifecycle
160 |
161 | override public func loadView() {
162 | super.loadView()
163 |
164 | view.addSubview(backgroundView)
165 | view.addSubview(sheetCollectionView)
166 | }
167 |
168 | override public func viewWillAppear(_ animated: Bool) {
169 | super.viewWillAppear(animated)
170 |
171 | preferredContentSize = CGSize(width: 400, height: view.frame.height)
172 |
173 | if PHPhotoLibrary.authorizationStatus() == .authorized {
174 | prepareAssets()
175 | }
176 | }
177 |
178 | override public func viewDidAppear(_ animated: Bool) {
179 | super.viewDidAppear(animated)
180 |
181 | if PHPhotoLibrary.authorizationStatus() == .notDetermined {
182 | PHPhotoLibrary.requestAuthorization() { status in
183 | if status == .authorized {
184 | DispatchQueue.main.async {
185 | self.prepareAssets()
186 | self.previewCollectionView.reloadData()
187 | self.sheetCollectionView.reloadData()
188 | self.view.setNeedsLayout()
189 |
190 | // Explicitely disable animations so it wouldn't animate either
191 | // if it was in a popover
192 | CATransaction.begin()
193 | CATransaction.setDisableActions(true)
194 | self.view.layoutIfNeeded()
195 | CATransaction.commit()
196 | }
197 | }
198 | }
199 | }
200 | }
201 |
202 | // MARK: - Actions
203 |
204 | /// Adds an new action.
205 | /// If the passed action is of type Cancel, any pre-existing Cancel actions will be removed.
206 | /// Always arranges the actions so that the Cancel action appears at the bottom.
207 | public func addAction(_ action: ImagePickerAction) {
208 | sheetController.addAction(action)
209 | view.setNeedsLayout()
210 | }
211 |
212 | // MARK: - Images
213 |
214 | fileprivate func sizeForAsset(_ asset: PHAsset, scale: CGFloat = 1) -> CGSize {
215 | let proportion = CGFloat(asset.pixelWidth)/CGFloat(asset.pixelHeight)
216 |
217 | let imageHeight = maximumPreviewHeight - 2 * previewInset
218 | let imageWidth = floor(proportion * imageHeight)
219 |
220 | return CGSize(width: imageWidth * scale, height: imageHeight * scale)
221 | }
222 |
223 | fileprivate func prepareAssets() {
224 | fetchAssets()
225 | reloadMaximumPreviewHeight()
226 | reloadCurrentPreviewHeight(invalidateLayout: false)
227 |
228 | // Filter out the assets that are too thin. This can't be done before because
229 | // we don't know how tall the images should be
230 | let minImageWidth = 2 * previewCheckmarkInset + (PreviewSupplementaryView.checkmarkImage?.size.width ?? 0)
231 | assets = assets.filter { asset in
232 | let size = sizeForAsset(asset)
233 | return size.width >= minImageWidth
234 | }
235 | }
236 |
237 | fileprivate func fetchAssets() {
238 | let options = PHFetchOptions()
239 | options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
240 |
241 | switch mediaType {
242 | case .image:
243 | options.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.image.rawValue)
244 | case .video:
245 | options.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.video.rawValue)
246 | case .imageAndVideo:
247 | options.predicate = NSPredicate(format: "mediaType = %d OR mediaType = %d", PHAssetMediaType.image.rawValue, PHAssetMediaType.video.rawValue)
248 | }
249 |
250 | let fetchLimit = 50
251 | options.fetchLimit = fetchLimit
252 |
253 | let result = PHAsset.fetchAssets(with: options)
254 | let requestOptions = PHImageRequestOptions()
255 | requestOptions.isSynchronous = true
256 | requestOptions.deliveryMode = .fastFormat
257 |
258 | result.enumerateObjects(options: [], using: { asset, index, stop in
259 | defer {
260 | if self.assets.count > fetchLimit {
261 | stop.initialize(to: true)
262 | }
263 | }
264 |
265 | self.imageManager.requestImageData(for: asset, options: requestOptions) { data, _, _, info in
266 | if data != nil {
267 | self.assets.append(asset)
268 | }
269 | }
270 | })
271 | }
272 |
273 | fileprivate func requestImageForAsset(_ asset: PHAsset, completion: @escaping (_ image: UIImage?) -> ()) {
274 | let targetSize = sizeForAsset(asset, scale: UIScreen.main.scale)
275 | requestOptions.isSynchronous = true
276 |
277 | // Workaround because PHImageManager.requestImageForAsset doesn't work for burst images
278 | if asset.representsBurst {
279 | imageManager.requestImageData(for: asset, options: requestOptions) { data, _, _, _ in
280 | let image = data.flatMap { UIImage(data: $0) }
281 | completion(image)
282 | }
283 | }
284 | else {
285 | imageManager.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFill, options: requestOptions) { image, _ in
286 | completion(image)
287 | }
288 | }
289 | }
290 |
291 | fileprivate func prefetchImagesForAsset(_ asset: PHAsset) {
292 | let targetSize = sizeForAsset(asset, scale: UIScreen.main.scale)
293 | imageManager.startCachingImages(for: [asset], targetSize: targetSize, contentMode: .aspectFill, options: requestOptions)
294 | }
295 |
296 | // MARK: - Layout
297 |
298 | override public func viewDidLayoutSubviews() {
299 | super.viewDidLayoutSubviews()
300 |
301 | if popoverPresentationController == nil {
302 | // Offset necessary for expanded status bar
303 | // Bug in UIKit which doesn't reset the view's frame correctly
304 |
305 | let offset = UIApplication.shared.statusBarFrame.height
306 | var backgroundViewFrame = UIScreen.main.bounds
307 | backgroundViewFrame.origin.y = -offset
308 | backgroundViewFrame.size.height += offset
309 | backgroundView.frame = backgroundViewFrame
310 | }
311 | else {
312 | backgroundView.frame = view.bounds
313 | }
314 |
315 | reloadMaximumPreviewHeight()
316 | reloadCurrentPreviewHeight(invalidateLayout: true)
317 |
318 | let sheetHeight = sheetController.preferredSheetHeight
319 | let sheetSize = CGSize(width: view.bounds.width, height: sheetHeight)
320 |
321 | // This particular order is necessary so that the sheet is layed out
322 | // correctly with and without an enclosing popover
323 | preferredContentSize = sheetSize
324 | sheetCollectionView.frame = CGRect(origin: CGPoint(x: view.bounds.minX, y: view.bounds.maxY - view.frame.origin.y - sheetHeight), size: sheetSize)
325 | }
326 |
327 | fileprivate func reloadCurrentPreviewHeight(invalidateLayout invalidate: Bool) {
328 | if assets.count <= 0 {
329 | sheetController.setPreviewHeight(0, invalidateLayout: invalidate)
330 | }
331 | else if assets.count > 0 && enlargedPreviews {
332 | sheetController.setPreviewHeight(maximumPreviewHeight, invalidateLayout: invalidate)
333 | }
334 | else {
335 | sheetController.setPreviewHeight(minimumPreviewHeight, invalidateLayout: invalidate)
336 | }
337 | }
338 |
339 | fileprivate func reloadMaximumPreviewHeight() {
340 | let maxHeight: CGFloat = 400
341 | let maxImageWidth = view.bounds.width - 2 * sheetInset - 2 * previewInset
342 |
343 | let assetRatios = assets.map { (asset: PHAsset) -> CGSize in
344 | CGSize(width: max(asset.pixelHeight, asset.pixelWidth), height: min(asset.pixelHeight, asset.pixelWidth))
345 | }.map { (size: CGSize) -> CGFloat in
346 | size.height / size.width
347 | }
348 |
349 | let assetHeights = assetRatios.map { (ratio: CGFloat) -> CGFloat in ratio * maxImageWidth }
350 | .filter { (height: CGFloat) -> Bool in height < maxImageWidth && height < maxHeight } // Make sure the preview isn't too high eg for squares
351 | .sorted(by: >)
352 | let assetHeight: CGFloat
353 | if let first = assetHeights.first {
354 | assetHeight = first
355 | }
356 | else {
357 | assetHeight = 0
358 | }
359 |
360 | // Just a sanity check, to make sure this doesn't exceed 400 points
361 | let scaledHeight: CGFloat = min(assetHeight, maxHeight)
362 | maximumPreviewHeight = scaledHeight + 2 * previewInset
363 | }
364 |
365 | // MARK: -
366 |
367 | func enlargePreviewsByCenteringToIndexPath(_ indexPath: IndexPath?, completion: (() -> ())?) {
368 | enlargedPreviews = true
369 | previewCollectionView.imagePreviewLayout.invalidationCenteredIndexPath = indexPath
370 | reloadCurrentPreviewHeight(invalidateLayout: false)
371 |
372 | view.setNeedsLayout()
373 |
374 | self.delegate?.controllerWillEnlargePreview?(self)
375 |
376 | UIView.animate(withDuration: 0.2, animations: {
377 | self.view.layoutIfNeeded()
378 | self.sheetCollectionView.collectionViewLayout.invalidateLayout()
379 | }, completion: { _ in
380 | self.delegate?.controllerDidEnlargePreview?(self)
381 |
382 | completion?()
383 | })
384 | }
385 |
386 | }
387 |
388 | // MARK: - UICollectionViewDataSource
389 |
390 | extension ImagePickerSheetController: UICollectionViewDataSource {
391 |
392 | public func numberOfSections(in collectionView: UICollectionView) -> Int {
393 | return assets.count
394 | }
395 |
396 | public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
397 | return 1
398 | }
399 |
400 | public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
401 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NSStringFromClass(PreviewCollectionViewCell.self), for: indexPath) as! PreviewCollectionViewCell
402 |
403 | let asset = assets[indexPath.section]
404 | cell.videoIndicatorView.isHidden = asset.mediaType != .video
405 |
406 | requestImageForAsset(asset) { image in
407 | cell.imageView.image = image
408 | }
409 |
410 | cell.isSelected = selectedAssetIndices.contains(indexPath.section)
411 |
412 | return cell
413 | }
414 |
415 | public func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath:
416 | IndexPath) -> UICollectionReusableView {
417 | let view = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: NSStringFromClass(PreviewSupplementaryView.self), for: indexPath) as! PreviewSupplementaryView
418 | view.isUserInteractionEnabled = false
419 | view.buttonInset = UIEdgeInsets(top: 0.0, left: previewCheckmarkInset, bottom: previewCheckmarkInset, right: 0.0)
420 | view.selected = selectedAssetIndices.contains(indexPath.section)
421 |
422 | supplementaryViews[indexPath.section] = view
423 |
424 | return view
425 | }
426 |
427 | }
428 |
429 | // MARK: - UICollectionViewDelegate
430 |
431 | extension ImagePickerSheetController: UICollectionViewDelegate {
432 |
433 | public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
434 | if let maximumSelection = maximumSelection {
435 | if selectedAssetIndices.count >= maximumSelection,
436 | let previousItemIndex = selectedAssetIndices.first {
437 | let deselectedAsset = assets[previousItemIndex]
438 | delegate?.controller?(self, willDeselectAsset: deselectedAsset)
439 |
440 | supplementaryViews[previousItemIndex]?.selected = false
441 | selectedAssetIndices.remove(at: 0)
442 |
443 | delegate?.controller?(self, didDeselectAsset: deselectedAsset)
444 | }
445 | }
446 |
447 | let selectedAsset = assets[indexPath.section]
448 | delegate?.controller?(self, willSelectAsset: selectedAsset)
449 |
450 | // Just to make sure the image is only selected once
451 | selectedAssetIndices = selectedAssetIndices.filter { $0 != indexPath.section }
452 | selectedAssetIndices.append(indexPath.section)
453 |
454 | if !enlargedPreviews {
455 | enlargePreviewsByCenteringToIndexPath(indexPath) {
456 | self.sheetController.reloadActionItems()
457 | self.previewCollectionView.imagePreviewLayout.showsSupplementaryViews = true
458 | }
459 | }
460 | else {
461 | // scrollToItemAtIndexPath doesn't work reliably
462 | if let cell = collectionView.cellForItem(at: indexPath) {
463 | var contentOffset = CGPoint(x: cell.frame.midX - collectionView.frame.width / 2.0, y: 0.0)
464 | contentOffset.x = max(contentOffset.x, -collectionView.contentInset.left)
465 | contentOffset.x = min(contentOffset.x, collectionView.contentSize.width - collectionView.frame.width + collectionView.contentInset.right)
466 |
467 | collectionView.setContentOffset(contentOffset, animated: true)
468 | }
469 |
470 | sheetController.reloadActionItems()
471 | }
472 |
473 | supplementaryViews[indexPath.section]?.selected = true
474 |
475 | delegate?.controller?(self, didSelectAsset: selectedAsset)
476 | }
477 |
478 | public func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
479 | if let index = selectedAssetIndices.index(of: indexPath.section) {
480 | let deselectedAsset = selectedAssets[index]
481 | delegate?.controller?(self, willDeselectAsset: deselectedAsset)
482 |
483 | selectedAssetIndices.remove(at: index)
484 | sheetController.reloadActionItems()
485 |
486 | delegate?.controller?(self, didDeselectAsset: deselectedAsset)
487 | }
488 |
489 | supplementaryViews[indexPath.section]?.selected = false
490 | }
491 |
492 | }
493 |
494 | // MARK: - UICollectionViewDelegateFlowLayout
495 |
496 | extension ImagePickerSheetController: UICollectionViewDelegateFlowLayout {
497 |
498 | public func collectionView(_ collectionView: UICollectionView, layout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
499 | let asset = assets[indexPath.section]
500 | let size = sizeForAsset(asset)
501 |
502 | // Scale down to the current preview height, sizeForAsset returns the original size
503 | let currentImagePreviewHeight = sheetController.previewHeight - 2 * previewInset
504 | let scale = currentImagePreviewHeight / size.height
505 |
506 | return CGSize(width: size.width * scale, height: currentImagePreviewHeight)
507 | }
508 |
509 | public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
510 | let checkmarkWidth = PreviewSupplementaryView.checkmarkImage?.size.width ?? 0
511 | return CGSize(width: checkmarkWidth + 2 * previewCheckmarkInset, height: sheetController.previewHeight - 2 * previewInset)
512 | }
513 |
514 | }
515 |
516 | // MARK: - UIViewControllerTransitioningDelegate
517 |
518 | extension ImagePickerSheetController: UIViewControllerTransitioningDelegate {
519 |
520 | public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
521 | return AnimationController(imagePickerSheetController: self, presenting: true)
522 | }
523 |
524 | public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
525 | return AnimationController(imagePickerSheetController: self, presenting: false)
526 | }
527 |
528 | }
529 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController/Images.xcassets/PreviewCollectionViewCell-video.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "PreviewCollectionViewCell-video.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "PreviewCollectionViewCell-video@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "PreviewCollectionViewCell-video@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController/Images.xcassets/PreviewCollectionViewCell-video.imageset/PreviewCollectionViewCell-video.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerSheetController/c57fa10ad847e29cabfa90a60baffca3c67474b8/ImagePickerSheetController/ImagePickerSheetController/Images.xcassets/PreviewCollectionViewCell-video.imageset/PreviewCollectionViewCell-video.png
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController/Images.xcassets/PreviewCollectionViewCell-video.imageset/PreviewCollectionViewCell-video@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerSheetController/c57fa10ad847e29cabfa90a60baffca3c67474b8/ImagePickerSheetController/ImagePickerSheetController/Images.xcassets/PreviewCollectionViewCell-video.imageset/PreviewCollectionViewCell-video@2x.png
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController/Images.xcassets/PreviewCollectionViewCell-video.imageset/PreviewCollectionViewCell-video@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerSheetController/c57fa10ad847e29cabfa90a60baffca3c67474b8/ImagePickerSheetController/ImagePickerSheetController/Images.xcassets/PreviewCollectionViewCell-video.imageset/PreviewCollectionViewCell-video@3x.png
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController/Images.xcassets/PreviewSupplementaryView-Checkmark-Selected.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x",
6 | "filename" : "PreviewSupplementaryView-Checkmark-Selected.png"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x",
11 | "filename" : "PreviewSupplementaryView-Checkmark-Selected@2x.png"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x",
16 | "filename" : "PreviewSupplementaryView-Checkmark-Selected@3x.png"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController/Images.xcassets/PreviewSupplementaryView-Checkmark-Selected.imageset/PreviewSupplementaryView-Checkmark-Selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerSheetController/c57fa10ad847e29cabfa90a60baffca3c67474b8/ImagePickerSheetController/ImagePickerSheetController/Images.xcassets/PreviewSupplementaryView-Checkmark-Selected.imageset/PreviewSupplementaryView-Checkmark-Selected.png
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController/Images.xcassets/PreviewSupplementaryView-Checkmark-Selected.imageset/PreviewSupplementaryView-Checkmark-Selected@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerSheetController/c57fa10ad847e29cabfa90a60baffca3c67474b8/ImagePickerSheetController/ImagePickerSheetController/Images.xcassets/PreviewSupplementaryView-Checkmark-Selected.imageset/PreviewSupplementaryView-Checkmark-Selected@2x.png
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController/Images.xcassets/PreviewSupplementaryView-Checkmark-Selected.imageset/PreviewSupplementaryView-Checkmark-Selected@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerSheetController/c57fa10ad847e29cabfa90a60baffca3c67474b8/ImagePickerSheetController/ImagePickerSheetController/Images.xcassets/PreviewSupplementaryView-Checkmark-Selected.imageset/PreviewSupplementaryView-Checkmark-Selected@3x.png
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController/Images.xcassets/PreviewSupplementaryView-Checkmark.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x",
6 | "filename" : "PreviewSupplementaryView-Checkmark.png"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x",
11 | "filename" : "PreviewSupplementaryView-Checkmark@2x.png"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x",
16 | "filename" : "PreviewSupplementaryView-Checkmark@3x.png"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController/Images.xcassets/PreviewSupplementaryView-Checkmark.imageset/PreviewSupplementaryView-Checkmark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerSheetController/c57fa10ad847e29cabfa90a60baffca3c67474b8/ImagePickerSheetController/ImagePickerSheetController/Images.xcassets/PreviewSupplementaryView-Checkmark.imageset/PreviewSupplementaryView-Checkmark.png
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController/Images.xcassets/PreviewSupplementaryView-Checkmark.imageset/PreviewSupplementaryView-Checkmark@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerSheetController/c57fa10ad847e29cabfa90a60baffca3c67474b8/ImagePickerSheetController/ImagePickerSheetController/Images.xcassets/PreviewSupplementaryView-Checkmark.imageset/PreviewSupplementaryView-Checkmark@2x.png
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController/Images.xcassets/PreviewSupplementaryView-Checkmark.imageset/PreviewSupplementaryView-Checkmark@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerSheetController/c57fa10ad847e29cabfa90a60baffca3c67474b8/ImagePickerSheetController/ImagePickerSheetController/Images.xcassets/PreviewSupplementaryView-Checkmark.imageset/PreviewSupplementaryView-Checkmark@3x.png
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController/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 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController/Sheet/Preview/PreviewCollectionView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PreviewCollectionView.swift
3 | // ImagePickerSheet
4 | //
5 | // Created by Laurin Brandner on 07/09/14.
6 | // Copyright (c) 2014 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class PreviewCollectionView: UICollectionView {
12 |
13 | var bouncing: Bool {
14 | if contentOffset.x < -contentInset.left { return true }
15 | if contentOffset.x + frame.width > contentSize.width + contentInset.right { return true }
16 | return false
17 | }
18 |
19 | var imagePreviewLayout: PreviewCollectionViewLayout {
20 | return collectionViewLayout as! PreviewCollectionViewLayout
21 | }
22 |
23 | // MARK: - Initialization
24 |
25 | init() {
26 | super.init(frame: .zero, collectionViewLayout: PreviewCollectionViewLayout())
27 |
28 | initialize()
29 | }
30 |
31 | required init?(coder aDecoder: NSCoder) {
32 | super.init(coder: aDecoder)
33 | initialize()
34 | }
35 |
36 | fileprivate func initialize() {
37 | panGestureRecognizer.addTarget(self, action: #selector(PreviewCollectionView.handlePanGesture(gestureRecognizer:)))
38 | }
39 |
40 | // MARK: - Panning
41 |
42 | @objc private func handlePanGesture(gestureRecognizer: UIPanGestureRecognizer) {
43 | if gestureRecognizer.state == .ended {
44 | let translation = gestureRecognizer.translation(in: self)
45 | if translation == CGPoint() {
46 | if !bouncing {
47 | let possibleIndexPath = indexPathForItem(at: gestureRecognizer.location(in: self))
48 | if let indexPath = possibleIndexPath {
49 | selectItem(at: indexPath, animated: false, scrollPosition: [])
50 | delegate?.collectionView?(self, didSelectItemAt: indexPath)
51 | }
52 | }
53 | }
54 | }
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController/Sheet/Preview/PreviewCollectionViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PreviewCollectionViewCell.swift
3 | // ImagePickerSheet
4 | //
5 | // Created by Laurin Brandner on 06/09/14.
6 | // Copyright (c) 2014 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class PreviewCollectionViewCell: 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 | let videoIndicatorView: UIImageView = {
22 | let imageView = UIImageView(image: videoImage)
23 | imageView.isHidden = true
24 |
25 | return imageView
26 | }()
27 |
28 | fileprivate class var videoImage: UIImage? {
29 | let bundle = Bundle(for: ImagePickerSheetController.self)
30 | let image = UIImage(named: "PreviewCollectionViewCell-video", in: bundle, compatibleWith: nil)
31 |
32 | return image
33 | }
34 |
35 | // MARK: - Initialization
36 |
37 | override init(frame: CGRect) {
38 | super.init(frame: frame)
39 |
40 | initialize()
41 | }
42 |
43 | required init?(coder aDecoder: NSCoder) {
44 | super.init(coder: aDecoder)
45 |
46 | initialize()
47 | }
48 |
49 | fileprivate func initialize() {
50 | addSubview(imageView)
51 | addSubview(videoIndicatorView)
52 | }
53 |
54 | // MARK: - Other Methods
55 |
56 | override func prepareForReuse() {
57 | super.prepareForReuse()
58 |
59 | imageView.image = nil
60 | videoIndicatorView.isHidden = true
61 | }
62 |
63 | // MARK: - Layout
64 |
65 | override func layoutSubviews() {
66 | super.layoutSubviews()
67 |
68 | imageView.frame = bounds
69 |
70 | let videoIndicatViewSize = videoIndicatorView.image?.size ?? CGSize()
71 | let inset: CGFloat = 4
72 | let videoIndicatorViewOrigin = CGPoint(x: bounds.minX + inset, y: bounds.maxY - inset - videoIndicatViewSize.height)
73 | videoIndicatorView.frame = CGRect(origin: videoIndicatorViewOrigin, size: videoIndicatViewSize)
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController/Sheet/Preview/PreviewCollectionViewLayout.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PreviewCollectionViewLayout.swift
3 | // ImagePickerSheet
4 | //
5 | // Created by Laurin Brandner on 06/09/14.
6 | // Copyright (c) 2014 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class PreviewCollectionViewLayout: UICollectionViewFlowLayout {
12 |
13 | var invalidationCenteredIndexPath: IndexPath?
14 |
15 | var showsSupplementaryViews: Bool = true {
16 | didSet {
17 | invalidateLayout()
18 | }
19 | }
20 |
21 | fileprivate var layoutAttributes = [UICollectionViewLayoutAttributes]()
22 | fileprivate var contentSize = CGSize.zero
23 |
24 | // MARK: - Initialization
25 |
26 | override init() {
27 | super.init()
28 |
29 | initialize()
30 | }
31 |
32 | required init?(coder aDecoder: NSCoder) {
33 | super.init(coder: aDecoder)
34 |
35 | initialize()
36 | }
37 |
38 | fileprivate func initialize() {
39 | scrollDirection = .horizontal
40 | }
41 |
42 | // MARK: - Layout
43 |
44 | override func prepare() {
45 | super.prepare()
46 |
47 | layoutAttributes.removeAll(keepingCapacity: false)
48 | contentSize = CGSize.zero
49 |
50 | if let collectionView = collectionView,
51 | let delegate = collectionView.delegate as? UICollectionViewDelegateFlowLayout {
52 | var origin = CGPoint(x: sectionInset.left, y: sectionInset.top)
53 | let numberOfSections = collectionView.numberOfSections
54 |
55 | for s in 0 ..< numberOfSections {
56 | guard collectionView.numberOfItems(inSection: s) > 0
57 | else { continue }
58 |
59 | let indexPath = IndexPath(item: 0, section: s)
60 | let size = delegate.collectionView?(collectionView, layout: self, sizeForItemAt: indexPath) ?? CGSize.zero
61 |
62 | let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
63 | attributes.frame = CGRect(origin: origin, size: size)
64 | attributes.zIndex = 0
65 |
66 | layoutAttributes.append(attributes)
67 |
68 | origin.x = attributes.frame.maxX + sectionInset.right
69 | }
70 |
71 | contentSize = CGSize(width: origin.x, height: collectionView.frame.height)
72 | }
73 | }
74 |
75 | override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
76 | return true
77 | }
78 |
79 | override var collectionViewContentSize : CGSize {
80 | return contentSize
81 | }
82 |
83 | override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
84 | var contentOffset = proposedContentOffset
85 | if let indexPath = invalidationCenteredIndexPath {
86 | if let collectionView = collectionView {
87 | let frame = layoutAttributes[indexPath.section].frame
88 | contentOffset.x = frame.midX - collectionView.frame.width / 2.0
89 |
90 | contentOffset.x = max(contentOffset.x, -collectionView.contentInset.left)
91 | contentOffset.x = min(contentOffset.x, collectionViewContentSize.width - collectionView.frame.width + collectionView.contentInset.right)
92 | }
93 | invalidationCenteredIndexPath = nil
94 | }
95 |
96 | return super.targetContentOffset(forProposedContentOffset: contentOffset)
97 | }
98 |
99 | override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
100 | return layoutAttributes
101 | .filter { rect.intersects($0.frame) }
102 | .reduce([UICollectionViewLayoutAttributes]()) { memo, attributes in
103 | if let supplementaryAttributes = layoutAttributesForSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, at: attributes.indexPath) {
104 | return memo + [attributes, supplementaryAttributes]
105 | }
106 | return memo
107 | }
108 | }
109 |
110 | override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
111 | return layoutAttributes[indexPath.section]
112 | }
113 |
114 | override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
115 | if let collectionView = collectionView,
116 | let delegate = collectionView.delegate as? UICollectionViewDelegateFlowLayout,
117 | let itemAttributes = layoutAttributesForItem(at: indexPath) {
118 |
119 | let inset = collectionView.contentInset
120 | let bounds = collectionView.bounds
121 | let contentOffset: CGPoint = {
122 | var contentOffset = collectionView.contentOffset
123 | contentOffset.x += inset.left
124 | contentOffset.y += inset.top
125 |
126 | return contentOffset
127 | }()
128 | let visibleSize: CGSize = {
129 | var size = bounds.size
130 | size.width -= (inset.left+inset.right)
131 |
132 | return size
133 | }()
134 | let visibleFrame = CGRect(origin: contentOffset, size: visibleSize)
135 |
136 | let size = delegate.collectionView?(collectionView, layout: self, referenceSizeForHeaderInSection: indexPath.section) ?? CGSize.zero
137 | let originX = max(itemAttributes.frame.minX, min(itemAttributes.frame.maxX - size.width, visibleFrame.maxX - size.width))
138 |
139 | let attributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: elementKind, with: indexPath)
140 | attributes.zIndex = 1
141 | attributes.isHidden = !showsSupplementaryViews
142 | attributes.frame = CGRect(origin: CGPoint(x: originX, y: itemAttributes.frame.minY), size: size)
143 |
144 | return attributes
145 | }
146 |
147 | return nil
148 | }
149 |
150 | override func initialLayoutAttributesForAppearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
151 | return layoutAttributesForItem(at: itemIndexPath)
152 | }
153 |
154 | override func finalLayoutAttributesForDisappearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
155 | return layoutAttributesForItem(at: itemIndexPath)
156 | }
157 |
158 | }
159 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController/Sheet/Preview/PreviewSupplementaryView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PreviewSupplementaryView.swift
3 | // ImagePickerSheet
4 | //
5 | // Created by Laurin Brandner on 06/09/14.
6 | // Copyright (c) 2014 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class PreviewSupplementaryView: UICollectionReusableView {
12 |
13 | fileprivate let button: UIButton = {
14 | let button = UIButton()
15 | button.tintColor = .white
16 | button.isUserInteractionEnabled = false
17 | button.setImage(PreviewSupplementaryView.checkmarkImage, for: UIControl.State())
18 | button.setImage(PreviewSupplementaryView.selectedCheckmarkImage, for: .selected)
19 |
20 | return button
21 | }()
22 |
23 | var buttonInset = UIEdgeInsets.zero
24 |
25 | var selected: Bool = false {
26 | didSet {
27 | button.isSelected = selected
28 | reloadButtonBackgroundColor()
29 | }
30 | }
31 |
32 | class var checkmarkImage: UIImage? {
33 | let bundle = Bundle(for: ImagePickerSheetController.self)
34 | let image = UIImage(named: "PreviewSupplementaryView-Checkmark", in: bundle, compatibleWith: nil)
35 |
36 | return image?.withRenderingMode(.alwaysTemplate)
37 | }
38 |
39 | class var selectedCheckmarkImage: UIImage? {
40 | let bundle = Bundle(for: ImagePickerSheetController.self)
41 | let image = UIImage(named: "PreviewSupplementaryView-Checkmark-Selected", in: bundle, compatibleWith: nil)
42 |
43 | return image?.withRenderingMode(.alwaysTemplate)
44 | }
45 |
46 | // MARK: - Initialization
47 |
48 | override init(frame: CGRect) {
49 | super.init(frame: frame)
50 |
51 | initialize()
52 | }
53 |
54 | required init?(coder aDecoder: NSCoder) {
55 | super.init(coder: aDecoder)
56 |
57 | initialize()
58 | }
59 |
60 | fileprivate func initialize() {
61 | addSubview(button)
62 | }
63 |
64 | // MARK: - Other Methods
65 |
66 | override func prepareForReuse() {
67 | super.prepareForReuse()
68 |
69 | selected = false
70 | }
71 |
72 | override func tintColorDidChange() {
73 | super.tintColorDidChange()
74 |
75 | reloadButtonBackgroundColor()
76 | }
77 |
78 | fileprivate func reloadButtonBackgroundColor() {
79 | button.backgroundColor = (selected) ? tintColor : nil
80 | }
81 |
82 | // MARK: - Layout
83 |
84 | override func layoutSubviews() {
85 | super.layoutSubviews()
86 |
87 | button.sizeToFit()
88 | button.frame.origin = CGPoint(x: buttonInset.left, y: bounds.height-button.frame.height-buttonInset.bottom)
89 | button.layer.cornerRadius = button.frame.height / 2.0
90 | }
91 |
92 | }
93 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController/Sheet/SheetActionCollectionViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SheetActionCollectionViewCell.swift
3 | // ImagePickerSheetController
4 | //
5 | // Created by Laurin Brandner on 26/08/15.
6 | // Copyright © 2015 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | private var KVOContext = 0
12 |
13 | class SheetActionCollectionViewCell: SheetCollectionViewCell {
14 |
15 | lazy fileprivate(set) var textLabel: UILabel = {
16 | let label = UILabel()
17 | label.textColor = self.tintColor
18 | label.textAlignment = .center
19 |
20 | self.addSubview(label)
21 |
22 | return label
23 | }()
24 |
25 | // MARK: - Initialization
26 |
27 | override init(frame: CGRect) {
28 | super.init(frame: frame)
29 | initialize()
30 | }
31 |
32 | required init?(coder aDecoder: NSCoder) {
33 | super.init(coder: aDecoder)
34 | initialize()
35 | }
36 |
37 | fileprivate func initialize() {
38 | textLabel.addObserver(self, forKeyPath: "text", options: NSKeyValueObservingOptions(rawValue: 0), context: &KVOContext)
39 | }
40 |
41 | deinit {
42 | textLabel.removeObserver(self, forKeyPath: "text")
43 | }
44 |
45 | // MARK: - Accessibility
46 |
47 | override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
48 | guard context == &KVOContext else {
49 | super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
50 | return
51 | }
52 |
53 | accessibilityLabel = textLabel.text
54 | }
55 |
56 | // MARK: -
57 |
58 | override func tintColorDidChange() {
59 | super.tintColorDidChange()
60 |
61 | textLabel.textColor = tintColor
62 | }
63 |
64 | override func layoutSubviews() {
65 | super.layoutSubviews()
66 | textLabel.frame = bounds.inset(by: backgroundInsets)
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController/Sheet/SheetCollectionViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SheetCollectionViewCell.swift
3 | // ImagePickerSheetController
4 | //
5 | // Created by Laurin Brandner on 24/08/15.
6 | // Copyright © 2015 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | enum RoundedCorner {
12 | case all(CGFloat)
13 | case top(CGFloat)
14 | case bottom(CGFloat)
15 | case none
16 | }
17 |
18 | class SheetCollectionViewCell: UICollectionViewCell {
19 |
20 | var backgroundInsets = UIEdgeInsets() {
21 | didSet {
22 | reloadMask()
23 | reloadSeparator()
24 | setNeedsLayout()
25 | }
26 | }
27 |
28 | var roundedCorners = RoundedCorner.none {
29 | didSet {
30 | reloadMask()
31 | }
32 | }
33 |
34 | var separatorVisible = false {
35 | didSet {
36 | reloadSeparator()
37 | }
38 | }
39 |
40 | var separatorColor = UIColor.black {
41 | didSet {
42 | separatorView?.backgroundColor = separatorColor
43 | }
44 | }
45 |
46 | var separatorHeight: CGFloat = 1 {
47 | didSet {
48 | setNeedsLayout()
49 | }
50 | }
51 |
52 | fileprivate var separatorView: UIView?
53 |
54 | override var isHighlighted: Bool {
55 | didSet {
56 | reloadBackgroundColor()
57 | }
58 | }
59 |
60 | var highlightedBackgroundColor: UIColor = .clear {
61 | didSet {
62 | reloadBackgroundColor()
63 | }
64 | }
65 |
66 | var normalBackgroundColor: UIColor = .clear {
67 | didSet {
68 | reloadBackgroundColor()
69 | }
70 | }
71 |
72 | fileprivate var needsMasking: Bool {
73 | guard backgroundInsets == UIEdgeInsets() else {
74 | return true
75 | }
76 |
77 | switch roundedCorners {
78 | case .none:
79 | return false
80 | default:
81 | return true
82 | }
83 | }
84 |
85 | // MARK: - Initialization
86 |
87 | override init(frame: CGRect) {
88 | super.init(frame: frame)
89 | initialize()
90 | }
91 |
92 | required init?(coder aDecoder: NSCoder) {
93 | super.init(coder: aDecoder)
94 | initialize()
95 | }
96 |
97 | fileprivate func initialize() {
98 | layoutMargins = UIEdgeInsets()
99 | }
100 |
101 | // MARK: - Layout
102 |
103 | override func layoutSubviews() {
104 | super.layoutSubviews()
105 |
106 | reloadMask()
107 |
108 | separatorView?.frame = CGRect(x: bounds.minY, y: bounds.maxY - separatorHeight, width: bounds.width, height: separatorHeight)
109 | }
110 |
111 | // MARK: - Mask
112 |
113 | fileprivate func reloadMask() {
114 | if needsMasking && layer.mask == nil {
115 | let maskLayer = CAShapeLayer()
116 | maskLayer.frame = bounds
117 | maskLayer.lineWidth = 0
118 | maskLayer.fillColor = UIColor.black.cgColor
119 |
120 | layer.mask = maskLayer
121 | }
122 |
123 | let layerMask = layer.mask as? CAShapeLayer
124 | layerMask?.frame = bounds
125 | layerMask?.path = maskPathWithRect(bounds.inset(by: backgroundInsets), roundedCorner: roundedCorners)
126 | }
127 |
128 | fileprivate func maskPathWithRect(_ rect: CGRect, roundedCorner: RoundedCorner) -> CGPath {
129 | let radii: CGFloat
130 | let corners: UIRectCorner
131 |
132 | switch roundedCorner {
133 | case .all(let value):
134 | corners = .allCorners
135 | radii = value
136 | case .top(let value):
137 | corners = [.topLeft, .topRight]
138 | radii = value
139 | case .bottom(let value):
140 | corners = [.bottomLeft, .bottomRight]
141 | radii = value
142 | case .none:
143 | return UIBezierPath(rect: rect).cgPath
144 | }
145 |
146 | return UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radii, height: radii)).cgPath
147 | }
148 |
149 | // MARK: - Separator
150 |
151 | fileprivate func reloadSeparator() {
152 | if separatorVisible && backgroundInsets.bottom < separatorHeight {
153 | if separatorView == nil {
154 | let view = UIView()
155 | view.backgroundColor = separatorColor
156 |
157 | addSubview(view)
158 | separatorView = view
159 | }
160 | }
161 | else {
162 | separatorView?.removeFromSuperview()
163 | separatorView = nil
164 | }
165 | }
166 |
167 | // MARK - Background
168 |
169 | fileprivate func reloadBackgroundColor() {
170 | backgroundColor = isHighlighted ? highlightedBackgroundColor : normalBackgroundColor
171 | }
172 |
173 | }
174 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController/Sheet/SheetCollectionViewLayout.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SheetCollectionViewLayout.swift
3 | // ImagePickerSheetController
4 | //
5 | // Created by Laurin Brandner on 26/08/15.
6 | // Copyright © 2015 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class SheetCollectionViewLayout: UICollectionViewLayout {
12 |
13 | fileprivate var layoutAttributes = [[UICollectionViewLayoutAttributes]]()
14 | fileprivate var invalidatedLayoutAttributes: [[UICollectionViewLayoutAttributes]]?
15 | fileprivate var contentSize = CGSize.zero
16 |
17 | // MARK: - Layout
18 |
19 | override func prepare() {
20 | super.prepare()
21 |
22 | layoutAttributes.removeAll(keepingCapacity: false)
23 | contentSize = CGSize.zero
24 |
25 | if let collectionView = collectionView,
26 | let delegate = collectionView.delegate as? UICollectionViewDelegateFlowLayout {
27 | let sections = collectionView.numberOfSections
28 | var origin = CGPoint()
29 |
30 | for section in 0 ..< sections {
31 | var sectionAttributes = [UICollectionViewLayoutAttributes]()
32 | let items = collectionView.numberOfItems(inSection: section)
33 | let indexPaths = (0 ..< items).map { IndexPath(item: $0, section: section) }
34 |
35 | for indexPath in indexPaths {
36 | let size = delegate.collectionView?(collectionView, layout: self, sizeForItemAt: indexPath) ?? CGSize.zero
37 |
38 | let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
39 | attributes.frame = CGRect(origin: origin, size: size)
40 |
41 | sectionAttributes.append(attributes)
42 | origin.y = attributes.frame.maxY
43 | }
44 |
45 | layoutAttributes.append(sectionAttributes)
46 | }
47 |
48 | contentSize = CGSize(width: collectionView.frame.width, height: origin.y)
49 | }
50 | }
51 |
52 | override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
53 | return true
54 | }
55 |
56 | override func invalidateLayout() {
57 | invalidatedLayoutAttributes = layoutAttributes
58 | super.invalidateLayout()
59 | }
60 |
61 | override var collectionViewContentSize : CGSize {
62 | return contentSize
63 | }
64 |
65 | override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
66 | return layoutAttributes.reduce([], +)
67 | .filter { rect.intersects($0.frame) }
68 | }
69 |
70 | fileprivate func layoutAttributesForItemAtIndexPath(_ indexPath: IndexPath, allAttributes: [[UICollectionViewLayoutAttributes]]) -> UICollectionViewLayoutAttributes? {
71 | guard allAttributes.count > indexPath.section && allAttributes[indexPath.section].count > indexPath.item else {
72 | return nil
73 | }
74 |
75 | return allAttributes[indexPath.section][indexPath.item]
76 | }
77 |
78 | fileprivate func invalidatedLayoutAttributesForItemAtIndexPath(_ indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
79 | guard let invalidatedLayoutAttributes = invalidatedLayoutAttributes else {
80 | return nil
81 | }
82 |
83 | return layoutAttributesForItemAtIndexPath(indexPath, allAttributes: invalidatedLayoutAttributes)
84 | }
85 |
86 | override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
87 | return layoutAttributesForItemAtIndexPath(indexPath, allAttributes: layoutAttributes)
88 | }
89 |
90 | override func initialLayoutAttributesForAppearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
91 | return invalidatedLayoutAttributesForItemAtIndexPath(itemIndexPath) ?? layoutAttributesForItem(at: itemIndexPath)
92 | }
93 |
94 | override func finalLayoutAttributesForDisappearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
95 | return layoutAttributesForItem(at: itemIndexPath)
96 | }
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController/Sheet/SheetController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SheetController.swift
3 | // ImagePickerSheetController
4 | //
5 | // Created by Laurin Brandner on 27/08/15.
6 | // Copyright © 2015 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | let sheetInset: CGFloat = 10
12 |
13 | class SheetController: NSObject {
14 |
15 | fileprivate(set) lazy var sheetCollectionView: UICollectionView = {
16 | let layout = SheetCollectionViewLayout()
17 | let collectionView = UICollectionView(frame: CGRect(), collectionViewLayout: layout)
18 | collectionView.dataSource = self
19 | collectionView.delegate = self
20 | collectionView.accessibilityIdentifier = "ImagePickerSheet"
21 | collectionView.backgroundColor = .clear
22 | collectionView.alwaysBounceVertical = false
23 | collectionView.register(SheetPreviewCollectionViewCell.self, forCellWithReuseIdentifier: NSStringFromClass(SheetPreviewCollectionViewCell.self))
24 | collectionView.register(SheetActionCollectionViewCell.self, forCellWithReuseIdentifier: NSStringFromClass(SheetActionCollectionViewCell.self))
25 |
26 | return collectionView
27 | }()
28 |
29 | var previewCollectionView: PreviewCollectionView
30 |
31 | fileprivate(set) var actions = [ImagePickerAction]()
32 |
33 | var actionHandlingCallback: (() -> ())?
34 |
35 | fileprivate(set) var previewHeight: CGFloat = 0
36 | var numberOfSelectedAssets = 0
37 |
38 | var preferredSheetHeight: CGFloat {
39 | return allIndexPaths().map { self.sizeForSheetItemAtIndexPath($0).height }
40 | .reduce(0, +)
41 | }
42 |
43 | // MARK: - Initialization
44 |
45 | init(previewCollectionView: PreviewCollectionView) {
46 | self.previewCollectionView = previewCollectionView
47 |
48 | super.init()
49 | }
50 |
51 | // MARK: - Data Source
52 | // These methods are necessary so that no call cycles happen when calculating some design attributes
53 |
54 | fileprivate func numberOfSections() -> Int {
55 | return 2
56 | }
57 |
58 | fileprivate func numberOfItemsInSection(_ section: Int) -> Int {
59 | if section == 0 {
60 | return 1
61 | }
62 |
63 | return actions.count
64 | }
65 |
66 | fileprivate func allIndexPaths() -> [IndexPath] {
67 | let s = numberOfSections()
68 | return (0 ..< s).map { (section: Int) -> (Int, Int) in (self.numberOfItemsInSection(section), section) }
69 | .flatMap { (numberOfItems: Int, section: Int) -> [IndexPath] in
70 | (0 ..< numberOfItems).map { (item: Int) -> IndexPath in IndexPath(item: item, section: section) }
71 | }
72 | }
73 |
74 | fileprivate func sizeForSheetItemAtIndexPath(_ indexPath: IndexPath) -> CGSize {
75 | let height: CGFloat = {
76 | if indexPath.section == 0 {
77 | return previewHeight
78 | }
79 |
80 | let actionItemHeight: CGFloat = 57
81 |
82 | let insets = attributesForItemAtIndexPath(indexPath).backgroundInsets
83 | return actionItemHeight + insets.top + insets.bottom
84 | }()
85 |
86 | return CGSize(width: sheetCollectionView.bounds.width, height: height)
87 | }
88 |
89 | // MARK: - Design
90 |
91 | fileprivate func attributesForItemAtIndexPath(_ indexPath: IndexPath) -> (corners: RoundedCorner, backgroundInsets: UIEdgeInsets) {
92 | let cornerRadius: CGFloat = 13
93 | let innerInset: CGFloat = 4
94 | var indexPaths = allIndexPaths()
95 |
96 | guard indexPaths.first != indexPath else {
97 | return (.top(cornerRadius), UIEdgeInsets(top: 0, left: sheetInset, bottom: 0, right: sheetInset))
98 | }
99 |
100 | let cancelIndexPath = actions.index { $0.style == ImagePickerActionStyle.cancel }
101 | .map { IndexPath(item: $0, section: 1) }
102 |
103 |
104 | if let cancelIndexPath = cancelIndexPath {
105 | if cancelIndexPath == indexPath {
106 | return (.all(cornerRadius), UIEdgeInsets(top: innerInset, left: sheetInset, bottom: sheetInset, right: sheetInset))
107 | }
108 |
109 | indexPaths.removeLast()
110 |
111 | if indexPath == indexPaths.last {
112 | return (.bottom(cornerRadius), UIEdgeInsets(top: 0, left: sheetInset, bottom: innerInset, right: sheetInset))
113 | }
114 | }
115 | else if indexPath == indexPaths.last {
116 | return (.bottom(cornerRadius), UIEdgeInsets(top: 0, left: sheetInset, bottom: sheetInset, right: sheetInset))
117 | }
118 |
119 | return (.none, UIEdgeInsets(top: 0, left: sheetInset, bottom: 0, right: sheetInset))
120 | }
121 |
122 | fileprivate func fontForAction(_ action: ImagePickerAction) -> UIFont {
123 | if action.style == .cancel {
124 | return UIFont.boldSystemFont(ofSize: 21)
125 | }
126 | return UIFont.systemFont(ofSize: 21)
127 | }
128 |
129 | // MARK: - Actions
130 |
131 | func reloadActionItems() {
132 | sheetCollectionView.reloadSections(IndexSet(integer: 1))
133 | }
134 |
135 | func addAction(_ action: ImagePickerAction) {
136 | if action.style == .cancel {
137 | actions = actions.filter { $0.style != .cancel }
138 | }
139 |
140 | actions.append(action)
141 |
142 | if let index = actions.index(where: { $0.style == .cancel }) {
143 | let cancelAction = actions.remove(at: index)
144 | actions.append(cancelAction)
145 | }
146 |
147 | reloadActionItems()
148 | }
149 |
150 | func removeAllActions() {
151 | actions = []
152 | reloadActionItems()
153 | }
154 |
155 | fileprivate func handleAction(_ action: ImagePickerAction) {
156 | actionHandlingCallback?()
157 | action.handle(numberOfSelectedAssets)
158 | }
159 |
160 | @objc func handleCancelAction() {
161 | let cancelAction = actions.filter { $0.style == .cancel }
162 | .first
163 |
164 | if let cancelAction = cancelAction {
165 | handleAction(cancelAction)
166 | }
167 | else {
168 | actionHandlingCallback?()
169 | }
170 | }
171 |
172 | // MARK: -
173 |
174 | func setPreviewHeight(_ height: CGFloat, invalidateLayout: Bool) {
175 | previewHeight = height
176 | if invalidateLayout {
177 | sheetCollectionView.collectionViewLayout.invalidateLayout()
178 | }
179 | }
180 |
181 | }
182 |
183 | extension SheetController: UICollectionViewDataSource {
184 |
185 | func numberOfSections(in collectionView: UICollectionView) -> Int {
186 | return numberOfSections()
187 | }
188 |
189 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
190 | return numberOfItemsInSection(section)
191 | }
192 |
193 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
194 | let cell: SheetCollectionViewCell
195 |
196 | if indexPath.section == 0 {
197 | let previewCell = collectionView.dequeueReusableCell(withReuseIdentifier: NSStringFromClass(SheetPreviewCollectionViewCell.self), for: indexPath) as! SheetPreviewCollectionViewCell
198 | previewCell.collectionView = previewCollectionView
199 |
200 | cell = previewCell
201 | }
202 | else {
203 | let action = actions[indexPath.item]
204 | let actionCell = collectionView.dequeueReusableCell(withReuseIdentifier: NSStringFromClass(SheetActionCollectionViewCell.self), for: indexPath) as! SheetActionCollectionViewCell
205 | actionCell.textLabel.font = fontForAction(action)
206 | actionCell.textLabel.text = numberOfSelectedAssets > 0 ? action.secondaryTitle(numberOfSelectedAssets) : action.title
207 |
208 | cell = actionCell
209 | }
210 |
211 | cell.separatorVisible = (indexPath.section == 1)
212 |
213 | // iOS specific design
214 | (cell.roundedCorners, cell.backgroundInsets) = attributesForItemAtIndexPath(indexPath)
215 | cell.normalBackgroundColor = UIColor(white: 0.97, alpha: 1)
216 | cell.highlightedBackgroundColor = UIColor(white: 0.92, alpha: 1)
217 | cell.separatorColor = UIColor(white: 0.84, alpha: 1)
218 |
219 | return cell
220 | }
221 |
222 | }
223 |
224 | extension SheetController: UICollectionViewDelegate {
225 |
226 | func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool {
227 | return indexPath.section != 0
228 | }
229 |
230 | func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
231 | collectionView.deselectItem(at: indexPath, animated: true)
232 |
233 | handleAction(actions[indexPath.item])
234 | }
235 |
236 | }
237 |
238 | extension SheetController: UICollectionViewDelegateFlowLayout {
239 |
240 | func collectionView(_ collectionView: UICollectionView, layout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
241 | return sizeForSheetItemAtIndexPath(indexPath)
242 | }
243 |
244 | }
245 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetController/Sheet/SheetPreviewCollectionViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SheetPreviewCollectionViewCell.swift
3 | // ImagePickerSheetController
4 | //
5 | // Created by Laurin Brandner on 06/09/14.
6 | // Copyright (c) 2014 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class SheetPreviewCollectionViewCell: SheetCollectionViewCell {
12 |
13 | var collectionView: PreviewCollectionView? {
14 | willSet {
15 | if let collectionView = collectionView {
16 | collectionView.removeFromSuperview()
17 | }
18 |
19 | if let collectionView = newValue {
20 | addSubview(collectionView)
21 | }
22 | }
23 | }
24 |
25 | // MARK: - Other Methods
26 |
27 | override func prepareForReuse() {
28 | collectionView = nil
29 | }
30 |
31 | // MARK: - Layout
32 |
33 | override func layoutSubviews() {
34 | super.layoutSubviews()
35 |
36 | collectionView?.frame = bounds.inset(by: backgroundInsets)
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetControllerTests/ActionHandlingTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ActionHandlingTests.swift
3 | // ImagePickerSheetController
4 | //
5 | // Created by Laurin Brandner on 06/09/15.
6 | // Copyright © 2015 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import KIF
11 | import Nimble
12 | import ImagePickerSheetController
13 |
14 | class ActionHandlingTests: ImagePickerSheetControllerTests {
15 |
16 | var defaultAction: ImagePickerAction!
17 | var cancelAction: ImagePickerAction!
18 |
19 | var defaultActionCalled: Int!
20 | var defaultSecondaryActionCalled: Int!
21 | var cancelActionCalled: Int!
22 | var cancelSecondaryActionCalled: Int!
23 |
24 | override func setUp() {
25 | super.setUp()
26 |
27 | defaultActionCalled = 0
28 | defaultSecondaryActionCalled = 0
29 | cancelActionCalled = 0
30 | cancelSecondaryActionCalled = 0
31 |
32 | defaultAction = ImagePickerAction(title: "Action", handler: { _ in
33 | self.defaultActionCalled = self.defaultActionCalled+1
34 | }, secondaryHandler: { _, _ in
35 | self.defaultSecondaryActionCalled = self.defaultSecondaryActionCalled+1
36 | })
37 | imageController.addAction(defaultAction)
38 |
39 | cancelAction = ImagePickerAction(title: "Cancel", style: .cancel, handler: { _ in
40 | self.cancelActionCalled = self.cancelActionCalled+1
41 | }, secondaryHandler: { _, _ in
42 | self.cancelSecondaryActionCalled = self.cancelSecondaryActionCalled+1
43 | })
44 | imageController.addAction(cancelAction)
45 | }
46 |
47 | func testDefaultActionHandling() {
48 | presentImagePickerSheetController()
49 |
50 | tester().tapView(withAccessibilityLabel: defaultAction.title)
51 |
52 | expect(self.defaultActionCalled) == 1
53 | expect(self.defaultSecondaryActionCalled) == 0
54 | expect(self.cancelActionCalled) == 0
55 | expect(self.cancelSecondaryActionCalled) == 0
56 | }
57 |
58 | func testSecondaryActionHandling() {
59 | presentImagePickerSheetController()
60 |
61 | tester().tapImagePreviewAtIndexPath(IndexPath(item: 0, section: 0), inCollectionViewWithAccessibilityIdentifier: imageControllerPreviewIdentifier)
62 | tester().tapView(withAccessibilityLabel: defaultAction.title)
63 |
64 | expect(self.defaultActionCalled) == 0
65 | expect(self.defaultSecondaryActionCalled) == 1
66 | expect(self.cancelActionCalled) == 0
67 | expect(self.cancelSecondaryActionCalled) == 0
68 | }
69 |
70 | func testCancelActionHandlingWhenTappingAction() {
71 | presentImagePickerSheetController()
72 |
73 | tester().tapView(withAccessibilityLabel: cancelAction.title)
74 |
75 | expect(self.defaultActionCalled) == 0
76 | expect(self.defaultSecondaryActionCalled) == 0
77 | expect(self.cancelActionCalled) == 1
78 | expect(self.cancelSecondaryActionCalled) == 0
79 | }
80 |
81 | func testCancelActionHandlingWhenTappingBackground() {
82 | presentImagePickerSheetController()
83 |
84 | tester().tapView(withAccessibilityIdentifier: imageControllerBackgroundViewIdentifier)
85 |
86 | expect(self.defaultActionCalled) == 0
87 | expect(self.defaultSecondaryActionCalled) == 0
88 | expect(self.cancelActionCalled) == 1
89 | expect(self.cancelSecondaryActionCalled) == 0
90 | }
91 |
92 | func testAdaptionOfActionTitles() {
93 | imageController.addAction(ImagePickerAction(title: "Action", secondaryTitle: { "Secondary \($0)" }, handler: { _ in }))
94 | presentImagePickerSheetController()
95 |
96 | let indexPath = IndexPath(item: 0, section: 0)
97 | tester().tapImagePreviewAtIndexPath(indexPath, inCollectionViewWithAccessibilityIdentifier: imageControllerPreviewIdentifier)
98 |
99 | tester().waitForView(withAccessibilityLabel: "Secondary 1")
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetControllerTests/AddingActionTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AddingActionTests.swift
3 | // ImagePickerSheetController
4 | //
5 | // Created by Laurin Brandner on 06/09/15.
6 | // Copyright © 2015 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import KIF
11 | import Nimble
12 | import ImagePickerSheetController
13 |
14 | class AddingActionTests: ImagePickerSheetControllerTests {
15 |
16 | func testAddingTwoCancelActions() {
17 | imageController.addAction(ImagePickerAction(title: "Cancel1", style: .cancel, handler: { _ in }))
18 | imageController.addAction(ImagePickerAction(title: "Cancel2", style: .cancel, handler: { _ in }))
19 |
20 | expect(self.imageController.actions.filter { $0.style == .Cancel }.count) == 1
21 | }
22 |
23 | func testDisplayOfAddedActions() {
24 | let actions: [(String, ImagePickerActionStyle)] = [("Action1", .default),
25 | ("Action2", .default),
26 | ("Cancel", .cancel)]
27 |
28 | for (title, style) in actions {
29 | imageController.addAction(ImagePickerAction(title: title, style: style, handler: { _ in }))
30 | }
31 |
32 | presentImagePickerSheetController()
33 |
34 | for (title, _) in actions {
35 | tester().waitForView(withAccessibilityLabel: title)
36 | }
37 | }
38 |
39 | func testActionOrdering() {
40 | imageController.addAction(ImagePickerAction(title: cancelActionTitle, style: .cancel, handler: { _ in }))
41 | imageController.addAction(ImagePickerAction(title: defaultActionTitle, handler: { _ in }))
42 |
43 | expect(self.imageController.actions.map { $0.title }) == [defaultActionTitle, cancelActionTitle]
44 | }
45 |
46 | func testAddingActionAfterPresentation() {
47 | presentImagePickerSheetController()
48 |
49 | imageController.addAction(ImagePickerAction(title: defaultActionTitle, handler: { _ in }))
50 | tester().waitForView(withAccessibilityLabel: defaultActionTitle)
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetControllerTests/DismissalTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DismissalTests.swift
3 | // ImagePickerSheetController
4 | //
5 | // Created by Laurin Brandner on 06/09/15.
6 | // Copyright © 2015 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import KIF
11 | import ImagePickerSheetController
12 |
13 | class DismissalTests: ImagePickerSheetControllerTests {
14 |
15 | override func setUp() {
16 | super.setUp()
17 |
18 | imageController.addAction(ImagePickerAction(title: defaultActionTitle, style: .default, handler: { _ in }))
19 | imageController.addAction(ImagePickerAction(title: cancelActionTitle, style: .cancel, handler: { _ in }))
20 |
21 | presentImagePickerSheetController()
22 | }
23 |
24 | func testDismissalByTappingDefaultAction() {
25 | tester().tapView(withAccessibilityLabel: defaultActionTitle)
26 | tester().waitForAbsenceOfView(withAccessibilityIdentifier: imageControllerViewIdentifier)
27 | }
28 |
29 | func testDismissalByTappingCancelAction() {
30 | tester().tapView(withAccessibilityLabel: cancelActionTitle)
31 | tester().waitForAbsenceOfView(withAccessibilityIdentifier: imageControllerViewIdentifier)
32 | }
33 |
34 | func testDismissalByTappingBackground() {
35 | tester().tapView(withAccessibilityIdentifier: imageControllerBackgroundViewIdentifier)
36 | tester().waitForAbsenceOfView(withAccessibilityIdentifier: imageControllerViewIdentifier)
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetControllerTests/ImagePickerSheetControllerTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImagePickerSheetControllerTests.swift
3 | // ImagePickerSheetControllerTests
4 | //
5 | // Created by Laurin Brandner on 26/05/15.
6 | // Copyright (c) 2015 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import XCTest
11 | import KIF
12 | import Nimble
13 | import Photos
14 | import ImagePickerSheetController
15 |
16 | let imageControllerViewIdentifier = "ImagePickerSheet"
17 | let imageControllerBackgroundViewIdentifier = "ImagePickerSheetBackground"
18 | let imageControllerPreviewIdentifier = "ImagePickerSheetPreview"
19 |
20 | class ImagePickerSheetControllerTests: XCTestCase {
21 |
22 | let rootViewController = UIApplication.shared.windows.first!.rootViewController!
23 | var imageController: ImagePickerSheetController!
24 |
25 | let defaultActionTitle = "Action"
26 | let cancelActionTitle = "Cancel"
27 |
28 | // MARK: - Setup
29 |
30 | override func setUp() {
31 | super.setUp()
32 |
33 | imageController = ImagePickerSheetController(mediaType: .imageAndVideo)
34 | }
35 |
36 | override func tearDown() {
37 | super.tearDown()
38 |
39 | rootViewController.dismiss(animated: false, completion: nil)
40 | }
41 |
42 | // MARK: - Utilities
43 |
44 | func presentImagePickerSheetController(_ animated: Bool = false) {
45 | rootViewController.present(imageController, animated: animated, completion: nil)
46 | tester().waitForView(withAccessibilityIdentifier: imageControllerViewIdentifier)
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetControllerTests/ImageSelectionTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageSelectionTests.swift
3 | // ImagePickerSheetController
4 | //
5 | // Created by Laurin Brandner on 06/09/15.
6 | // Copyright © 2015 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import KIF
11 | import Nimble
12 | import Photos
13 | import ImagePickerSheetController
14 |
15 | class ImageSelectionTests: ImagePickerSheetControllerTests {
16 |
17 | let result: PHFetchResult = { () -> PHFetchResult in
18 | let options = PHFetchOptions()
19 | options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
20 |
21 | return PHAsset.fetchAssets(with: .image, options: options)
22 | }()
23 |
24 | let count = 3
25 |
26 | override func setUp() {
27 | super.setUp()
28 |
29 | presentImagePickerSheetController()
30 | }
31 |
32 | }
33 |
34 | class ImageSelectionWithoutLimitTests: ImageSelectionTests {
35 |
36 | override func setUp() {
37 | super.setUp()
38 |
39 | for i in 0 ..< count {
40 | let indexPath = IndexPath(item: 0, section: i)
41 | tester().tapImagePreviewAtIndexPath(indexPath, inCollectionViewWithAccessibilityIdentifier: imageControllerPreviewIdentifier)
42 | }
43 |
44 | expect(self.imageController.selectedAssets.count) == count
45 | }
46 |
47 | func testImageSelection() {
48 | let selectedAssets = imageController.selectedAssets
49 | result.enumerateObjects { obj, idx, _ in
50 | if let asset = obj as? PHAsset , idx < 3 {
51 | expect(asset.localIdentifier) == selectedAssets[idx].localIdentifier
52 | }
53 | }
54 | }
55 |
56 | func testImageDeselection() {
57 | let indexPath = IndexPath(item: 0, section: 0)
58 | tester().tapImagePreviewAtIndexPath(indexPath, inCollectionViewWithAccessibilityIdentifier: imageControllerPreviewIdentifier)
59 |
60 | expect(self.imageController.selectedAssets.count) == count - 1
61 |
62 | let selectedAssets = imageController.selectedAssets
63 | result.enumerateObjects { obj, idx, _ in
64 | if let asset = obj as? PHAsset , idx < self.count && idx > 0 {
65 | expect(asset.localIdentifier) == selectedAssets[idx-1].localIdentifier
66 | }
67 | }
68 | }
69 |
70 | }
71 |
72 | class ImageSelectionWithLimitTests: ImageSelectionTests {
73 |
74 | func testImageSelection() {
75 | let maxSelection = 2
76 | imageController.maximumSelection = maxSelection
77 |
78 | for i in 0 ..< count {
79 | let indexPath = IndexPath(item: 0, section: i)
80 | tester().tapImagePreviewAtIndexPath(indexPath, inCollectionViewWithAccessibilityIdentifier: imageControllerPreviewIdentifier)
81 | }
82 |
83 | expect(self.imageController.selectedAssets.count) == maxSelection
84 |
85 | let selectedAssets = imageController.selectedAssets
86 | result.enumerateObjects { obj, idx, _ in
87 | if let asset = obj as? PHAsset , idx < maxSelection && idx > 0 {
88 | expect(asset.localIdentifier) == selectedAssets[idx-1].localIdentifier
89 | }
90 | }
91 | }
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetControllerTests/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 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetControllerTests/KIFExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KIFExtensions.swift
3 | // ImagePickerSheetController
4 | //
5 | // Created by Laurin Brandner on 05/06/15.
6 | // Copyright (c) 2015 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import KIF
11 |
12 | extension XCTestCase {
13 |
14 | func tester(_ file : String = #file, _ line : Int = #line) -> KIFUITestActor {
15 | return KIFUITestActor(inFile: file, atLine: line, delegate: self)
16 | }
17 |
18 | func system(_ file : String = #file, _ line : Int = #line) -> KIFSystemTestActor {
19 | return KIFSystemTestActor(inFile: file, atLine: line, delegate: self)
20 | }
21 |
22 | }
23 |
24 | extension KIFTestActor {
25 |
26 | func tester(_ file : String = #file, _ line : Int = #line) -> KIFUITestActor {
27 | return KIFUITestActor(inFile: file, atLine: line, delegate: self)
28 | }
29 |
30 | func system(_ file : String = #file, _ line : Int = #line) -> KIFSystemTestActor {
31 | return KIFSystemTestActor(inFile: file, atLine: line, delegate: self)
32 | }
33 |
34 | }
35 |
36 | extension KIFUITestActor {
37 |
38 | // Needed because UICollectionView fails to select an item due to a reason I don't quite grasp
39 | func tapImagePreviewAtIndexPath(_ indexPath: IndexPath, inCollectionViewWithAccessibilityIdentifier collectionViewIdentifier: String) {
40 | let collectionView = waitForView(withAccessibilityIdentifier: collectionViewIdentifier) as! UICollectionView
41 |
42 | let cellAttributes = collectionView.layoutAttributesForItem(at: indexPath)
43 | let contentOffset = CGPoint(x: cellAttributes!.frame.minX-collectionView.contentInset.left, y: 0)
44 |
45 | collectionView.setContentOffset(contentOffset, animated: false)
46 |
47 | let newCellAttributes = collectionView.layoutAttributesForItem(at: indexPath)
48 | let cellCenter = collectionView.convert(newCellAttributes!.center, to: nil)
49 |
50 | // Tap it manually here, no UICollectionView selection
51 | tapScreen(at: cellCenter)
52 |
53 | // Wait so that a possible preview zooming animation can finish
54 | waitForAnimationsToFinish()
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/ImagePickerSheetController/ImagePickerSheetControllerTests/PresentationTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PresentationTests.swift
3 | // ImagePickerSheetController
4 | //
5 | // Created by Laurin Brandner on 06/09/15.
6 | // Copyright © 2015 Laurin Brandner. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import KIF
11 |
12 | class PresentationTests: ImagePickerSheetControllerTests {
13 |
14 | func testPresentation() {
15 | presentImagePickerSheetController(true)
16 | tester().acknowledgeSystemAlert()
17 | tester().waitForView(withAccessibilityIdentifier: imageControllerViewIdentifier)
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 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 | # ImagePickerSheetController
2 |
3 | [](https://twitter.com/lbrndnr)
4 | [](https://github.com/lbrndnr/ImagePickerSheetController/blob/master/LICENSE)
5 | [](https://github.com/Carthage/Carthage)
6 |
7 | ## About
8 | ImagePickerSheetController is a component that replicates the custom photo action sheet in iMessage. It's very similar to UIAlertController which makes its usage simple and concise.
9 | ⚠️You can also find an iOS 10 version of this library [here](https://github.com/lbrndnr/ImagePickerTrayController)⚠️
10 |
11 | 
12 |
13 | ## Usage
14 | `ImagePickerSheetController` is similar to `UIAlertController` in its usage.
15 |
16 | ### Example
17 |
18 | ```swift
19 | let controller = ImagePickerSheetController(mediaType: .ImageAndVideo)
20 | controller.addAction(ImagePickerAction(title: NSLocalizedString("Take Photo Or Video", comment: "Action Title"), secondaryTitle: NSLocalizedString("Add comment", comment: "Action Title"), handler: { _ in
21 | presentImagePickerController(.Camera)
22 | }, secondaryHandler: { _, numberOfPhotos in
23 | println("Comment \(numberOfPhotos) photos")
24 | }))
25 | controller.addAction(ImagePickerAction(title: NSLocalizedString("Photo Library", comment: "Action Title"), secondaryTitle: { NSString.localizedStringWithFormat(NSLocalizedString("ImagePickerSheet.button1.Send %lu Photo", comment: "Action Title"), $0) as String}, handler: { _ in
26 | presentImagePickerController(.PhotoLibrary)
27 | }, secondaryHandler: { _, numberOfPhotos in
28 | println("Send \(controller.selectedImageAssets)")
29 | }))
30 | controller.addAction(ImagePickerAction(title: NSLocalizedString("Cancel", comment: "Action Title"), style: .Cancel, handler: { _ in
31 | println("Cancelled")
32 | }))
33 |
34 | presentViewController(controller, animated: true, completion: nil)
35 | ```
36 | It's recommended to use [stringsdict](https://developer.apple.com/library/ios/documentation/MacOSX/Conceptual/BPInternational/StringsdictFileFormat/StringsdictFileFormat.html) to easily translate plural forms in any language.
37 |
38 | ## Installation
39 |
40 | ### CocoaPods
41 | ```ruby
42 | pod "ImagePickerSheetController", "~> 0.9.1"
43 | ```
44 |
45 | ###Carthage
46 | ```objc
47 | github "lbrndnr/ImagePickerSheetController" ~> 0.9.1
48 | ```
49 |
50 | You should also add two new values to your app's `Info.plist` to tell the user why you need to access the Camera and Photo Library.
51 | ```
52 | NSCameraUsageDescription
53 | Camera usage description
54 | NSPhotoLibraryUsageDescription
55 | Photo Library usage description
56 | ```
57 |
58 | ## Requirements
59 | ImagePickerSheetController is written in Swift and links against `Photos.framework`. It therefore requires iOS 9.0 or later.
60 |
61 | ## Author
62 | I'm Laurin Brandner, I'm on [Twitter](https://twitter.com/lbrndnr).
63 |
64 | ## License
65 | ImagePickerSheetController is licensed under the [MIT License](http://opensource.org/licenses/mit-license.php).
66 |
--------------------------------------------------------------------------------
/Screenshots/GoT.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerSheetController/c57fa10ad847e29cabfa90a60baffca3c67474b8/Screenshots/GoT.gif
--------------------------------------------------------------------------------
/Screenshots/Nature.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lbrndnr/ImagePickerSheetController/c57fa10ad847e29cabfa90a60baffca3c67474b8/Screenshots/Nature.png
--------------------------------------------------------------------------------