├── .gitignore ├── .swiftlint.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Demo └── ImagePickerDemo │ ├── ImagePickerDemo.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata │ ├── ImagePickerDemo.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ ├── ImagePickerDemo │ ├── AppDelegate.swift │ ├── Base.lproj │ │ └── LaunchScreen.xib │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-40.png │ │ │ ├── Icon-40@2x.png │ │ │ ├── Icon-40@3x.png │ │ │ ├── Icon-60@2x.png │ │ │ ├── Icon-60@3x.png │ │ │ ├── Icon-72.png │ │ │ ├── Icon-72@2x.png │ │ │ ├── Icon-76.png │ │ │ ├── Icon-76@2x.png │ │ │ ├── Icon-83.5@2x.png │ │ │ ├── Icon-Small-50.png │ │ │ ├── Icon-Small-50@2x.png │ │ │ ├── Icon-Small.png │ │ │ ├── Icon-Small@2x.png │ │ │ ├── Icon-Small@3x.png │ │ │ ├── Icon.png │ │ │ └── Icon@2x.png │ │ └── Contents.json │ ├── Info.plist │ └── ViewController.swift │ ├── Podfile │ └── Podfile.lock ├── ImagePicker.podspec ├── ImagePicker.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── ImagePicker.xcscmblueprint └── xcshareddata │ └── xcschemes │ └── ImagePicker-iOS.xcscheme ├── Images ├── AUTO@3x.png ├── OFF@3x.png ├── ON@3x.png ├── cameraIcon@3x.png ├── focusIcon@3x.png ├── selectedImageGallery@3x.png └── video@3x.png ├── LICENSE.md ├── Package.swift ├── README.md ├── Resources ├── ImagePickerIcon.png └── ImagePickerPresentation.png ├── Source ├── AssetManager.swift ├── BottomView │ ├── BottomContainerView.swift │ ├── ButtonPicker.swift │ ├── ImageStack.swift │ └── StackView.swift ├── CameraView │ ├── CameraMan.swift │ └── CameraView.swift ├── Configuration.swift ├── Extensions │ └── ConstraintsSetup.swift ├── Helper.swift ├── ImageGallery │ ├── ImageGalleryLayout.swift │ ├── ImageGalleryView.swift │ ├── ImageGalleryViewCell.swift │ ├── ImageGalleryViewDataSource.swift │ └── VideoInfoView.swift ├── ImagePickerController.swift ├── LocationManager.swift └── TopView │ └── TopView.swift └── SupportFiles └── Info.plist /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | Icon 6 | ._* 7 | .Spotlight-V100 8 | .Trashes 9 | 10 | # Xcode 11 | # 12 | build/ 13 | *.pbxuser 14 | !default.pbxuser 15 | *.mode1v3 16 | !default.mode1v3 17 | *.mode2v3 18 | !default.mode2v3 19 | *.perspectivev3 20 | !default.perspectivev3 21 | xcuserdata 22 | *.xccheckout 23 | *.moved-aside 24 | DerivedData 25 | *.hmap 26 | *.ipa 27 | *.xcuserstate 28 | 29 | # CocoaPods 30 | Pods 31 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | included: # paths to include during linting. `--path` is ignored if present. 2 | - Source 3 | excluded: # paths to ignore during linting. Takes precedence over `included`. 4 | - Carthage 5 | - Pods 6 | 7 | # configurable rules can be customized from this configuration file 8 | # binary rules can set their severity level 9 | force_cast: warning # implicitly 10 | force_try: 11 | severity: warning # explicitly 12 | # rules that have both warning and error levels, can set just the warning level 13 | # implicitly 14 | line_length: 200 15 | # they can set both implicitly with an array 16 | type_body_length: 17 | - 300 # warning 18 | - 400 # error 19 | # or they can set both explicitly 20 | file_length: 21 | warning: 500 22 | error: 1200 23 | # naming rules can set warnings/errors for min_length and max_length 24 | # additionally they can set excluded names 25 | type_name: 26 | min_length: 3 # only warning 27 | max_length: # warning and error 28 | warning: 40 29 | error: 50 30 | excluded: iPhone # excluded via string 31 | variable_name: 32 | min_length: # only min_length 33 | error: 2 # only error 34 | excluded: # excluded via string array 35 | - x 36 | - y 37 | - id 38 | - URL 39 | - GlobalAPIKey 40 | reporter: "xcode" # reporter type (xcode, json, csv, checkstyle) 41 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [Unreleased](https://github.com/hyperoslo/ImagePicker/tree/HEAD) 4 | 5 | [Full Changelog](https://github.com/hyperoslo/ImagePicker/compare/1.2...HEAD) 6 | 7 | **Fixed bugs:** 8 | 9 | - Dangerous warning [\#126](https://github.com/hyperoslo/ImagePicker/issues/126) 10 | 11 | **Closed issues:** 12 | 13 | - Demo Could Not Run [\#155](https://github.com/hyperoslo/ImagePicker/issues/155) 14 | - UICollectionViewFlowLayoutForInvalidSizes [\#152](https://github.com/hyperoslo/ImagePicker/issues/152) 15 | - Swift 3.0 Warnings [\#138](https://github.com/hyperoslo/ImagePicker/issues/138) 16 | - iPad 2 crash [\#124](https://github.com/hyperoslo/ImagePicker/issues/124) 17 | - Is there plan to support different albums? [\#114](https://github.com/hyperoslo/ImagePicker/issues/114) 18 | - Only Photo Library [\#113](https://github.com/hyperoslo/ImagePicker/issues/113) 19 | - Take a picture programmatically [\#111](https://github.com/hyperoslo/ImagePicker/issues/111) 20 | 21 | **Merged pull requests:** 22 | 23 | - Remove Guard in Demo [\#157](https://github.com/hyperoslo/ImagePicker/pull/157) ([onmyway133](https://github.com/onmyway133)) 24 | - Stop in deinit [\#153](https://github.com/hyperoslo/ImagePicker/pull/153) ([onmyway133](https://github.com/onmyway133)) 25 | - Improvements [\#151](https://github.com/hyperoslo/ImagePicker/pull/151) ([onmyway133](https://github.com/onmyway133)) 26 | - Refactor/camera [\#150](https://github.com/hyperoslo/ImagePicker/pull/150) ([onmyway133](https://github.com/onmyway133)) 27 | - Check input before add [\#149](https://github.com/hyperoslo/ImagePicker/pull/149) ([onmyway133](https://github.com/onmyway133)) 28 | - Use serial sessionQueue because most session calls are blocking [\#146](https://github.com/hyperoslo/ImagePicker/pull/146) ([onmyway133](https://github.com/onmyway133)) 29 | - Check for buffer [\#145](https://github.com/hyperoslo/ImagePicker/pull/145) ([onmyway133](https://github.com/onmyway133)) 30 | - Configure preset before addInput [\#144](https://github.com/hyperoslo/ImagePicker/pull/144) ([onmyway133](https://github.com/onmyway133)) 31 | - Add a Gitter chat badge to README.md [\#143](https://github.com/hyperoslo/ImagePicker/pull/143) ([gitter-badger](https://github.com/gitter-badger)) 32 | - Release 1.3 [\#141](https://github.com/hyperoslo/ImagePicker/pull/141) ([aashishdhawan](https://github.com/aashishdhawan)) 33 | - Reset transform and contentInset just in case [\#140](https://github.com/hyperoslo/ImagePicker/pull/140) ([onmyway133](https://github.com/onmyway133)) 34 | - Call reloadData after updating collectionSize [\#139](https://github.com/hyperoslo/ImagePicker/pull/139) ([onmyway133](https://github.com/onmyway133)) 35 | - Fix access UI not on main queue [\#137](https://github.com/hyperoslo/ImagePicker/pull/137) ([onmyway133](https://github.com/onmyway133)) 36 | - Refactor. Move showNoCamera into mainQueue [\#136](https://github.com/hyperoslo/ImagePicker/pull/136) ([onmyway133](https://github.com/onmyway133)) 37 | - Remove sessionPreset [\#134](https://github.com/hyperoslo/ImagePicker/pull/134) ([aashishdhawan](https://github.com/aashishdhawan)) 38 | - Fix UI when Camera access is denied. [\#133](https://github.com/hyperoslo/ImagePicker/pull/133) ([aashishdhawan](https://github.com/aashishdhawan)) 39 | - Refactored ImageGalleryPanGestureDelegate [\#132](https://github.com/hyperoslo/ImagePicker/pull/132) ([aashishdhawan](https://github.com/aashishdhawan)) 40 | - Change demo bundle to no.hyper.ImagePickerDemo [\#131](https://github.com/hyperoslo/ImagePicker/pull/131) ([onmyway133](https://github.com/onmyway133)) 41 | - Fix frame for NoImage Label [\#130](https://github.com/hyperoslo/ImagePicker/pull/130) ([aashishdhawan](https://github.com/aashishdhawan)) 42 | - Fix Image Limit bug [\#129](https://github.com/hyperoslo/ImagePicker/pull/129) ([aashishdhawan](https://github.com/aashishdhawan)) 43 | - Refactored LocationManager into its own File [\#128](https://github.com/hyperoslo/ImagePicker/pull/128) ([aashishdhawan](https://github.com/aashishdhawan)) 44 | - Fixing frame issues on rotation [\#127](https://github.com/hyperoslo/ImagePicker/pull/127) ([aashishdhawan](https://github.com/aashishdhawan)) 45 | - Refactored same code in a class [\#122](https://github.com/hyperoslo/ImagePicker/pull/122) ([aashishdhawan](https://github.com/aashishdhawan)) 46 | - Minor refactoring [\#121](https://github.com/hyperoslo/ImagePicker/pull/121) ([aashishdhawan](https://github.com/aashishdhawan)) 47 | - Fix/image selection [\#120](https://github.com/hyperoslo/ImagePicker/pull/120) ([onmyway133](https://github.com/onmyway133)) 48 | - Improve/swift 2.2 [\#119](https://github.com/hyperoslo/ImagePicker/pull/119) ([onmyway133](https://github.com/onmyway133)) 49 | 50 | ## [1.2](https://github.com/hyperoslo/ImagePicker/tree/1.2) (2016-03-18) 51 | [Full Changelog](https://github.com/hyperoslo/ImagePicker/compare/1.1...1.2) 52 | 53 | **Implemented enhancements:** 54 | 55 | - Metadata support [\#102](https://github.com/hyperoslo/ImagePicker/issues/102) 56 | 57 | **Closed issues:** 58 | 59 | - I am trying to use the demo with latest cocoda pod and xcode 7 but its not working [\#108](https://github.com/hyperoslo/ImagePicker/issues/108) 60 | - To quickly explore the demo project, as a User, I'd like to be able to run the demo on a device. [\#99](https://github.com/hyperoslo/ImagePicker/issues/99) 61 | - Can't run demo app as-is [\#98](https://github.com/hyperoslo/ImagePicker/issues/98) 62 | - Objective C support ? [\#97](https://github.com/hyperoslo/ImagePicker/issues/97) 63 | - After dismissing the view overlaps with status bar. [\#94](https://github.com/hyperoslo/ImagePicker/issues/94) 64 | - Present ViewController from the ImagePickerController [\#88](https://github.com/hyperoslo/ImagePicker/issues/88) 65 | - Add paragraph about how to limit selection in the README [\#86](https://github.com/hyperoslo/ImagePicker/issues/86) 66 | - Limitation for images [\#85](https://github.com/hyperoslo/ImagePicker/issues/85) 67 | - StatusBar is kept hidden when leaving view [\#78](https://github.com/hyperoslo/ImagePicker/issues/78) 68 | 69 | **Merged pull requests:** 70 | 71 | - Disable code signing [\#109](https://github.com/hyperoslo/ImagePicker/pull/109) ([zenangst](https://github.com/zenangst)) 72 | - Record location with CLLocationManager if permission given by user [\#107](https://github.com/hyperoslo/ImagePicker/pull/107) ([fnakstad](https://github.com/fnakstad)) 73 | - Fix bug where pictures are repeatedly taken due to new AVCaptureSessi… [\#106](https://github.com/hyperoslo/ImagePicker/pull/106) ([fnakstad](https://github.com/fnakstad)) 74 | - Fixed issues in iPad landscape [\#104](https://github.com/hyperoslo/ImagePicker/pull/104) ([BenchR267](https://github.com/BenchR267)) 75 | - fix reloadAssets still showing pictures as selected in collection view [\#101](https://github.com/hyperoslo/ImagePicker/pull/101) ([aronse](https://github.com/aronse)) 76 | - Changed how status bar appear/disappear [\#95](https://github.com/hyperoslo/ImagePicker/pull/95) ([JARMourato](https://github.com/JARMourato)) 77 | - Can Disable Camera Rotation [\#93](https://github.com/hyperoslo/ImagePicker/pull/93) ([kernjackson](https://github.com/kernjackson)) 78 | - Support taking pictures with volume button [\#92](https://github.com/hyperoslo/ImagePicker/pull/92) ([zenangst](https://github.com/zenangst)) 79 | - Support rotation [\#91](https://github.com/hyperoslo/ImagePicker/pull/91) ([zenangst](https://github.com/zenangst)) 80 | - Add image selection limiting to the README [\#90](https://github.com/hyperoslo/ImagePicker/pull/90) ([zenangst](https://github.com/zenangst)) 81 | - Configure Travis to build the project [\#89](https://github.com/hyperoslo/ImagePicker/pull/89) ([zenangst](https://github.com/zenangst)) 82 | - Fix framework resources [\#87](https://github.com/hyperoslo/ImagePicker/pull/87) ([vadymmarkov](https://github.com/vadymmarkov)) 83 | 84 | ## [1.1](https://github.com/hyperoslo/ImagePicker/tree/1.1) (2016-01-05) 85 | [Full Changelog](https://github.com/hyperoslo/ImagePicker/compare/1.0...1.1) 86 | 87 | **Implemented enhancements:** 88 | 89 | - How to limit number of images to select? [\#66](https://github.com/hyperoslo/ImagePicker/issues/66) 90 | 91 | **Closed issues:** 92 | 93 | - Not able to force only 1 picture selection [\#81](https://github.com/hyperoslo/ImagePicker/issues/81) 94 | - Status Bar Hidden [\#77](https://github.com/hyperoslo/ImagePicker/issues/77) 95 | - set max number of photos for selection? [\#76](https://github.com/hyperoslo/ImagePicker/issues/76) 96 | - Status bar is not reset when ImagePicker is removed [\#69](https://github.com/hyperoslo/ImagePicker/issues/69) 97 | - Update for iOS 9.0 [\#67](https://github.com/hyperoslo/ImagePicker/issues/67) 98 | 99 | **Merged pull requests:** 100 | 101 | - Release 1.1 [\#84](https://github.com/hyperoslo/ImagePicker/pull/84) ([RamonGilabert](https://github.com/RamonGilabert)) 102 | - Development [\#83](https://github.com/hyperoslo/ImagePicker/pull/83) ([RamonGilabert](https://github.com/RamonGilabert)) 103 | - Fixes the development branch with master [\#82](https://github.com/hyperoslo/ImagePicker/pull/82) ([RamonGilabert](https://github.com/RamonGilabert)) 104 | - Add Carthage in Readme [\#75](https://github.com/hyperoslo/ImagePicker/pull/75) ([vadymmarkov](https://github.com/vadymmarkov)) 105 | - Feature: carthage support [\#74](https://github.com/hyperoslo/ImagePicker/pull/74) ([vadymmarkov](https://github.com/vadymmarkov)) 106 | - Refactoring the ImagePicker. [\#72](https://github.com/hyperoslo/ImagePicker/pull/72) ([RamonGilabert](https://github.com/RamonGilabert)) 107 | - Status bar initial state [\#71](https://github.com/hyperoslo/ImagePicker/pull/71) ([RamonGilabert](https://github.com/RamonGilabert)) 108 | - Feature requests [\#68](https://github.com/hyperoslo/ImagePicker/pull/68) ([RamonGilabert](https://github.com/RamonGilabert)) 109 | - Adds new image with white brackground [\#65](https://github.com/hyperoslo/ImagePicker/pull/65) ([RamonGilabert](https://github.com/RamonGilabert)) 110 | - Release 1.0 [\#64](https://github.com/hyperoslo/ImagePicker/pull/64) ([RamonGilabert](https://github.com/RamonGilabert)) 111 | 112 | ## [1.0](https://github.com/hyperoslo/ImagePicker/tree/1.0) (2015-12-01) 113 | **Implemented enhancements:** 114 | 115 | - Use Swift set instead of Array to store images/photos [\#50](https://github.com/hyperoslo/ImagePicker/issues/50) 116 | 117 | **Fixed bugs:** 118 | 119 | - Crash, branch swift 2.0 [\#29](https://github.com/hyperoslo/ImagePicker/issues/29) 120 | - AutoLayout warning [\#19](https://github.com/hyperoslo/ImagePicker/issues/19) 121 | 122 | **Closed issues:** 123 | 124 | - Fetch all the photos of the camera effitiently [\#6](https://github.com/hyperoslo/ImagePicker/issues/6) 125 | - Be able to do all you can do with the normal camera [\#5](https://github.com/hyperoslo/ImagePicker/issues/5) 126 | - Display a message when the gallery is not available [\#4](https://github.com/hyperoslo/ImagePicker/issues/4) 127 | - Crash on loading demo [\#2](https://github.com/hyperoslo/ImagePicker/issues/2) 128 | 129 | **Merged pull requests:** 130 | 131 | - Improve README [\#63](https://github.com/hyperoslo/ImagePicker/pull/63) ([RamonGilabert](https://github.com/RamonGilabert)) 132 | - README [\#62](https://github.com/hyperoslo/ImagePicker/pull/62) ([RamonGilabert](https://github.com/RamonGilabert)) 133 | - Improve the README. [\#61](https://github.com/hyperoslo/ImagePicker/pull/61) ([RamonGilabert](https://github.com/RamonGilabert)) 134 | - Adds the other part of the README to resolve assets [\#60](https://github.com/hyperoslo/ImagePicker/pull/60) ([RamonGilabert](https://github.com/RamonGilabert)) 135 | - Refactor configuration to be static instead of a singleton [\#59](https://github.com/hyperoslo/ImagePicker/pull/59) ([RamonGilabert](https://github.com/RamonGilabert)) 136 | - README [\#58](https://github.com/hyperoslo/ImagePicker/pull/58) ([RamonGilabert](https://github.com/RamonGilabert)) 137 | - Generic fix [\#57](https://github.com/hyperoslo/ImagePicker/pull/57) ([RamonGilabert](https://github.com/RamonGilabert)) 138 | - Improvements across. [\#56](https://github.com/hyperoslo/ImagePicker/pull/56) ([RamonGilabert](https://github.com/RamonGilabert)) 139 | - Feature: camera not available [\#55](https://github.com/hyperoslo/ImagePicker/pull/55) ([vadymmarkov](https://github.com/vadymmarkov)) 140 | - Update noImagesLabel [\#54](https://github.com/hyperoslo/ImagePicker/pull/54) ([onmyway133](https://github.com/onmyway133)) 141 | - Feature loading indicator [\#53](https://github.com/hyperoslo/ImagePicker/pull/53) ([zenangst](https://github.com/zenangst)) 142 | - Fix delegate call in the closure. [\#51](https://github.com/hyperoslo/ImagePicker/pull/51) ([vadymmarkov](https://github.com/vadymmarkov)) 143 | - Fix/collection view resizing [\#49](https://github.com/hyperoslo/ImagePicker/pull/49) ([richardoti](https://github.com/richardoti)) 144 | - New design + refactoring [\#48](https://github.com/hyperoslo/ImagePicker/pull/48) ([richardoti](https://github.com/richardoti)) 145 | - Improves the cropping of the image. [\#47](https://github.com/hyperoslo/ImagePicker/pull/47) ([RamonGilabert](https://github.com/RamonGilabert)) 146 | - Disable "flash" button if capture device has no flash. [\#46](https://github.com/hyperoslo/ImagePicker/pull/46) ([richardoti](https://github.com/richardoti)) 147 | - Adds public to the configuration. [\#45](https://github.com/hyperoslo/ImagePicker/pull/45) ([RamonGilabert](https://github.com/RamonGilabert)) 148 | - Adds public where necessary. [\#44](https://github.com/hyperoslo/ImagePicker/pull/44) ([RamonGilabert](https://github.com/RamonGilabert)) 149 | - Fixes last force cast. [\#43](https://github.com/hyperoslo/ImagePicker/pull/43) ([RamonGilabert](https://github.com/RamonGilabert)) 150 | - Make Photos public [\#42](https://github.com/hyperoslo/ImagePicker/pull/42) ([zenangst](https://github.com/zenangst)) 151 | - Lookup asynchronously and use PHAssets instead of UIImages [\#41](https://github.com/hyperoslo/ImagePicker/pull/41) ([zenangst](https://github.com/zenangst)) 152 | - Feature/external configuration [\#40](https://github.com/hyperoslo/ImagePicker/pull/40) ([RamonGilabert](https://github.com/RamonGilabert)) 153 | - Refactor code to remove and improve all the things [\#39](https://github.com/hyperoslo/ImagePicker/pull/39) ([zenangst](https://github.com/zenangst)) 154 | - Remove force unwrapping [\#38](https://github.com/hyperoslo/ImagePicker/pull/38) ([zenangst](https://github.com/zenangst)) 155 | - Improves all the things [\#37](https://github.com/hyperoslo/ImagePicker/pull/37) ([RamonGilabert](https://github.com/RamonGilabert)) 156 | - Adds two new things [\#36](https://github.com/hyperoslo/ImagePicker/pull/36) ([RamonGilabert](https://github.com/RamonGilabert)) 157 | - Improvements in the fetching. [\#34](https://github.com/hyperoslo/ImagePicker/pull/34) ([RamonGilabert](https://github.com/RamonGilabert)) 158 | - Fix/collectionview cells [\#33](https://github.com/hyperoslo/ImagePicker/pull/33) ([richardoti](https://github.com/richardoti)) 159 | - Improves the security in ImagePicker [\#32](https://github.com/hyperoslo/ImagePicker/pull/32) ([RamonGilabert](https://github.com/RamonGilabert)) 160 | - Swift 2.0 [\#31](https://github.com/hyperoslo/ImagePicker/pull/31) ([RamonGilabert](https://github.com/RamonGilabert)) 161 | - Fix image rotation [\#30](https://github.com/hyperoslo/ImagePicker/pull/30) ([richardoti](https://github.com/richardoti)) 162 | - Fix/crash [\#28](https://github.com/hyperoslo/ImagePicker/pull/28) ([richardoti](https://github.com/richardoti)) 163 | - Fix image rotation bug [\#26](https://github.com/hyperoslo/ImagePicker/pull/26) ([richardoti](https://github.com/richardoti)) 164 | - Make suffix\(\) zero-based [\#25](https://github.com/hyperoslo/ImagePicker/pull/25) ([richardoti](https://github.com/richardoti)) 165 | - Improve/usability [\#23](https://github.com/hyperoslo/ImagePicker/pull/23) ([RamonGilabert](https://github.com/RamonGilabert)) 166 | - Fixes the iPhone 4 problem [\#21](https://github.com/hyperoslo/ImagePicker/pull/21) ([RamonGilabert](https://github.com/RamonGilabert)) 167 | - Improvements/refactoring matching desings [\#18](https://github.com/hyperoslo/ImagePicker/pull/18) ([RamonGilabert](https://github.com/RamonGilabert)) 168 | - Remove shared instance [\#16](https://github.com/hyperoslo/ImagePicker/pull/16) ([richardoti](https://github.com/richardoti)) 169 | - Fix/image picker bug fixes [\#15](https://github.com/hyperoslo/ImagePicker/pull/15) ([richardoti](https://github.com/richardoti)) 170 | - Improve/image stack [\#14](https://github.com/hyperoslo/ImagePicker/pull/14) ([richardoti](https://github.com/richardoti)) 171 | - Refactor [\#13](https://github.com/hyperoslo/ImagePicker/pull/13) ([richardoti](https://github.com/richardoti)) 172 | - Transfer branch [\#12](https://github.com/hyperoslo/ImagePicker/pull/12) ([richardoti](https://github.com/richardoti)) 173 | - Added public everywhere [\#10](https://github.com/hyperoslo/ImagePicker/pull/10) ([RamonGilabert](https://github.com/RamonGilabert)) 174 | - Added new delegate method [\#9](https://github.com/hyperoslo/ImagePicker/pull/9) ([RamonGilabert](https://github.com/RamonGilabert)) 175 | - Added public protocol [\#8](https://github.com/hyperoslo/ImagePicker/pull/8) ([RamonGilabert](https://github.com/RamonGilabert)) 176 | - Feature/actual camera [\#7](https://github.com/hyperoslo/ImagePicker/pull/7) ([RamonGilabert](https://github.com/RamonGilabert)) 177 | - Replace UIColor with .color\(\) [\#3](https://github.com/hyperoslo/ImagePicker/pull/3) ([richardoti](https://github.com/richardoti)) 178 | - First implementation [\#1](https://github.com/hyperoslo/ImagePicker/pull/1) ([RamonGilabert](https://github.com/RamonGilabert)) 179 | 180 | 181 | 182 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contribute 2 | 3 | 1. Fork it 4 | 2. Create your feature branch (`git checkout -b my-new-feature`) 5 | 3. Commit your changes (`git commit -am 'Add some feature'`) 6 | 4. Push to the branch (`git push origin my-new-feature`) 7 | 5. Create pull request 8 | 9 | Make sure you check our [Playbook](https://github.com/hyperoslo/iOS-playbook) before contributing! :) 10 | 11 | ### Other information 12 | 13 | GitHub Issues is for reporting bugs, discussing features and general feedback in **ImagePicker**. Be sure to check our [documentation](http://cocoadocs.org/docsets/ImagePicker), [FAQ](https://github.com/hyperoslo/ImagePicker/wiki/FAQ) and [past issues](https://github.com/hyperoslo/ImagePicker/issues?state=closed) before opening any new issues. 14 | 15 | If you are posting about a crash in your application, a stack trace is helpful, but additional context, in the form of code and explanation, is necessary to be of any use. 16 | -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/ImagePickerDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 29D699DF1B70ABFC0021FA73 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29D699DE1B70ABFC0021FA73 /* AppDelegate.swift */; }; 11 | 29D699E61B70ABFC0021FA73 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 29D699E51B70ABFC0021FA73 /* Images.xcassets */; }; 12 | 29D699E91B70ABFC0021FA73 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 29D699E71B70ABFC0021FA73 /* LaunchScreen.xib */; }; 13 | C8F4D55202BE019B86A4E77D /* Pods_ImagePickerDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FFC61735F9A2B4D6548267F9 /* Pods_ImagePickerDemo.framework */; }; 14 | D20AA8A51D5330100085FF5B /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20AA8A41D5330100085FF5B /* ViewController.swift */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXFileReference section */ 18 | 29D699D91B70ABFC0021FA73 /* ImagePickerDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ImagePickerDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 19 | 29D699DD1B70ABFC0021FA73 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 20 | 29D699DE1B70ABFC0021FA73 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 21 | 29D699E51B70ABFC0021FA73 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 22 | 29D699E81B70ABFC0021FA73 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 23 | 733A7AD0105A657A80502E72 /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | B395016097341D865EDC2786 /* Pods-ImagePickerDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImagePickerDemo.debug.xcconfig"; path = "Target Support Files/Pods-ImagePickerDemo/Pods-ImagePickerDemo.debug.xcconfig"; sourceTree = ""; }; 25 | C44A895B3EB7319444A79C21 /* Pods-ImagePickerDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ImagePickerDemo.release.xcconfig"; path = "Target Support Files/Pods-ImagePickerDemo/Pods-ImagePickerDemo.release.xcconfig"; sourceTree = ""; }; 26 | D20AA8A41D5330100085FF5B /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 27 | FFC61735F9A2B4D6548267F9 /* Pods_ImagePickerDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ImagePickerDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 28 | /* End PBXFileReference section */ 29 | 30 | /* Begin PBXFrameworksBuildPhase section */ 31 | 29D699D61B70ABFC0021FA73 /* Frameworks */ = { 32 | isa = PBXFrameworksBuildPhase; 33 | buildActionMask = 2147483647; 34 | files = ( 35 | C8F4D55202BE019B86A4E77D /* Pods_ImagePickerDemo.framework in Frameworks */, 36 | ); 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXFrameworksBuildPhase section */ 40 | 41 | /* Begin PBXGroup section */ 42 | 29D699D01B70ABFC0021FA73 = { 43 | isa = PBXGroup; 44 | children = ( 45 | 29D699DB1B70ABFC0021FA73 /* ImagePickerDemo */, 46 | 29D699DA1B70ABFC0021FA73 /* Products */, 47 | DD112158CF9886DE925FED5E /* Frameworks */, 48 | 83910006B3F12E4B5D35FECA /* Pods */, 49 | ); 50 | indentWidth = 2; 51 | sourceTree = ""; 52 | tabWidth = 2; 53 | }; 54 | 29D699DA1B70ABFC0021FA73 /* Products */ = { 55 | isa = PBXGroup; 56 | children = ( 57 | 29D699D91B70ABFC0021FA73 /* ImagePickerDemo.app */, 58 | ); 59 | name = Products; 60 | sourceTree = ""; 61 | }; 62 | 29D699DB1B70ABFC0021FA73 /* ImagePickerDemo */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | 29D699DE1B70ABFC0021FA73 /* AppDelegate.swift */, 66 | 29D699E51B70ABFC0021FA73 /* Images.xcassets */, 67 | 29D699E71B70ABFC0021FA73 /* LaunchScreen.xib */, 68 | 29D699DC1B70ABFC0021FA73 /* Supporting Files */, 69 | D20AA8A41D5330100085FF5B /* ViewController.swift */, 70 | ); 71 | path = ImagePickerDemo; 72 | sourceTree = ""; 73 | }; 74 | 29D699DC1B70ABFC0021FA73 /* Supporting Files */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | 29D699DD1B70ABFC0021FA73 /* Info.plist */, 78 | ); 79 | name = "Supporting Files"; 80 | sourceTree = ""; 81 | }; 82 | 83910006B3F12E4B5D35FECA /* Pods */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | B395016097341D865EDC2786 /* Pods-ImagePickerDemo.debug.xcconfig */, 86 | C44A895B3EB7319444A79C21 /* Pods-ImagePickerDemo.release.xcconfig */, 87 | ); 88 | path = Pods; 89 | sourceTree = ""; 90 | }; 91 | DD112158CF9886DE925FED5E /* Frameworks */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 733A7AD0105A657A80502E72 /* Pods.framework */, 95 | FFC61735F9A2B4D6548267F9 /* Pods_ImagePickerDemo.framework */, 96 | ); 97 | name = Frameworks; 98 | sourceTree = ""; 99 | }; 100 | /* End PBXGroup section */ 101 | 102 | /* Begin PBXNativeTarget section */ 103 | 29D699D81B70ABFC0021FA73 /* ImagePickerDemo */ = { 104 | isa = PBXNativeTarget; 105 | buildConfigurationList = 29D699F81B70ABFC0021FA73 /* Build configuration list for PBXNativeTarget "ImagePickerDemo" */; 106 | buildPhases = ( 107 | 83A82AF336A335387958FC7E /* [CP] Check Pods Manifest.lock */, 108 | 29D699D51B70ABFC0021FA73 /* Sources */, 109 | 29D699D61B70ABFC0021FA73 /* Frameworks */, 110 | 29D699D71B70ABFC0021FA73 /* Resources */, 111 | 4A386DDFD23A61E26D0CD500 /* [CP] Embed Pods Frameworks */, 112 | ); 113 | buildRules = ( 114 | ); 115 | dependencies = ( 116 | ); 117 | name = ImagePickerDemo; 118 | productName = ImagePickerDemo; 119 | productReference = 29D699D91B70ABFC0021FA73 /* ImagePickerDemo.app */; 120 | productType = "com.apple.product-type.application"; 121 | }; 122 | /* End PBXNativeTarget section */ 123 | 124 | /* Begin PBXProject section */ 125 | 29D699D11B70ABFC0021FA73 /* Project object */ = { 126 | isa = PBXProject; 127 | attributes = { 128 | LastSwiftUpdateCheck = 0700; 129 | LastUpgradeCheck = 1150; 130 | ORGANIZATIONNAME = "Ramon Gilabert Llop"; 131 | TargetAttributes = { 132 | 29D699D81B70ABFC0021FA73 = { 133 | CreatedOnToolsVersion = 6.4; 134 | DevelopmentTeam = D34XZHQLE3; 135 | LastSwiftMigration = 1150; 136 | }; 137 | }; 138 | }; 139 | buildConfigurationList = 29D699D41B70ABFC0021FA73 /* Build configuration list for PBXProject "ImagePickerDemo" */; 140 | compatibilityVersion = "Xcode 3.2"; 141 | developmentRegion = en; 142 | hasScannedForEncodings = 0; 143 | knownRegions = ( 144 | en, 145 | Base, 146 | ); 147 | mainGroup = 29D699D01B70ABFC0021FA73; 148 | productRefGroup = 29D699DA1B70ABFC0021FA73 /* Products */; 149 | projectDirPath = ""; 150 | projectRoot = ""; 151 | targets = ( 152 | 29D699D81B70ABFC0021FA73 /* ImagePickerDemo */, 153 | ); 154 | }; 155 | /* End PBXProject section */ 156 | 157 | /* Begin PBXResourcesBuildPhase section */ 158 | 29D699D71B70ABFC0021FA73 /* Resources */ = { 159 | isa = PBXResourcesBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | 29D699E91B70ABFC0021FA73 /* LaunchScreen.xib in Resources */, 163 | 29D699E61B70ABFC0021FA73 /* Images.xcassets in Resources */, 164 | ); 165 | runOnlyForDeploymentPostprocessing = 0; 166 | }; 167 | /* End PBXResourcesBuildPhase section */ 168 | 169 | /* Begin PBXShellScriptBuildPhase section */ 170 | 4A386DDFD23A61E26D0CD500 /* [CP] Embed Pods Frameworks */ = { 171 | isa = PBXShellScriptBuildPhase; 172 | buildActionMask = 2147483647; 173 | files = ( 174 | ); 175 | inputPaths = ( 176 | "${PODS_ROOT}/Target Support Files/Pods-ImagePickerDemo/Pods-ImagePickerDemo-frameworks.sh", 177 | "${BUILT_PRODUCTS_DIR}/ImagePicker/ImagePicker.framework", 178 | ); 179 | name = "[CP] Embed Pods Frameworks"; 180 | outputPaths = ( 181 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ImagePicker.framework", 182 | ); 183 | runOnlyForDeploymentPostprocessing = 0; 184 | shellPath = /bin/sh; 185 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ImagePickerDemo/Pods-ImagePickerDemo-frameworks.sh\"\n"; 186 | showEnvVarsInLog = 0; 187 | }; 188 | 83A82AF336A335387958FC7E /* [CP] Check Pods Manifest.lock */ = { 189 | isa = PBXShellScriptBuildPhase; 190 | buildActionMask = 2147483647; 191 | files = ( 192 | ); 193 | inputFileListPaths = ( 194 | ); 195 | inputPaths = ( 196 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 197 | "${PODS_ROOT}/Manifest.lock", 198 | ); 199 | name = "[CP] Check Pods Manifest.lock"; 200 | outputFileListPaths = ( 201 | ); 202 | outputPaths = ( 203 | "$(DERIVED_FILE_DIR)/Pods-ImagePickerDemo-checkManifestLockResult.txt", 204 | ); 205 | runOnlyForDeploymentPostprocessing = 0; 206 | shellPath = /bin/sh; 207 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 208 | showEnvVarsInLog = 0; 209 | }; 210 | /* End PBXShellScriptBuildPhase section */ 211 | 212 | /* Begin PBXSourcesBuildPhase section */ 213 | 29D699D51B70ABFC0021FA73 /* Sources */ = { 214 | isa = PBXSourcesBuildPhase; 215 | buildActionMask = 2147483647; 216 | files = ( 217 | D20AA8A51D5330100085FF5B /* ViewController.swift in Sources */, 218 | 29D699DF1B70ABFC0021FA73 /* AppDelegate.swift in Sources */, 219 | ); 220 | runOnlyForDeploymentPostprocessing = 0; 221 | }; 222 | /* End PBXSourcesBuildPhase section */ 223 | 224 | /* Begin PBXVariantGroup section */ 225 | 29D699E71B70ABFC0021FA73 /* LaunchScreen.xib */ = { 226 | isa = PBXVariantGroup; 227 | children = ( 228 | 29D699E81B70ABFC0021FA73 /* Base */, 229 | ); 230 | name = LaunchScreen.xib; 231 | sourceTree = ""; 232 | }; 233 | /* End PBXVariantGroup section */ 234 | 235 | /* Begin XCBuildConfiguration section */ 236 | 29D699F61B70ABFC0021FA73 /* Debug */ = { 237 | isa = XCBuildConfiguration; 238 | buildSettings = { 239 | ALWAYS_SEARCH_USER_PATHS = NO; 240 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 241 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 242 | CLANG_CXX_LIBRARY = "libc++"; 243 | CLANG_ENABLE_MODULES = YES; 244 | CLANG_ENABLE_OBJC_ARC = YES; 245 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 246 | CLANG_WARN_BOOL_CONVERSION = YES; 247 | CLANG_WARN_COMMA = YES; 248 | CLANG_WARN_CONSTANT_CONVERSION = YES; 249 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 250 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 251 | CLANG_WARN_EMPTY_BODY = YES; 252 | CLANG_WARN_ENUM_CONVERSION = YES; 253 | CLANG_WARN_INFINITE_RECURSION = YES; 254 | CLANG_WARN_INT_CONVERSION = YES; 255 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 256 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 257 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 258 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 259 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 260 | CLANG_WARN_STRICT_PROTOTYPES = YES; 261 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 262 | CLANG_WARN_UNREACHABLE_CODE = YES; 263 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 264 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 265 | COPY_PHASE_STRIP = NO; 266 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 267 | ENABLE_STRICT_OBJC_MSGSEND = YES; 268 | ENABLE_TESTABILITY = YES; 269 | GCC_C_LANGUAGE_STANDARD = gnu99; 270 | GCC_DYNAMIC_NO_PIC = NO; 271 | GCC_NO_COMMON_BLOCKS = YES; 272 | GCC_OPTIMIZATION_LEVEL = 0; 273 | GCC_PREPROCESSOR_DEFINITIONS = ( 274 | "DEBUG=1", 275 | "$(inherited)", 276 | ); 277 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 278 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 279 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 280 | GCC_WARN_UNDECLARED_SELECTOR = YES; 281 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 282 | GCC_WARN_UNUSED_FUNCTION = YES; 283 | GCC_WARN_UNUSED_VARIABLE = YES; 284 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 285 | MTL_ENABLE_DEBUG_INFO = YES; 286 | ONLY_ACTIVE_ARCH = YES; 287 | SDKROOT = iphoneos; 288 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 289 | SWIFT_VERSION = 4.0; 290 | TARGETED_DEVICE_FAMILY = "1,2"; 291 | }; 292 | name = Debug; 293 | }; 294 | 29D699F71B70ABFC0021FA73 /* Release */ = { 295 | isa = XCBuildConfiguration; 296 | buildSettings = { 297 | ALWAYS_SEARCH_USER_PATHS = NO; 298 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 299 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 300 | CLANG_CXX_LIBRARY = "libc++"; 301 | CLANG_ENABLE_MODULES = YES; 302 | CLANG_ENABLE_OBJC_ARC = YES; 303 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 304 | CLANG_WARN_BOOL_CONVERSION = YES; 305 | CLANG_WARN_COMMA = YES; 306 | CLANG_WARN_CONSTANT_CONVERSION = YES; 307 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 308 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 309 | CLANG_WARN_EMPTY_BODY = YES; 310 | CLANG_WARN_ENUM_CONVERSION = YES; 311 | CLANG_WARN_INFINITE_RECURSION = YES; 312 | CLANG_WARN_INT_CONVERSION = YES; 313 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 314 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 315 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 316 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 317 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 318 | CLANG_WARN_STRICT_PROTOTYPES = YES; 319 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 320 | CLANG_WARN_UNREACHABLE_CODE = YES; 321 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 322 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 323 | COPY_PHASE_STRIP = NO; 324 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 325 | ENABLE_NS_ASSERTIONS = NO; 326 | ENABLE_STRICT_OBJC_MSGSEND = YES; 327 | GCC_C_LANGUAGE_STANDARD = gnu99; 328 | GCC_NO_COMMON_BLOCKS = YES; 329 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 330 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 331 | GCC_WARN_UNDECLARED_SELECTOR = YES; 332 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 333 | GCC_WARN_UNUSED_FUNCTION = YES; 334 | GCC_WARN_UNUSED_VARIABLE = YES; 335 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 336 | MTL_ENABLE_DEBUG_INFO = NO; 337 | SDKROOT = iphoneos; 338 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 339 | SWIFT_VERSION = 4.0; 340 | TARGETED_DEVICE_FAMILY = "1,2"; 341 | VALIDATE_PRODUCT = YES; 342 | }; 343 | name = Release; 344 | }; 345 | 29D699F91B70ABFC0021FA73 /* Debug */ = { 346 | isa = XCBuildConfiguration; 347 | baseConfigurationReference = B395016097341D865EDC2786 /* Pods-ImagePickerDemo.debug.xcconfig */; 348 | buildSettings = { 349 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 350 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 351 | CODE_SIGN_IDENTITY = "iPhone Developer"; 352 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 353 | DEVELOPMENT_TEAM = D34XZHQLE3; 354 | INFOPLIST_FILE = ImagePickerDemo/Info.plist; 355 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 356 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 357 | PRODUCT_BUNDLE_IDENTIFIER = demo.no.hyper.ImagePicker; 358 | PRODUCT_NAME = "$(TARGET_NAME)"; 359 | PROVISIONING_PROFILE = ""; 360 | SWIFT_VERSION = 5.0; 361 | }; 362 | name = Debug; 363 | }; 364 | 29D699FA1B70ABFC0021FA73 /* Release */ = { 365 | isa = XCBuildConfiguration; 366 | baseConfigurationReference = C44A895B3EB7319444A79C21 /* Pods-ImagePickerDemo.release.xcconfig */; 367 | buildSettings = { 368 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 369 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 370 | CODE_SIGN_IDENTITY = "iPhone Developer"; 371 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 372 | DEVELOPMENT_TEAM = D34XZHQLE3; 373 | INFOPLIST_FILE = ImagePickerDemo/Info.plist; 374 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 375 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 376 | PRODUCT_BUNDLE_IDENTIFIER = demo.no.hyper.ImagePicker; 377 | PRODUCT_NAME = "$(TARGET_NAME)"; 378 | PROVISIONING_PROFILE = ""; 379 | SWIFT_VERSION = 5.0; 380 | }; 381 | name = Release; 382 | }; 383 | /* End XCBuildConfiguration section */ 384 | 385 | /* Begin XCConfigurationList section */ 386 | 29D699D41B70ABFC0021FA73 /* Build configuration list for PBXProject "ImagePickerDemo" */ = { 387 | isa = XCConfigurationList; 388 | buildConfigurations = ( 389 | 29D699F61B70ABFC0021FA73 /* Debug */, 390 | 29D699F71B70ABFC0021FA73 /* Release */, 391 | ); 392 | defaultConfigurationIsVisible = 0; 393 | defaultConfigurationName = Debug; 394 | }; 395 | 29D699F81B70ABFC0021FA73 /* Build configuration list for PBXNativeTarget "ImagePickerDemo" */ = { 396 | isa = XCConfigurationList; 397 | buildConfigurations = ( 398 | 29D699F91B70ABFC0021FA73 /* Debug */, 399 | 29D699FA1B70ABFC0021FA73 /* Release */, 400 | ); 401 | defaultConfigurationIsVisible = 0; 402 | defaultConfigurationName = Debug; 403 | }; 404 | /* End XCConfigurationList section */ 405 | }; 406 | rootObject = 29D699D11B70ABFC0021FA73 /* Project object */; 407 | } 408 | -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/ImagePickerDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/ImagePickerDemo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/ImagePickerDemo.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/ImagePickerDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @UIApplicationMain 4 | class AppDelegate: UIResponder, UIApplicationDelegate { 5 | 6 | lazy var controller: UIViewController = ViewController() 7 | 8 | var window: UIWindow? 9 | 10 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 11 | window = UIWindow() 12 | window?.rootViewController = controller 13 | window?.makeKeyAndVisible() 14 | 15 | return true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/ImagePickerDemo/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 | -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "29x29", 26 | "scale" : "3x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-40@3x.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "57x57", 41 | "idiom" : "iphone", 42 | "filename" : "Icon.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "57x57", 47 | "idiom" : "iphone", 48 | "filename" : "Icon@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-60@2x.png", 55 | "scale" : "2x" 56 | }, 57 | { 58 | "size" : "60x60", 59 | "idiom" : "iphone", 60 | "filename" : "Icon-60@3x.png", 61 | "scale" : "3x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "20x20", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "20x20", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "size" : "29x29", 75 | "idiom" : "ipad", 76 | "filename" : "Icon-Small.png", 77 | "scale" : "1x" 78 | }, 79 | { 80 | "idiom" : "ipad", 81 | "size" : "29x29", 82 | "scale" : "2x" 83 | }, 84 | { 85 | "size" : "40x40", 86 | "idiom" : "ipad", 87 | "filename" : "Icon-40.png", 88 | "scale" : "1x" 89 | }, 90 | { 91 | "size" : "40x40", 92 | "idiom" : "ipad", 93 | "filename" : "Icon-40@2x.png", 94 | "scale" : "2x" 95 | }, 96 | { 97 | "size" : "50x50", 98 | "idiom" : "ipad", 99 | "filename" : "Icon-Small-50.png", 100 | "scale" : "1x" 101 | }, 102 | { 103 | "size" : "50x50", 104 | "idiom" : "ipad", 105 | "filename" : "Icon-Small-50@2x.png", 106 | "scale" : "2x" 107 | }, 108 | { 109 | "size" : "72x72", 110 | "idiom" : "ipad", 111 | "filename" : "Icon-72.png", 112 | "scale" : "1x" 113 | }, 114 | { 115 | "size" : "72x72", 116 | "idiom" : "ipad", 117 | "filename" : "Icon-72@2x.png", 118 | "scale" : "2x" 119 | }, 120 | { 121 | "size" : "76x76", 122 | "idiom" : "ipad", 123 | "filename" : "Icon-76.png", 124 | "scale" : "1x" 125 | }, 126 | { 127 | "size" : "76x76", 128 | "idiom" : "ipad", 129 | "filename" : "Icon-76@2x.png", 130 | "scale" : "2x" 131 | }, 132 | { 133 | "size" : "83.5x83.5", 134 | "idiom" : "ipad", 135 | "filename" : "Icon-83.5@2x.png", 136 | "scale" : "2x" 137 | }, 138 | { 139 | "size" : "24x24", 140 | "idiom" : "watch", 141 | "scale" : "2x", 142 | "role" : "notificationCenter", 143 | "subtype" : "38mm" 144 | }, 145 | { 146 | "size" : "27.5x27.5", 147 | "idiom" : "watch", 148 | "scale" : "2x", 149 | "role" : "notificationCenter", 150 | "subtype" : "42mm" 151 | }, 152 | { 153 | "size" : "29x29", 154 | "idiom" : "watch", 155 | "filename" : "Icon-Small@2x.png", 156 | "role" : "companionSettings", 157 | "scale" : "2x" 158 | }, 159 | { 160 | "size" : "29x29", 161 | "idiom" : "watch", 162 | "filename" : "Icon-Small@3x.png", 163 | "role" : "companionSettings", 164 | "scale" : "3x" 165 | }, 166 | { 167 | "size" : "40x40", 168 | "idiom" : "watch", 169 | "scale" : "2x", 170 | "role" : "appLauncher", 171 | "subtype" : "38mm" 172 | }, 173 | { 174 | "size" : "86x86", 175 | "idiom" : "watch", 176 | "scale" : "2x", 177 | "role" : "quickLook", 178 | "subtype" : "38mm" 179 | }, 180 | { 181 | "size" : "98x98", 182 | "idiom" : "watch", 183 | "scale" : "2x", 184 | "role" : "quickLook", 185 | "subtype" : "42mm" 186 | } 187 | ], 188 | "info" : { 189 | "version" : 1, 190 | "author" : "xcode" 191 | } 192 | } -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/ImagePicker/d8a8f840380b9a28230c8fcad277adb458706d0a/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-40.png -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/ImagePicker/d8a8f840380b9a28230c8fcad277adb458706d0a/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/ImagePicker/d8a8f840380b9a28230c8fcad277adb458706d0a/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/ImagePicker/d8a8f840380b9a28230c8fcad277adb458706d0a/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/ImagePicker/d8a8f840380b9a28230c8fcad277adb458706d0a/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/ImagePicker/d8a8f840380b9a28230c8fcad277adb458706d0a/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-72.png -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/ImagePicker/d8a8f840380b9a28230c8fcad277adb458706d0a/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-72@2x.png -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/ImagePicker/d8a8f840380b9a28230c8fcad277adb458706d0a/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/ImagePicker/d8a8f840380b9a28230c8fcad277adb458706d0a/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/ImagePicker/d8a8f840380b9a28230c8fcad277adb458706d0a/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-83.5@2x.png -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-Small-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/ImagePicker/d8a8f840380b9a28230c8fcad277adb458706d0a/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-Small-50.png -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/ImagePicker/d8a8f840380b9a28230c8fcad277adb458706d0a/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/ImagePicker/d8a8f840380b9a28230c8fcad277adb458706d0a/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-Small.png -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/ImagePicker/d8a8f840380b9a28230c8fcad277adb458706d0a/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/ImagePicker/d8a8f840380b9a28230c8fcad277adb458706d0a/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/ImagePicker/d8a8f840380b9a28230c8fcad277adb458706d0a/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon.png -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/ImagePicker/d8a8f840380b9a28230c8fcad277adb458706d0a/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon@2x.png -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/ImagePickerDemo/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 | ImagePicker 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSCameraUsageDescription 26 | This app uses the camera for testing purposes 27 | NSLocationWhenInUseUsageDescription 28 | This app requires location permision 29 | NSPhotoLibraryUsageDescription 30 | This app uses the library for testing purposes 31 | UILaunchStoryboardName 32 | LaunchScreen 33 | UIRequiredDeviceCapabilities 34 | 35 | armv7 36 | 37 | UIRequiresFullScreen 38 | 39 | UISupportedInterfaceOrientations 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | UISupportedInterfaceOrientations~ipad 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationPortraitUpsideDown 50 | UIInterfaceOrientationLandscapeLeft 51 | UIInterfaceOrientationLandscapeRight 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/ImagePickerDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import ImagePicker 3 | 4 | class ViewController: UIViewController, ImagePickerDelegate { 5 | 6 | lazy var button: UIButton = self.makeButton() 7 | 8 | override func viewDidLoad() { 9 | super.viewDidLoad() 10 | 11 | view.backgroundColor = UIColor.white 12 | view.addSubview(button) 13 | button.translatesAutoresizingMaskIntoConstraints = false 14 | 15 | view.addConstraint( 16 | NSLayoutConstraint(item: button, attribute: .centerX, 17 | relatedBy: .equal, toItem: view, 18 | attribute: .centerX, multiplier: 1, 19 | constant: 0)) 20 | 21 | view.addConstraint( 22 | NSLayoutConstraint(item: button, attribute: .centerY, 23 | relatedBy: .equal, toItem: view, 24 | attribute: .centerY, multiplier: 1, 25 | constant: 0)) 26 | } 27 | 28 | func makeButton() -> UIButton { 29 | let button = UIButton() 30 | button.setTitle("Show ImagePicker", for: .normal) 31 | button.setTitleColor(UIColor.black, for: .normal) 32 | button.addTarget(self, action: #selector(buttonTouched(button:)), for: .touchUpInside) 33 | 34 | return button 35 | } 36 | 37 | @objc func buttonTouched(button: UIButton) { 38 | let config = ImagePickerConfiguration() 39 | config.doneButtonTitle = "Finish" 40 | config.noImagesTitle = "Sorry! There are no images here!" 41 | config.recordLocation = false 42 | config.allowVideoSelection = true 43 | 44 | let imagePicker = ImagePickerController(configuration: config) 45 | imagePicker.delegate = self 46 | 47 | present(imagePicker, animated: true, completion: nil) 48 | } 49 | 50 | // MARK: - ImagePickerDelegate 51 | 52 | func cancelButtonDidPress(_ imagePicker: ImagePickerController) { 53 | imagePicker.dismiss(animated: true, completion: nil) 54 | } 55 | 56 | func wrapperDidPress(_ imagePicker: ImagePickerController, images: [UIImage]) { 57 | /* 58 | guard images.count > 0 else { return } 59 | 60 | let lightboxImages = images.map { 61 | return LightboxImage(image: $0) 62 | } 63 | 64 | let lightbox = LightboxController(images: lightboxImages, startIndex: 0) 65 | imagePicker.present(lightbox, animated: true, completion: nil) 66 | */ 67 | } 68 | 69 | func doneButtonDidPress(_ imagePicker: ImagePickerController, images: [UIImage]) { 70 | imagePicker.dismiss(animated: true, completion: nil) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '9.2' 2 | 3 | use_frameworks! 4 | inhibit_all_warnings! 5 | 6 | target 'ImagePickerDemo' do 7 | pod 'ImagePicker', path: '../../' 8 | end 9 | -------------------------------------------------------------------------------- /Demo/ImagePickerDemo/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - ImagePicker (3.1.0) 3 | 4 | DEPENDENCIES: 5 | - ImagePicker (from `../../`) 6 | 7 | EXTERNAL SOURCES: 8 | ImagePicker: 9 | :path: "../../" 10 | 11 | SPEC CHECKSUMS: 12 | ImagePicker: db1fd7626337b2577c523bef5f499bbbe5a37079 13 | 14 | PODFILE CHECKSUM: 23900dc409ff6cf1fd583a459dd91c9db12e588b 15 | 16 | COCOAPODS: 1.9.3 17 | -------------------------------------------------------------------------------- /ImagePicker.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "ImagePicker" 3 | s.summary = "Reinventing the way ImagePicker works." 4 | s.version = "3.2.0" 5 | s.homepage = "https://github.com/hyperoslo/ImagePicker" 6 | s.license = 'MIT' 7 | s.author = { "Hyper Interaktiv AS" => "ios@hyper.no" } 8 | s.source = { :git => "https://github.com/hyperoslo/ImagePicker.git", :tag => s.version.to_s } 9 | s.social_media_url = 'https://twitter.com/hyperoslo' 10 | s.platform = :ios, '9.0' 11 | s.requires_arc = true 12 | s.source_files = 'Source/**/*' 13 | s.resource_bundles = { 'ImagePicker' => ['Images/*.{png}'] } 14 | s.frameworks = 'AVFoundation' 15 | s.swift_version = '5.0' 16 | end 17 | -------------------------------------------------------------------------------- /ImagePicker.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 39D134101CAC4B4E00EA2ECE /* AssetManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39D1340F1CAC4B4E00EA2ECE /* AssetManager.swift */; }; 11 | 39E3C3311CAFD79200340DAD /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39E3C3301CAFD79200340DAD /* LocationManager.swift */; }; 12 | D20FF6361CD23426000F3BFE /* CameraMan.swift in Sources */ = {isa = PBXBuildFile; fileRef = D20FF6351CD23426000F3BFE /* CameraMan.swift */; }; 13 | D25A8C2C1D47681E0008D2F7 /* ImageGalleryLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = D25A8C2B1D47681E0008D2F7 /* ImageGalleryLayout.swift */; }; 14 | D25A8C2E1D4768250008D2F7 /* Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D25A8C2D1D4768250008D2F7 /* Helper.swift */; }; 15 | D5D370C01C44FD1600690C0A /* AUTO@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D5D370BA1C44FD1600690C0A /* AUTO@3x.png */; }; 16 | D5D370C11C44FD1600690C0A /* cameraIcon@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D5D370BB1C44FD1600690C0A /* cameraIcon@3x.png */; }; 17 | D5D370C21C44FD1600690C0A /* focusIcon@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D5D370BC1C44FD1600690C0A /* focusIcon@3x.png */; }; 18 | D5D370C31C44FD1600690C0A /* OFF@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D5D370BD1C44FD1600690C0A /* OFF@3x.png */; }; 19 | D5D370C41C44FD1600690C0A /* ON@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D5D370BE1C44FD1600690C0A /* ON@3x.png */; }; 20 | D5D370C51C44FD1600690C0A /* selectedImageGallery@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = D5D370BF1C44FD1600690C0A /* selectedImageGallery@3x.png */; }; 21 | D5DC59C21C201CC4003BD79B /* BottomContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5DC59AD1C201CC4003BD79B /* BottomContainerView.swift */; }; 22 | D5DC59C31C201CC4003BD79B /* ButtonPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5DC59AE1C201CC4003BD79B /* ButtonPicker.swift */; }; 23 | D5DC59C41C201CC4003BD79B /* ImageStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5DC59AF1C201CC4003BD79B /* ImageStack.swift */; }; 24 | D5DC59C51C201CC4003BD79B /* StackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5DC59B01C201CC4003BD79B /* StackView.swift */; }; 25 | D5DC59C61C201CC4003BD79B /* CameraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5DC59B21C201CC4003BD79B /* CameraView.swift */; }; 26 | D5DC59C71C201CC4003BD79B /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5DC59B31C201CC4003BD79B /* Configuration.swift */; }; 27 | D5DC59C81C201CC4003BD79B /* ConstraintsSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5DC59B51C201CC4003BD79B /* ConstraintsSetup.swift */; }; 28 | D5DC59C91C201CC4003BD79B /* ImageGalleryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5DC59B71C201CC4003BD79B /* ImageGalleryView.swift */; }; 29 | D5DC59CA1C201CC4003BD79B /* ImageGalleryViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5DC59B81C201CC4003BD79B /* ImageGalleryViewCell.swift */; }; 30 | D5DC59CB1C201CC4003BD79B /* ImageGalleryViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5DC59B91C201CC4003BD79B /* ImageGalleryViewDataSource.swift */; }; 31 | D5DC59CC1C201CC4003BD79B /* ImagePickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5DC59BA1C201CC4003BD79B /* ImagePickerController.swift */; }; 32 | D5DC59CE1C201CC4003BD79B /* TopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5DC59BD1C201CC4003BD79B /* TopView.swift */; }; 33 | DC197E381E945FA500F2A7D1 /* video@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = DC197E371E945FA500F2A7D1 /* video@3x.png */; }; 34 | DC197E3C1E94DA5600F2A7D1 /* VideoInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC197E3B1E94DA5600F2A7D1 /* VideoInfoView.swift */; }; 35 | /* End PBXBuildFile section */ 36 | 37 | /* Begin PBXFileReference section */ 38 | 39D1340F1CAC4B4E00EA2ECE /* AssetManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetManager.swift; sourceTree = ""; }; 39 | 39E3C3301CAFD79200340DAD /* LocationManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = ""; }; 40 | 77F15A38249EE45A00FDF417 /* Package.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; 41 | D20FF6351CD23426000F3BFE /* CameraMan.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraMan.swift; sourceTree = ""; }; 42 | D25A8C2B1D47681E0008D2F7 /* ImageGalleryLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageGalleryLayout.swift; sourceTree = ""; }; 43 | D25A8C2D1D4768250008D2F7 /* Helper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Helper.swift; sourceTree = ""; }; 44 | D5D370BA1C44FD1600690C0A /* AUTO@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AUTO@3x.png"; sourceTree = ""; }; 45 | D5D370BB1C44FD1600690C0A /* cameraIcon@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "cameraIcon@3x.png"; sourceTree = ""; }; 46 | D5D370BC1C44FD1600690C0A /* focusIcon@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "focusIcon@3x.png"; sourceTree = ""; }; 47 | D5D370BD1C44FD1600690C0A /* OFF@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "OFF@3x.png"; sourceTree = ""; }; 48 | D5D370BE1C44FD1600690C0A /* ON@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "ON@3x.png"; sourceTree = ""; }; 49 | D5D370BF1C44FD1600690C0A /* selectedImageGallery@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "selectedImageGallery@3x.png"; sourceTree = ""; }; 50 | D5DC598B1C201BE1003BD79B /* ImagePicker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ImagePicker.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | D5DC59A91C201CC4003BD79B /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 52 | D5DC59AD1C201CC4003BD79B /* BottomContainerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BottomContainerView.swift; sourceTree = ""; }; 53 | D5DC59AE1C201CC4003BD79B /* ButtonPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonPicker.swift; sourceTree = ""; }; 54 | D5DC59AF1C201CC4003BD79B /* ImageStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageStack.swift; sourceTree = ""; }; 55 | D5DC59B01C201CC4003BD79B /* StackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackView.swift; sourceTree = ""; }; 56 | D5DC59B21C201CC4003BD79B /* CameraView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraView.swift; sourceTree = ""; }; 57 | D5DC59B31C201CC4003BD79B /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; 58 | D5DC59B51C201CC4003BD79B /* ConstraintsSetup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConstraintsSetup.swift; sourceTree = ""; }; 59 | D5DC59B71C201CC4003BD79B /* ImageGalleryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageGalleryView.swift; sourceTree = ""; }; 60 | D5DC59B81C201CC4003BD79B /* ImageGalleryViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageGalleryViewCell.swift; sourceTree = ""; }; 61 | D5DC59B91C201CC4003BD79B /* ImageGalleryViewDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageGalleryViewDataSource.swift; sourceTree = ""; }; 62 | D5DC59BA1C201CC4003BD79B /* ImagePickerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagePickerController.swift; sourceTree = ""; }; 63 | D5DC59BD1C201CC4003BD79B /* TopView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TopView.swift; sourceTree = ""; }; 64 | DC197E371E945FA500F2A7D1 /* video@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "video@3x.png"; sourceTree = ""; }; 65 | DC197E3B1E94DA5600F2A7D1 /* VideoInfoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoInfoView.swift; sourceTree = ""; }; 66 | /* End PBXFileReference section */ 67 | 68 | /* Begin PBXFrameworksBuildPhase section */ 69 | D5DC59871C201BE1003BD79B /* Frameworks */ = { 70 | isa = PBXFrameworksBuildPhase; 71 | buildActionMask = 2147483647; 72 | files = ( 73 | ); 74 | runOnlyForDeploymentPostprocessing = 0; 75 | }; 76 | /* End PBXFrameworksBuildPhase section */ 77 | 78 | /* Begin PBXGroup section */ 79 | 77F15A35249EE2C500FDF417 /* RootFiles */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | 77F15A38249EE45A00FDF417 /* Package.swift */, 83 | ); 84 | name = RootFiles; 85 | sourceTree = ""; 86 | }; 87 | D5D370B91C44FD1600690C0A /* Images */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | D5D370BA1C44FD1600690C0A /* AUTO@3x.png */, 91 | D5D370BB1C44FD1600690C0A /* cameraIcon@3x.png */, 92 | D5D370BC1C44FD1600690C0A /* focusIcon@3x.png */, 93 | D5D370BD1C44FD1600690C0A /* OFF@3x.png */, 94 | D5D370BE1C44FD1600690C0A /* ON@3x.png */, 95 | D5D370BF1C44FD1600690C0A /* selectedImageGallery@3x.png */, 96 | DC197E371E945FA500F2A7D1 /* video@3x.png */, 97 | ); 98 | path = Images; 99 | sourceTree = ""; 100 | }; 101 | D5DC59811C201BE1003BD79B = { 102 | isa = PBXGroup; 103 | children = ( 104 | D5D370B91C44FD1600690C0A /* Images */, 105 | D5DC59A81C201CC4003BD79B /* SupportFiles */, 106 | D5DC59AA1C201CC4003BD79B /* Source */, 107 | 77F15A35249EE2C500FDF417 /* RootFiles */, 108 | D5DC598C1C201BE1003BD79B /* Products */, 109 | ); 110 | indentWidth = 2; 111 | sourceTree = ""; 112 | tabWidth = 2; 113 | }; 114 | D5DC598C1C201BE1003BD79B /* Products */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | D5DC598B1C201BE1003BD79B /* ImagePicker.framework */, 118 | ); 119 | name = Products; 120 | sourceTree = ""; 121 | }; 122 | D5DC59A81C201CC4003BD79B /* SupportFiles */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | D5DC59A91C201CC4003BD79B /* Info.plist */, 126 | ); 127 | path = SupportFiles; 128 | sourceTree = ""; 129 | }; 130 | D5DC59AA1C201CC4003BD79B /* Source */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | D25A8C2D1D4768250008D2F7 /* Helper.swift */, 134 | D5DC59AC1C201CC4003BD79B /* BottomView */, 135 | D5DC59B11C201CC4003BD79B /* CameraView */, 136 | D5DC59B31C201CC4003BD79B /* Configuration.swift */, 137 | D5DC59B41C201CC4003BD79B /* Extensions */, 138 | D5DC59B61C201CC4003BD79B /* ImageGallery */, 139 | D5DC59BA1C201CC4003BD79B /* ImagePickerController.swift */, 140 | D5DC59BC1C201CC4003BD79B /* TopView */, 141 | 39D1340F1CAC4B4E00EA2ECE /* AssetManager.swift */, 142 | 39E3C3301CAFD79200340DAD /* LocationManager.swift */, 143 | ); 144 | path = Source; 145 | sourceTree = ""; 146 | }; 147 | D5DC59AC1C201CC4003BD79B /* BottomView */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | D5DC59AD1C201CC4003BD79B /* BottomContainerView.swift */, 151 | D5DC59AE1C201CC4003BD79B /* ButtonPicker.swift */, 152 | D5DC59AF1C201CC4003BD79B /* ImageStack.swift */, 153 | D5DC59B01C201CC4003BD79B /* StackView.swift */, 154 | ); 155 | path = BottomView; 156 | sourceTree = ""; 157 | }; 158 | D5DC59B11C201CC4003BD79B /* CameraView */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | D5DC59B21C201CC4003BD79B /* CameraView.swift */, 162 | D20FF6351CD23426000F3BFE /* CameraMan.swift */, 163 | ); 164 | path = CameraView; 165 | sourceTree = ""; 166 | }; 167 | D5DC59B41C201CC4003BD79B /* Extensions */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | D5DC59B51C201CC4003BD79B /* ConstraintsSetup.swift */, 171 | ); 172 | path = Extensions; 173 | sourceTree = ""; 174 | }; 175 | D5DC59B61C201CC4003BD79B /* ImageGallery */ = { 176 | isa = PBXGroup; 177 | children = ( 178 | D25A8C2B1D47681E0008D2F7 /* ImageGalleryLayout.swift */, 179 | D5DC59B71C201CC4003BD79B /* ImageGalleryView.swift */, 180 | D5DC59B81C201CC4003BD79B /* ImageGalleryViewCell.swift */, 181 | D5DC59B91C201CC4003BD79B /* ImageGalleryViewDataSource.swift */, 182 | DC197E3B1E94DA5600F2A7D1 /* VideoInfoView.swift */, 183 | ); 184 | path = ImageGallery; 185 | sourceTree = ""; 186 | }; 187 | D5DC59BC1C201CC4003BD79B /* TopView */ = { 188 | isa = PBXGroup; 189 | children = ( 190 | D5DC59BD1C201CC4003BD79B /* TopView.swift */, 191 | ); 192 | path = TopView; 193 | sourceTree = ""; 194 | }; 195 | /* End PBXGroup section */ 196 | 197 | /* Begin PBXHeadersBuildPhase section */ 198 | D5DC59881C201BE1003BD79B /* Headers */ = { 199 | isa = PBXHeadersBuildPhase; 200 | buildActionMask = 2147483647; 201 | files = ( 202 | ); 203 | runOnlyForDeploymentPostprocessing = 0; 204 | }; 205 | /* End PBXHeadersBuildPhase section */ 206 | 207 | /* Begin PBXNativeTarget section */ 208 | D5DC598A1C201BE1003BD79B /* ImagePicker-iOS */ = { 209 | isa = PBXNativeTarget; 210 | buildConfigurationList = D5DC599F1C201BE1003BD79B /* Build configuration list for PBXNativeTarget "ImagePicker-iOS" */; 211 | buildPhases = ( 212 | D5DC59861C201BE1003BD79B /* Sources */, 213 | D5DC59871C201BE1003BD79B /* Frameworks */, 214 | D5DC59881C201BE1003BD79B /* Headers */, 215 | D5DC59891C201BE1003BD79B /* Resources */, 216 | BDEA246A1CF1C0B5004D4642 /* ShellScript */, 217 | ); 218 | buildRules = ( 219 | ); 220 | dependencies = ( 221 | ); 222 | name = "ImagePicker-iOS"; 223 | productName = ImagePicker; 224 | productReference = D5DC598B1C201BE1003BD79B /* ImagePicker.framework */; 225 | productType = "com.apple.product-type.framework"; 226 | }; 227 | /* End PBXNativeTarget section */ 228 | 229 | /* Begin PBXProject section */ 230 | D5DC59821C201BE1003BD79B /* Project object */ = { 231 | isa = PBXProject; 232 | attributes = { 233 | LastSwiftUpdateCheck = 0720; 234 | LastUpgradeCheck = 1020; 235 | ORGANIZATIONNAME = "Hyper Interaktiv AS"; 236 | TargetAttributes = { 237 | D5DC598A1C201BE1003BD79B = { 238 | CreatedOnToolsVersion = 7.2; 239 | DevelopmentTeam = ADTR2923N7; 240 | LastSwiftMigration = 1020; 241 | }; 242 | }; 243 | }; 244 | buildConfigurationList = D5DC59851C201BE1003BD79B /* Build configuration list for PBXProject "ImagePicker" */; 245 | compatibilityVersion = "Xcode 3.2"; 246 | developmentRegion = en; 247 | hasScannedForEncodings = 0; 248 | knownRegions = ( 249 | en, 250 | Base, 251 | ); 252 | mainGroup = D5DC59811C201BE1003BD79B; 253 | productRefGroup = D5DC598C1C201BE1003BD79B /* Products */; 254 | projectDirPath = ""; 255 | projectRoot = ""; 256 | targets = ( 257 | D5DC598A1C201BE1003BD79B /* ImagePicker-iOS */, 258 | ); 259 | }; 260 | /* End PBXProject section */ 261 | 262 | /* Begin PBXResourcesBuildPhase section */ 263 | D5DC59891C201BE1003BD79B /* Resources */ = { 264 | isa = PBXResourcesBuildPhase; 265 | buildActionMask = 2147483647; 266 | files = ( 267 | D5D370C41C44FD1600690C0A /* ON@3x.png in Resources */, 268 | D5D370C21C44FD1600690C0A /* focusIcon@3x.png in Resources */, 269 | D5D370C01C44FD1600690C0A /* AUTO@3x.png in Resources */, 270 | D5D370C51C44FD1600690C0A /* selectedImageGallery@3x.png in Resources */, 271 | DC197E381E945FA500F2A7D1 /* video@3x.png in Resources */, 272 | D5D370C11C44FD1600690C0A /* cameraIcon@3x.png in Resources */, 273 | D5D370C31C44FD1600690C0A /* OFF@3x.png in Resources */, 274 | ); 275 | runOnlyForDeploymentPostprocessing = 0; 276 | }; 277 | /* End PBXResourcesBuildPhase section */ 278 | 279 | /* Begin PBXShellScriptBuildPhase section */ 280 | BDEA246A1CF1C0B5004D4642 /* ShellScript */ = { 281 | isa = PBXShellScriptBuildPhase; 282 | buildActionMask = 2147483647; 283 | files = ( 284 | ); 285 | inputPaths = ( 286 | ); 287 | outputPaths = ( 288 | ); 289 | runOnlyForDeploymentPostprocessing = 0; 290 | shellPath = /bin/sh; 291 | shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi"; 292 | }; 293 | /* End PBXShellScriptBuildPhase section */ 294 | 295 | /* Begin PBXSourcesBuildPhase section */ 296 | D5DC59861C201BE1003BD79B /* Sources */ = { 297 | isa = PBXSourcesBuildPhase; 298 | buildActionMask = 2147483647; 299 | files = ( 300 | DC197E3C1E94DA5600F2A7D1 /* VideoInfoView.swift in Sources */, 301 | D5DC59CE1C201CC4003BD79B /* TopView.swift in Sources */, 302 | D5DC59C41C201CC4003BD79B /* ImageStack.swift in Sources */, 303 | 39E3C3311CAFD79200340DAD /* LocationManager.swift in Sources */, 304 | D5DC59CB1C201CC4003BD79B /* ImageGalleryViewDataSource.swift in Sources */, 305 | D25A8C2C1D47681E0008D2F7 /* ImageGalleryLayout.swift in Sources */, 306 | D5DC59CA1C201CC4003BD79B /* ImageGalleryViewCell.swift in Sources */, 307 | D5DC59C81C201CC4003BD79B /* ConstraintsSetup.swift in Sources */, 308 | D5DC59C71C201CC4003BD79B /* Configuration.swift in Sources */, 309 | D25A8C2E1D4768250008D2F7 /* Helper.swift in Sources */, 310 | 39D134101CAC4B4E00EA2ECE /* AssetManager.swift in Sources */, 311 | D5DC59C21C201CC4003BD79B /* BottomContainerView.swift in Sources */, 312 | D5DC59CC1C201CC4003BD79B /* ImagePickerController.swift in Sources */, 313 | D5DC59C61C201CC4003BD79B /* CameraView.swift in Sources */, 314 | D5DC59C91C201CC4003BD79B /* ImageGalleryView.swift in Sources */, 315 | D5DC59C31C201CC4003BD79B /* ButtonPicker.swift in Sources */, 316 | D5DC59C51C201CC4003BD79B /* StackView.swift in Sources */, 317 | D20FF6361CD23426000F3BFE /* CameraMan.swift in Sources */, 318 | ); 319 | runOnlyForDeploymentPostprocessing = 0; 320 | }; 321 | /* End PBXSourcesBuildPhase section */ 322 | 323 | /* Begin XCBuildConfiguration section */ 324 | D5DC599D1C201BE1003BD79B /* Debug */ = { 325 | isa = XCBuildConfiguration; 326 | buildSettings = { 327 | ALWAYS_SEARCH_USER_PATHS = NO; 328 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 329 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 330 | CLANG_CXX_LIBRARY = "libc++"; 331 | CLANG_ENABLE_MODULES = YES; 332 | CLANG_ENABLE_OBJC_ARC = YES; 333 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 334 | CLANG_WARN_BOOL_CONVERSION = YES; 335 | CLANG_WARN_COMMA = YES; 336 | CLANG_WARN_CONSTANT_CONVERSION = YES; 337 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 338 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 339 | CLANG_WARN_EMPTY_BODY = YES; 340 | CLANG_WARN_ENUM_CONVERSION = YES; 341 | CLANG_WARN_INFINITE_RECURSION = YES; 342 | CLANG_WARN_INT_CONVERSION = YES; 343 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 344 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 345 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 346 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 347 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 348 | CLANG_WARN_STRICT_PROTOTYPES = YES; 349 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 350 | CLANG_WARN_UNREACHABLE_CODE = YES; 351 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 352 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 353 | COPY_PHASE_STRIP = NO; 354 | CURRENT_PROJECT_VERSION = 1; 355 | DEBUG_INFORMATION_FORMAT = dwarf; 356 | ENABLE_STRICT_OBJC_MSGSEND = YES; 357 | ENABLE_TESTABILITY = YES; 358 | GCC_C_LANGUAGE_STANDARD = gnu99; 359 | GCC_DYNAMIC_NO_PIC = NO; 360 | GCC_NO_COMMON_BLOCKS = YES; 361 | GCC_OPTIMIZATION_LEVEL = 0; 362 | GCC_PREPROCESSOR_DEFINITIONS = ( 363 | "DEBUG=1", 364 | "$(inherited)", 365 | ); 366 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 367 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 368 | GCC_WARN_UNDECLARED_SELECTOR = YES; 369 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 370 | GCC_WARN_UNUSED_FUNCTION = YES; 371 | GCC_WARN_UNUSED_VARIABLE = YES; 372 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 373 | MTL_ENABLE_DEBUG_INFO = YES; 374 | ONLY_ACTIVE_ARCH = YES; 375 | SDKROOT = iphoneos; 376 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 377 | SWIFT_VERSION = 4.0; 378 | TARGETED_DEVICE_FAMILY = "1,2"; 379 | VERSIONING_SYSTEM = "apple-generic"; 380 | VERSION_INFO_PREFIX = ""; 381 | }; 382 | name = Debug; 383 | }; 384 | D5DC599E1C201BE1003BD79B /* Release */ = { 385 | isa = XCBuildConfiguration; 386 | buildSettings = { 387 | ALWAYS_SEARCH_USER_PATHS = NO; 388 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 389 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 390 | CLANG_CXX_LIBRARY = "libc++"; 391 | CLANG_ENABLE_MODULES = YES; 392 | CLANG_ENABLE_OBJC_ARC = YES; 393 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 394 | CLANG_WARN_BOOL_CONVERSION = YES; 395 | CLANG_WARN_COMMA = YES; 396 | CLANG_WARN_CONSTANT_CONVERSION = YES; 397 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 398 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 399 | CLANG_WARN_EMPTY_BODY = YES; 400 | CLANG_WARN_ENUM_CONVERSION = YES; 401 | CLANG_WARN_INFINITE_RECURSION = YES; 402 | CLANG_WARN_INT_CONVERSION = YES; 403 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 404 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 405 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 406 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 407 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 408 | CLANG_WARN_STRICT_PROTOTYPES = YES; 409 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 410 | CLANG_WARN_UNREACHABLE_CODE = YES; 411 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 412 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 413 | COPY_PHASE_STRIP = NO; 414 | CURRENT_PROJECT_VERSION = 1; 415 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 416 | ENABLE_NS_ASSERTIONS = NO; 417 | ENABLE_STRICT_OBJC_MSGSEND = YES; 418 | GCC_C_LANGUAGE_STANDARD = gnu99; 419 | GCC_NO_COMMON_BLOCKS = YES; 420 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 421 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 422 | GCC_WARN_UNDECLARED_SELECTOR = YES; 423 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 424 | GCC_WARN_UNUSED_FUNCTION = YES; 425 | GCC_WARN_UNUSED_VARIABLE = YES; 426 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 427 | MTL_ENABLE_DEBUG_INFO = NO; 428 | SDKROOT = iphoneos; 429 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 430 | SWIFT_VERSION = 4.0; 431 | TARGETED_DEVICE_FAMILY = "1,2"; 432 | VALIDATE_PRODUCT = YES; 433 | VERSIONING_SYSTEM = "apple-generic"; 434 | VERSION_INFO_PREFIX = ""; 435 | }; 436 | name = Release; 437 | }; 438 | D5DC59A01C201BE1003BD79B /* Debug */ = { 439 | isa = XCBuildConfiguration; 440 | buildSettings = { 441 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 442 | DEFINES_MODULE = YES; 443 | DEVELOPMENT_TEAM = ADTR2923N7; 444 | DYLIB_COMPATIBILITY_VERSION = 1; 445 | DYLIB_CURRENT_VERSION = 1; 446 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 447 | INFOPLIST_FILE = "$(SRCROOT)/SupportFiles/Info.plist"; 448 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 449 | IPHONEOS_DEPLOYMENT_TARGET = 8.2; 450 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 451 | PRODUCT_BUNDLE_IDENTIFIER = no.hyper.ImagePicker; 452 | PRODUCT_NAME = ImagePicker; 453 | SKIP_INSTALL = YES; 454 | SWIFT_VERSION = 5.0; 455 | }; 456 | name = Debug; 457 | }; 458 | D5DC59A11C201BE1003BD79B /* Release */ = { 459 | isa = XCBuildConfiguration; 460 | buildSettings = { 461 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 462 | DEFINES_MODULE = YES; 463 | DEVELOPMENT_TEAM = ADTR2923N7; 464 | DYLIB_COMPATIBILITY_VERSION = 1; 465 | DYLIB_CURRENT_VERSION = 1; 466 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 467 | INFOPLIST_FILE = "$(SRCROOT)/SupportFiles/Info.plist"; 468 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 469 | IPHONEOS_DEPLOYMENT_TARGET = 8.2; 470 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 471 | PRODUCT_BUNDLE_IDENTIFIER = no.hyper.ImagePicker; 472 | PRODUCT_NAME = ImagePicker; 473 | SKIP_INSTALL = YES; 474 | SWIFT_VERSION = 5.0; 475 | }; 476 | name = Release; 477 | }; 478 | /* End XCBuildConfiguration section */ 479 | 480 | /* Begin XCConfigurationList section */ 481 | D5DC59851C201BE1003BD79B /* Build configuration list for PBXProject "ImagePicker" */ = { 482 | isa = XCConfigurationList; 483 | buildConfigurations = ( 484 | D5DC599D1C201BE1003BD79B /* Debug */, 485 | D5DC599E1C201BE1003BD79B /* Release */, 486 | ); 487 | defaultConfigurationIsVisible = 0; 488 | defaultConfigurationName = Release; 489 | }; 490 | D5DC599F1C201BE1003BD79B /* Build configuration list for PBXNativeTarget "ImagePicker-iOS" */ = { 491 | isa = XCConfigurationList; 492 | buildConfigurations = ( 493 | D5DC59A01C201BE1003BD79B /* Debug */, 494 | D5DC59A11C201BE1003BD79B /* Release */, 495 | ); 496 | defaultConfigurationIsVisible = 0; 497 | defaultConfigurationName = Release; 498 | }; 499 | /* End XCConfigurationList section */ 500 | }; 501 | rootObject = D5DC59821C201BE1003BD79B /* Project object */; 502 | } 503 | -------------------------------------------------------------------------------- /ImagePicker.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ImagePicker.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ImagePicker.xcodeproj/project.xcworkspace/xcshareddata/ImagePicker.xcscmblueprint: -------------------------------------------------------------------------------- 1 | { 2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "B763FC9623928D804BC7C9A73D90792E4628A1CE", 3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { 4 | 5 | }, 6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { 7 | "DD6144A9FB4D66DBB8AF354930B70BC27549910A" : 9223372036854775807, 8 | "B763FC9623928D804BC7C9A73D90792E4628A1CE" : 9223372036854775807 9 | }, 10 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "B77C8114-D371-4E10-8033-32ED4AA00143", 11 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { 12 | "DD6144A9FB4D66DBB8AF354930B70BC27549910A" : "", 13 | "B763FC9623928D804BC7C9A73D90792E4628A1CE" : "ImagePicker\/" 14 | }, 15 | "DVTSourceControlWorkspaceBlueprintNameKey" : "ImagePicker", 16 | "DVTSourceControlWorkspaceBlueprintVersion" : 204, 17 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "ImagePicker.xcodeproj", 18 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ 19 | { 20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/ksinghal\/ImagePicker.git", 21 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "B763FC9623928D804BC7C9A73D90792E4628A1CE" 23 | }, 24 | { 25 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/spotlight-parking\/attendant-ios.git", 26 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 27 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "DD6144A9FB4D66DBB8AF354930B70BC27549910A" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /ImagePicker.xcodeproj/xcshareddata/xcschemes/ImagePicker-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 79 | 85 | 86 | 87 | 88 | 89 | 90 | 96 | 97 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /Images/AUTO@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/ImagePicker/d8a8f840380b9a28230c8fcad277adb458706d0a/Images/AUTO@3x.png -------------------------------------------------------------------------------- /Images/OFF@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/ImagePicker/d8a8f840380b9a28230c8fcad277adb458706d0a/Images/OFF@3x.png -------------------------------------------------------------------------------- /Images/ON@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/ImagePicker/d8a8f840380b9a28230c8fcad277adb458706d0a/Images/ON@3x.png -------------------------------------------------------------------------------- /Images/cameraIcon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/ImagePicker/d8a8f840380b9a28230c8fcad277adb458706d0a/Images/cameraIcon@3x.png -------------------------------------------------------------------------------- /Images/focusIcon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/ImagePicker/d8a8f840380b9a28230c8fcad277adb458706d0a/Images/focusIcon@3x.png -------------------------------------------------------------------------------- /Images/selectedImageGallery@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/ImagePicker/d8a8f840380b9a28230c8fcad277adb458706d0a/Images/selectedImageGallery@3x.png -------------------------------------------------------------------------------- /Images/video@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/ImagePicker/d8a8f840380b9a28230c8fcad277adb458706d0a/Images/video@3x.png -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Licensed under the **MIT** license 2 | 3 | > Copyright (c) 2015 Hyper Interaktiv AS 4 | > 5 | > Permission is hereby granted, free of charge, to any person obtaining 6 | > a copy of this software and associated documentation files (the 7 | > "Software"), to deal in the Software without restriction, including 8 | > without limitation the rights to use, copy, modify, merge, publish, 9 | > distribute, sublicense, and/or sell copies of the Software, and to 10 | > permit persons to whom the Software is furnished to do so, subject to 11 | > the following conditions: 12 | > 13 | > The above copyright notice and this permission notice shall be 14 | > included in all copies or substantial portions of the Software. 15 | > 16 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | > IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | > CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | > TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | > SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "ImagePicker", 7 | platforms: [ 8 | .iOS(.v9) 9 | ], 10 | products: [ 11 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 12 | .library( 13 | name: "ImagePicker", 14 | targets: ["ImagePicker"]), 15 | ], 16 | dependencies: [ 17 | // Dependencies declare other packages that this package depends on. 18 | // .package(url: /* package url */, from: "1.0.0"), 19 | ], 20 | targets: [ 21 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 22 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 23 | .target( 24 | name: "ImagePicker", 25 | dependencies: [], 26 | path: "Source") 27 | ], 28 | swiftLanguageVersions: [.v5] 29 | ) 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![ImagePicker](https://github.com/hyperoslo/ImagePicker/blob/master/Resources/ImagePickerPresentation.png) 2 | 3 | [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 4 | [![License](https://img.shields.io/cocoapods/l/ImagePicker.svg?style=flat)](http://cocoadocs.org/docsets/ImagePicker) 5 | [![Platform](https://img.shields.io/cocoapods/p/ImagePicker.svg?style=flat)](http://cocoadocs.org/docsets/ImagePicker) 6 | 7 | ## Description 8 | 9 | ImagePicker Icon 10 | 11 | **ImagePicker** is an all-in-one camera solution for your iOS app. It lets your users select images from the library and take pictures at the same time. As a developer you get notified of all the user interactions and get the beautiful UI for free, out of the box, it's just that simple. 12 | 13 | **ImagePicker** has been optimized to give a great user experience, it passes around referenced images instead of the image itself which makes it less memory consuming. This is what makes it smooth as butter. 14 | 15 | ## Usage 16 | 17 | **ImagePicker** works as a normal controller, just instantiate it and present it. 18 | 19 | ```swift 20 | let imagePickerController = ImagePickerController() 21 | imagePickerController.delegate = self 22 | present(imagePickerController, animated: true, completion: nil) 23 | ``` 24 | 25 | **ImagePicker** has three delegate methods that will inform you what the users are up to: 26 | 27 | ```swift 28 | func wrapperDidPress(_ imagePicker: ImagePickerController, images: [UIImage]) 29 | func doneButtonDidPress(_ imagePicker: ImagePickerController, images: [UIImage]) 30 | func cancelButtonDidPress(_ imagePicker: ImagePickerController) 31 | ``` 32 | 33 | **ImagePicker** supports limiting the amount of images that can be selected, it defaults 34 | to zero, which means that the user can select as many images as he/she wants. 35 | 36 | ```swift 37 | let imagePickerController = ImagePickerController() 38 | imagePickerController.imageLimit = 5 39 | ``` 40 | 41 | ### Optional bonus 42 | 43 | ##### Configuration 44 | 45 | You can inject `Configuration` instance to ImagePicker, which allows you to configure text, colors, fonts and camera features 46 | 47 | ```swift 48 | var configuration = Configuration() 49 | configuration.doneButtonTitle = "Finish" 50 | configuration.noImagesTitle = "Sorry! There are no images here!" 51 | configuration.recordLocation = false 52 | 53 | let imagePicker = ImagePickerController(configuration: configuration) 54 | ``` 55 | 56 | ##### Resolve assets 57 | 58 | As said before, **ImagePicker** works with referenced images, that is really powerful because it lets you download the asset and choose the size you want. If you want to change the default implementation, just add a variable in your controller. 59 | 60 | ```swift 61 | public var imageAssets: [UIImage] { 62 | return AssetManager.resolveAssets(imagePicker.stack.assets) 63 | } 64 | ``` 65 | 66 | And when you call any delegate method that returns images, add in the first line: 67 | 68 | ```swift 69 | let images = imageAssets 70 | ``` 71 | 72 | ## FAQ 73 | 74 | ### Limiting selection to 1 item 75 | 76 | ```swift 77 | let config = Configuration() 78 | config.allowMultiplePhotoSelection = false 79 | let imagePicker = ImagePickerController(configuration: config) 80 | imagePicker.delegate = self 81 | ``` 82 | 83 | ## Installation 84 | 85 | **ImagePicker** is available through [CocoaPods](http://cocoapods.org). To install 86 | it, simply add the following line to your Podfile: 87 | 88 | ```ruby 89 | pod 'ImagePicker' 90 | ``` 91 | 92 | **ImagePicker** is also available through [Carthage](https://github.com/Carthage/Carthage). 93 | To install just write into your Cartfile: 94 | 95 | ```ruby 96 | github "hyperoslo/ImagePicker" 97 | ``` 98 | 99 | ## Author 100 | 101 | [Hyper](http://hyper.no) made this with ❤️ 102 | 103 | ## Contribute 104 | 105 | We would love you to contribute to **ImagePicker**, check the [CONTRIBUTING](https://github.com/hyperoslo/ImagePicker/blob/master/CONTRIBUTING.md) file for more info. 106 | 107 | ## License 108 | 109 | **ImagePicker** is available under the MIT license. See the [LICENSE](https://github.com/hyperoslo/ImagePicker/blob/master/LICENSE.md) file for more info. 110 | -------------------------------------------------------------------------------- /Resources/ImagePickerIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/ImagePicker/d8a8f840380b9a28230c8fcad277adb458706d0a/Resources/ImagePickerIcon.png -------------------------------------------------------------------------------- /Resources/ImagePickerPresentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperoslo/ImagePicker/d8a8f840380b9a28230c8fcad277adb458706d0a/Resources/ImagePickerPresentation.png -------------------------------------------------------------------------------- /Source/AssetManager.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import UIKit 3 | import Photos 4 | 5 | extension Bundle { 6 | static func myResourceBundle() -> Bundle? { 7 | let bundles = Bundle.allBundles 8 | let bundlePaths = bundles.compactMap { $0.resourceURL?.appendingPathComponent("ImagePicker", isDirectory: false).appendingPathExtension("bundle") } 9 | 10 | return bundlePaths.compactMap({ Bundle(url: $0) }).first 11 | } 12 | } 13 | 14 | open class AssetManager { 15 | 16 | public static func getImage(_ name: String) -> UIImage { 17 | let traitCollection = UITraitCollection(displayScale: 3) 18 | var bundle = Bundle.myResourceBundle() 19 | 20 | if let resource = bundle?.resourcePath, let resourceBundle = Bundle(path: resource + "/ImagePicker.bundle") { 21 | bundle = resourceBundle 22 | } 23 | 24 | return UIImage(named: name, in: bundle, compatibleWith: traitCollection) ?? UIImage() 25 | } 26 | 27 | public static func fetch(withConfiguration configuration: ImagePickerConfiguration, _ completion: @escaping (_ assets: [PHAsset]) -> Void) { 28 | guard PHPhotoLibrary.authorizationStatus() == .authorized else { return } 29 | 30 | DispatchQueue.global(qos: .background).async { 31 | let fetchResult = configuration.allowVideoSelection 32 | ? PHAsset.fetchAssets(with: PHFetchOptions()) 33 | : PHAsset.fetchAssets(with: .image, options: PHFetchOptions()) 34 | 35 | if fetchResult.count > 0 { 36 | var assets = [PHAsset]() 37 | fetchResult.enumerateObjects({ object, _, _ in 38 | assets.insert(object, at: 0) 39 | }) 40 | 41 | DispatchQueue.main.async { 42 | completion(assets) 43 | } 44 | } 45 | } 46 | } 47 | 48 | public static func resolveAsset(_ asset: PHAsset, size: CGSize = CGSize(width: 720, height: 1280), shouldPreferLowRes: Bool = false, completion: @escaping (_ image: UIImage?) -> Void) { 49 | let imageManager = PHImageManager.default() 50 | let requestOptions = PHImageRequestOptions() 51 | requestOptions.deliveryMode = shouldPreferLowRes ? .fastFormat : .highQualityFormat 52 | requestOptions.isNetworkAccessAllowed = true 53 | 54 | imageManager.requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: requestOptions) { image, info in 55 | if let info = info, info["PHImageFileUTIKey"] == nil { 56 | DispatchQueue.main.async(execute: { 57 | completion(image) 58 | }) 59 | } 60 | } 61 | } 62 | 63 | public static func resolveAssets(_ assets: [PHAsset], size: CGSize = CGSize(width: 720, height: 1280)) -> [UIImage] { 64 | let imageManager = PHImageManager.default() 65 | let requestOptions = PHImageRequestOptions() 66 | requestOptions.isSynchronous = true 67 | 68 | var images = [UIImage]() 69 | for asset in assets { 70 | imageManager.requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: requestOptions) { image, _ in 71 | if let image = image { 72 | images.append(image) 73 | } 74 | } 75 | } 76 | return images 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Source/BottomView/BottomContainerView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | protocol BottomContainerViewDelegate: class { 4 | 5 | func pickerButtonDidPress() 6 | func doneButtonDidPress() 7 | func cancelButtonDidPress() 8 | func imageStackViewDidPress() 9 | } 10 | 11 | open class BottomContainerView: UIView { 12 | 13 | struct Dimensions { 14 | static let height: CGFloat = 101 15 | } 16 | 17 | var configuration = ImagePickerConfiguration() 18 | 19 | lazy var pickerButton: ButtonPicker = { [unowned self] in 20 | let pickerButton = ButtonPicker(configuration: self.configuration) 21 | pickerButton.setTitleColor(UIColor.white, for: UIControl.State()) 22 | pickerButton.delegate = self 23 | pickerButton.numberLabel.isHidden = !self.configuration.showsImageCountLabel 24 | 25 | return pickerButton 26 | }() 27 | 28 | lazy var borderPickerButton: UIView = { 29 | let view = UIView() 30 | view.backgroundColor = UIColor.clear 31 | view.layer.borderColor = UIColor.white.cgColor 32 | view.layer.borderWidth = ButtonPicker.Dimensions.borderWidth 33 | view.layer.cornerRadius = ButtonPicker.Dimensions.buttonBorderSize / 2 34 | 35 | return view 36 | }() 37 | 38 | open lazy var doneButton: UIButton = { [unowned self] in 39 | let button = UIButton() 40 | button.setTitle(self.configuration.cancelButtonTitle, for: UIControl.State()) 41 | button.titleLabel?.font = self.configuration.doneButton 42 | button.addTarget(self, action: #selector(doneButtonDidPress(_:)), for: .touchUpInside) 43 | 44 | return button 45 | }() 46 | 47 | lazy var stackView = ImageStackView(frame: CGRect(x: 0, y: 0, width: 80, height: 80)) 48 | 49 | lazy var topSeparator: UIView = { [unowned self] in 50 | let view = UIView() 51 | view.backgroundColor = self.configuration.backgroundColor 52 | 53 | return view 54 | }() 55 | 56 | lazy var tapGestureRecognizer: UITapGestureRecognizer = { [unowned self] in 57 | let gesture = UITapGestureRecognizer() 58 | gesture.addTarget(self, action: #selector(handleTapGestureRecognizer(_:))) 59 | 60 | return gesture 61 | }() 62 | 63 | weak var delegate: BottomContainerViewDelegate? 64 | var pastCount = 0 65 | 66 | // MARK: Initializers 67 | 68 | public init(configuration: ImagePickerConfiguration? = nil) { 69 | if let configuration = configuration { 70 | self.configuration = configuration 71 | } 72 | super.init(frame: .zero) 73 | configure() 74 | } 75 | 76 | public required init?(coder aDecoder: NSCoder) { 77 | fatalError("init(coder:) has not been implemented") 78 | } 79 | 80 | func configure() { 81 | [borderPickerButton, pickerButton, doneButton, stackView, topSeparator].forEach { 82 | addSubview($0) 83 | $0.translatesAutoresizingMaskIntoConstraints = false 84 | } 85 | 86 | backgroundColor = configuration.backgroundColor 87 | stackView.accessibilityLabel = "Image stack" 88 | stackView.addGestureRecognizer(tapGestureRecognizer) 89 | 90 | setupConstraints() 91 | if configuration.galleryOnly { 92 | borderPickerButton.isHidden = true 93 | pickerButton.isHidden = true 94 | } 95 | if !configuration.allowMultiplePhotoSelection { 96 | stackView.isHidden = true 97 | } 98 | } 99 | 100 | // MARK: - Action methods 101 | 102 | @objc func doneButtonDidPress(_ button: UIButton) { 103 | if button.currentTitle == configuration.cancelButtonTitle { 104 | delegate?.cancelButtonDidPress() 105 | } else { 106 | delegate?.doneButtonDidPress() 107 | } 108 | } 109 | 110 | @objc func handleTapGestureRecognizer(_ recognizer: UITapGestureRecognizer) { 111 | delegate?.imageStackViewDidPress() 112 | } 113 | 114 | fileprivate func animateImageView(_ imageView: UIImageView) { 115 | imageView.transform = CGAffineTransform(scaleX: 0, y: 0) 116 | 117 | UIView.animate(withDuration: 0.3, animations: { 118 | imageView.transform = CGAffineTransform(scaleX: 1.05, y: 1.05) 119 | }, completion: { _ in 120 | UIView.animate(withDuration: 0.2, animations: { 121 | imageView.transform = CGAffineTransform.identity 122 | }) 123 | }) 124 | } 125 | } 126 | 127 | // MARK: - ButtonPickerDelegate methods 128 | 129 | extension BottomContainerView: ButtonPickerDelegate { 130 | 131 | func buttonDidPress() { 132 | delegate?.pickerButtonDidPress() 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Source/BottomView/ButtonPicker.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | protocol ButtonPickerDelegate: class { 4 | 5 | func buttonDidPress() 6 | } 7 | 8 | class ButtonPicker: UIButton { 9 | 10 | struct Dimensions { 11 | static let borderWidth: CGFloat = 2 12 | static let buttonSize: CGFloat = 58 13 | static let buttonBorderSize: CGFloat = 68 14 | } 15 | 16 | var imagePickerConfiguration = ImagePickerConfiguration() 17 | 18 | lazy var numberLabel: UILabel = { [unowned self] in 19 | let label = UILabel() 20 | label.translatesAutoresizingMaskIntoConstraints = false 21 | label.font = self.imagePickerConfiguration.numberLabelFont 22 | 23 | return label 24 | }() 25 | 26 | weak var delegate: ButtonPickerDelegate? 27 | 28 | // MARK: - Initializers 29 | 30 | public init(configuration: ImagePickerConfiguration? = nil) { 31 | if let configuration = configuration { 32 | self.imagePickerConfiguration = configuration 33 | } 34 | super.init(frame: .zero) 35 | configure() 36 | } 37 | 38 | override init(frame: CGRect) { 39 | super.init(frame: frame) 40 | configure() 41 | } 42 | 43 | func configure() { 44 | addSubview(numberLabel) 45 | 46 | subscribe() 47 | setupButton() 48 | setupConstraints() 49 | } 50 | 51 | deinit { 52 | NotificationCenter.default.removeObserver(self) 53 | } 54 | 55 | func subscribe() { 56 | NotificationCenter.default.addObserver(self, 57 | selector: #selector(recalculatePhotosCount(_:)), 58 | name: NSNotification.Name(rawValue: ImageStack.Notifications.imageDidPush), 59 | object: nil) 60 | 61 | NotificationCenter.default.addObserver(self, 62 | selector: #selector(recalculatePhotosCount(_:)), 63 | name: NSNotification.Name(rawValue: ImageStack.Notifications.imageDidDrop), 64 | object: nil) 65 | 66 | NotificationCenter.default.addObserver(self, 67 | selector: #selector(recalculatePhotosCount(_:)), 68 | name: NSNotification.Name(rawValue: ImageStack.Notifications.stackDidReload), 69 | object: nil) 70 | } 71 | 72 | required init?(coder aDecoder: NSCoder) { 73 | fatalError("init(coder:) has not been implemented") 74 | } 75 | 76 | // MARK: - Configuration 77 | 78 | func setupButton() { 79 | backgroundColor = UIColor.white 80 | layer.cornerRadius = Dimensions.buttonSize / 2 81 | accessibilityLabel = "Take photo" 82 | addTarget(self, action: #selector(pickerButtonDidPress(_:)), for: .touchUpInside) 83 | addTarget(self, action: #selector(pickerButtonDidHighlight(_:)), for: .touchDown) 84 | } 85 | 86 | // MARK: - Actions 87 | 88 | @objc func recalculatePhotosCount(_ notification: Notification) { 89 | guard let sender = notification.object as? ImageStack else { return } 90 | numberLabel.text = sender.assets.isEmpty ? "" : String(sender.assets.count) 91 | } 92 | 93 | @objc func pickerButtonDidPress(_ button: UIButton) { 94 | backgroundColor = UIColor.white 95 | numberLabel.textColor = UIColor.black 96 | numberLabel.sizeToFit() 97 | delegate?.buttonDidPress() 98 | } 99 | 100 | @objc func pickerButtonDidHighlight(_ button: UIButton) { 101 | numberLabel.textColor = UIColor.white 102 | backgroundColor = UIColor(red: 0.3, green: 0.3, blue: 0.3, alpha: 1) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Source/BottomView/ImageStack.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Photos 3 | 4 | open class ImageStack { 5 | 6 | public struct Notifications { 7 | public static let imageDidPush = "imageDidPush" 8 | public static let imageDidDrop = "imageDidDrop" 9 | public static let stackDidReload = "stackDidReload" 10 | } 11 | 12 | open var assets = [PHAsset]() 13 | fileprivate let imageKey = "image" 14 | 15 | open func pushAsset(_ asset: PHAsset) { 16 | assets.append(asset) 17 | NotificationCenter.default.post(name: Notification.Name(rawValue: Notifications.imageDidPush), object: self, userInfo: [imageKey: asset]) 18 | } 19 | 20 | open func dropAsset(_ asset: PHAsset) { 21 | assets = assets.filter {$0 != asset} 22 | NotificationCenter.default.post(name: Notification.Name(rawValue: Notifications.imageDidDrop), object: self, userInfo: [imageKey: asset]) 23 | } 24 | 25 | open func resetAssets(_ assetsArray: [PHAsset]) { 26 | assets = assetsArray 27 | NotificationCenter.default.post(name: Notification.Name(rawValue: Notifications.stackDidReload), object: self, userInfo: nil) 28 | } 29 | 30 | open func containsAsset(_ asset: PHAsset) -> Bool { 31 | return assets.contains(asset) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Source/BottomView/StackView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Photos 3 | 4 | protocol ImageStackViewDelegate: class { 5 | func imageStackViewDidPress() 6 | } 7 | 8 | class ImageStackView: UIView { 9 | 10 | struct Dimensions { 11 | static let imageSize: CGFloat = 58 12 | } 13 | 14 | weak var delegate: ImageStackViewDelegate? 15 | 16 | lazy var activityView: UIActivityIndicatorView = { 17 | let view = UIActivityIndicatorView() 18 | view.alpha = 0.0 19 | 20 | return view 21 | }() 22 | 23 | var views: [UIImageView] = { 24 | var array = [UIImageView]() 25 | for _ in 0...3 { 26 | let view = UIImageView() 27 | view.layer.cornerRadius = 3 28 | view.layer.borderColor = UIColor.white.cgColor 29 | view.layer.borderWidth = 1 30 | view.contentMode = .scaleAspectFill 31 | view.clipsToBounds = true 32 | view.alpha = 0 33 | array.append(view) 34 | } 35 | return array 36 | }() 37 | 38 | // MARK: - Initializers 39 | 40 | override init(frame: CGRect) { 41 | super.init(frame: frame) 42 | 43 | subscribe() 44 | 45 | views.forEach { addSubview($0) } 46 | addSubview(activityView) 47 | views.first?.alpha = 1 48 | } 49 | 50 | required init?(coder aDecoder: NSCoder) { 51 | fatalError("init(coder:) has not been implemented") 52 | } 53 | 54 | deinit { 55 | NotificationCenter.default.removeObserver(self) 56 | } 57 | 58 | // MARK: - Helpers 59 | 60 | func subscribe() { 61 | NotificationCenter.default.addObserver(self, 62 | selector: #selector(imageDidPush(_:)), 63 | name: NSNotification.Name(rawValue: ImageStack.Notifications.imageDidPush), 64 | object: nil) 65 | 66 | NotificationCenter.default.addObserver(self, 67 | selector: #selector(imageStackDidChangeContent(_:)), 68 | name: NSNotification.Name(rawValue: ImageStack.Notifications.imageDidDrop), 69 | object: nil) 70 | 71 | NotificationCenter.default.addObserver(self, 72 | selector: #selector(imageStackDidChangeContent(_:)), 73 | name: NSNotification.Name(rawValue: ImageStack.Notifications.stackDidReload), 74 | object: nil) 75 | } 76 | 77 | override func layoutSubviews() { 78 | let step: CGFloat = -3.0 79 | let scale: CGFloat = 0.8 80 | let viewSize = CGSize(width: frame.width * scale, 81 | height: frame.height * scale) 82 | 83 | let offset = -step * CGFloat(views.count) 84 | var origin = CGPoint(x: offset, y: offset) 85 | 86 | for view in views { 87 | origin.x += step 88 | origin.y += step 89 | view.frame = CGRect(origin: origin, size: viewSize) 90 | } 91 | } 92 | 93 | func startLoader() { 94 | if let firstVisibleView = views.filter({ $0.alpha == 1.0 }).last { 95 | activityView.frame.origin.x = firstVisibleView.center.x 96 | activityView.frame.origin.y = firstVisibleView.center.y 97 | } 98 | 99 | activityView.startAnimating() 100 | UIView.animate(withDuration: 0.3, animations: { 101 | self.activityView.alpha = 1.0 102 | }) 103 | } 104 | } 105 | 106 | extension ImageStackView { 107 | 108 | @objc func imageDidPush(_ notification: Notification) { 109 | let emptyView = views.filter { $0.image == nil }.first 110 | 111 | if let emptyView = emptyView { 112 | animateImageView(emptyView) 113 | } 114 | 115 | if let sender = notification.object as? ImageStack { 116 | renderViews(sender.assets) 117 | activityView.stopAnimating() 118 | } 119 | } 120 | 121 | @objc func imageStackDidChangeContent(_ notification: Notification) { 122 | if let sender = notification.object as? ImageStack { 123 | renderViews(sender.assets) 124 | activityView.stopAnimating() 125 | } 126 | } 127 | 128 | @objc func renderViews(_ assets: [PHAsset]) { 129 | if let firstView = views.first, assets.isEmpty { 130 | views.forEach { 131 | $0.image = nil 132 | $0.alpha = 0 133 | } 134 | 135 | firstView.alpha = 1 136 | return 137 | } 138 | 139 | let photos = Array(assets.suffix(4)) 140 | 141 | for (index, view) in views.enumerated() { 142 | if index <= photos.count - 1 { 143 | AssetManager.resolveAsset(photos[index], size: CGSize(width: Dimensions.imageSize, height: Dimensions.imageSize)) { image in 144 | view.image = image 145 | } 146 | view.alpha = 1 147 | } else { 148 | view.image = nil 149 | view.alpha = 0 150 | } 151 | 152 | if index == photos.count { 153 | UIView.animate(withDuration: 0.3, animations: { 154 | self.activityView.frame.origin = CGPoint(x: view.center.x + 3, y: view.center.x + 3) 155 | }) 156 | } 157 | } 158 | } 159 | 160 | fileprivate func animateImageView(_ imageView: UIImageView) { 161 | imageView.transform = CGAffineTransform(scaleX: 0, y: 0) 162 | 163 | UIView.animate(withDuration: 0.3, animations: { 164 | imageView.transform = CGAffineTransform(scaleX: 1.05, y: 1.05) 165 | }, completion: { _ in 166 | UIView.animate(withDuration: 0.2, animations: { () -> Void in 167 | self.activityView.alpha = 0.0 168 | imageView.transform = CGAffineTransform.identity 169 | }, completion: { _ in 170 | self.activityView.stopAnimating() 171 | }) 172 | }) 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /Source/CameraView/CameraMan.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import AVFoundation 3 | import PhotosUI 4 | 5 | protocol CameraManDelegate: class { 6 | func cameraManNotAvailable(_ cameraMan: CameraMan) 7 | func cameraManDidStart(_ cameraMan: CameraMan) 8 | func cameraMan(_ cameraMan: CameraMan, didChangeInput input: AVCaptureDeviceInput) 9 | } 10 | 11 | class CameraMan { 12 | weak var delegate: CameraManDelegate? 13 | 14 | let session = AVCaptureSession() 15 | let queue = DispatchQueue(label: "no.hyper.ImagePicker.Camera.SessionQueue") 16 | 17 | var backCamera: AVCaptureDeviceInput? 18 | var frontCamera: AVCaptureDeviceInput? 19 | var stillImageOutput: AVCaptureStillImageOutput? 20 | var startOnFrontCamera: Bool = false 21 | 22 | deinit { 23 | stop() 24 | } 25 | 26 | // MARK: - Setup 27 | 28 | func setup(_ startOnFrontCamera: Bool = false) { 29 | self.startOnFrontCamera = startOnFrontCamera 30 | checkPermission() 31 | } 32 | 33 | func setupDevices() { 34 | // Input 35 | AVCaptureDevice 36 | .devices() 37 | .filter { 38 | return $0.hasMediaType(AVMediaType.video) 39 | }.forEach { 40 | switch $0.position { 41 | case .front: 42 | self.frontCamera = try? AVCaptureDeviceInput(device: $0) 43 | case .back: 44 | self.backCamera = try? AVCaptureDeviceInput(device: $0) 45 | default: 46 | break 47 | } 48 | } 49 | 50 | // Output 51 | stillImageOutput = AVCaptureStillImageOutput() 52 | stillImageOutput?.outputSettings = [AVVideoCodecKey: AVVideoCodecJPEG] 53 | } 54 | 55 | func addInput(_ input: AVCaptureDeviceInput) { 56 | configurePreset(input) 57 | 58 | if session.canAddInput(input) { 59 | session.addInput(input) 60 | 61 | DispatchQueue.main.async { 62 | self.delegate?.cameraMan(self, didChangeInput: input) 63 | } 64 | } 65 | } 66 | 67 | // MARK: - Permission 68 | 69 | func checkPermission() { 70 | let status = AVCaptureDevice.authorizationStatus(for: AVMediaType.video) 71 | 72 | switch status { 73 | case .authorized: 74 | start() 75 | case .notDetermined: 76 | requestPermission() 77 | default: 78 | delegate?.cameraManNotAvailable(self) 79 | } 80 | } 81 | 82 | func requestPermission() { 83 | AVCaptureDevice.requestAccess(for: AVMediaType.video) { granted in 84 | DispatchQueue.main.async { 85 | if granted { 86 | self.start() 87 | } else { 88 | self.delegate?.cameraManNotAvailable(self) 89 | } 90 | } 91 | } 92 | } 93 | 94 | // MARK: - Session 95 | 96 | var currentInput: AVCaptureDeviceInput? { 97 | return session.inputs.first as? AVCaptureDeviceInput 98 | } 99 | 100 | fileprivate func start() { 101 | // Devices 102 | setupDevices() 103 | 104 | guard let input = (self.startOnFrontCamera) ? frontCamera ?? backCamera : backCamera, let output = stillImageOutput else { return } 105 | 106 | addInput(input) 107 | 108 | if session.canAddOutput(output) { 109 | session.addOutput(output) 110 | } 111 | 112 | queue.async { 113 | self.session.startRunning() 114 | 115 | DispatchQueue.main.async { 116 | self.delegate?.cameraManDidStart(self) 117 | } 118 | } 119 | } 120 | 121 | func stop() { 122 | self.session.stopRunning() 123 | } 124 | 125 | func switchCamera(_ completion: (() -> Void)? = nil) { 126 | guard let currentInput = currentInput 127 | else { 128 | completion?() 129 | return 130 | } 131 | 132 | queue.async { 133 | guard let input = (currentInput == self.backCamera) ? self.frontCamera : self.backCamera 134 | else { 135 | DispatchQueue.main.async { 136 | completion?() 137 | } 138 | return 139 | } 140 | 141 | self.configure { 142 | self.session.removeInput(currentInput) 143 | self.addInput(input) 144 | } 145 | 146 | DispatchQueue.main.async { 147 | completion?() 148 | } 149 | } 150 | } 151 | 152 | func takePhoto(_ previewLayer: AVCaptureVideoPreviewLayer, location: CLLocation?, completion: (() -> Void)? = nil) { 153 | guard let connection = stillImageOutput?.connection(with: AVMediaType.video) else { return } 154 | 155 | connection.videoOrientation = Helper.videoOrientation() 156 | 157 | queue.async { 158 | self.stillImageOutput?.captureStillImageAsynchronously(from: connection) { buffer, error in 159 | guard let buffer = buffer, error == nil && CMSampleBufferIsValid(buffer), 160 | let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(buffer), 161 | let image = UIImage(data: imageData) 162 | else { 163 | DispatchQueue.main.async { 164 | completion?() 165 | } 166 | return 167 | } 168 | 169 | self.savePhoto(image, location: location, completion: completion) 170 | } 171 | } 172 | } 173 | 174 | func savePhoto(_ image: UIImage, location: CLLocation?, completion: (() -> Void)? = nil) { 175 | PHPhotoLibrary.shared().performChanges({ 176 | let request = PHAssetChangeRequest.creationRequestForAsset(from: image) 177 | request.creationDate = Date() 178 | request.location = location 179 | }, completionHandler: { (_, _) in 180 | DispatchQueue.main.async { 181 | completion?() 182 | } 183 | }) 184 | } 185 | 186 | func flash(_ mode: AVCaptureDevice.FlashMode) { 187 | guard let device = currentInput?.device, device.isFlashModeSupported(mode) else { return } 188 | 189 | queue.async { 190 | self.lock { 191 | device.flashMode = mode 192 | } 193 | } 194 | } 195 | 196 | func focus(_ point: CGPoint) { 197 | guard let device = currentInput?.device, device.isFocusModeSupported(AVCaptureDevice.FocusMode.locked) else { return } 198 | 199 | queue.async { 200 | self.lock { 201 | device.focusPointOfInterest = point 202 | } 203 | } 204 | } 205 | 206 | func zoom(_ zoomFactor: CGFloat) { 207 | guard let device = currentInput?.device, device.position == .back else { return } 208 | 209 | queue.async { 210 | self.lock { 211 | device.videoZoomFactor = zoomFactor 212 | } 213 | } 214 | } 215 | 216 | // MARK: - Lock 217 | 218 | func lock(_ block: () -> Void) { 219 | if let device = currentInput?.device, (try? device.lockForConfiguration()) != nil { 220 | block() 221 | device.unlockForConfiguration() 222 | } 223 | } 224 | 225 | // MARK: - Configure 226 | func configure(_ block: () -> Void) { 227 | session.beginConfiguration() 228 | block() 229 | session.commitConfiguration() 230 | } 231 | 232 | // MARK: - Preset 233 | 234 | func configurePreset(_ input: AVCaptureDeviceInput) { 235 | for asset in preferredPresets() { 236 | if input.device.supportsSessionPreset(AVCaptureSession.Preset(rawValue: asset)) && self.session.canSetSessionPreset(AVCaptureSession.Preset(rawValue: asset)) { 237 | self.session.sessionPreset = AVCaptureSession.Preset(rawValue: asset) 238 | return 239 | } 240 | } 241 | } 242 | 243 | func preferredPresets() -> [String] { 244 | return [ 245 | AVCaptureSession.Preset.high.rawValue, 246 | AVCaptureSession.Preset.high.rawValue, 247 | AVCaptureSession.Preset.low.rawValue 248 | ] 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /Source/CameraView/CameraView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import AVFoundation 3 | import PhotosUI 4 | 5 | protocol CameraViewDelegate: class { 6 | 7 | func setFlashButtonHidden(_ hidden: Bool) 8 | func imageToLibrary() 9 | func cameraNotAvailable() 10 | } 11 | 12 | class CameraView: UIViewController, CLLocationManagerDelegate, CameraManDelegate { 13 | 14 | var configuration = ImagePickerConfiguration() 15 | 16 | lazy var blurView: UIVisualEffectView = { [unowned self] in 17 | let effect = UIBlurEffect(style: .dark) 18 | let blurView = UIVisualEffectView(effect: effect) 19 | 20 | return blurView 21 | }() 22 | 23 | lazy var focusImageView: UIImageView = { [unowned self] in 24 | let imageView = UIImageView() 25 | imageView.image = AssetManager.getImage("focusIcon") 26 | imageView.backgroundColor = UIColor.clear 27 | imageView.frame = CGRect(x: 0, y: 0, width: 110, height: 110) 28 | imageView.alpha = 0 29 | 30 | return imageView 31 | }() 32 | 33 | lazy var capturedImageView: UIView = { [unowned self] in 34 | let view = UIView() 35 | view.backgroundColor = UIColor.black 36 | view.alpha = 0 37 | 38 | return view 39 | }() 40 | 41 | lazy var containerView: UIView = { 42 | let view = UIView() 43 | view.alpha = 0 44 | 45 | return view 46 | }() 47 | 48 | lazy var noCameraLabel: UILabel = { [unowned self] in 49 | let label = UILabel() 50 | label.font = self.configuration.noCameraFont 51 | label.textColor = self.configuration.noCameraColor 52 | label.text = self.configuration.noCameraTitle 53 | label.sizeToFit() 54 | 55 | return label 56 | }() 57 | 58 | lazy var noCameraButton: UIButton = { [unowned self] in 59 | let button = UIButton(type: .system) 60 | let title = NSAttributedString(string: self.configuration.settingsTitle, 61 | attributes: [ 62 | NSAttributedString.Key.font: self.configuration.settingsFont, 63 | NSAttributedString.Key.foregroundColor: self.configuration.settingsColor 64 | ]) 65 | 66 | button.setAttributedTitle(title, for: UIControl.State()) 67 | button.contentEdgeInsets = UIEdgeInsets(top: 5.0, left: 10.0, bottom: 5.0, right: 10.0) 68 | button.sizeToFit() 69 | button.layer.borderColor = self.configuration.settingsColor.cgColor 70 | button.layer.borderWidth = 1 71 | button.layer.cornerRadius = 4 72 | button.addTarget(self, action: #selector(settingsButtonDidTap), for: .touchUpInside) 73 | 74 | return button 75 | }() 76 | 77 | lazy var tapGestureRecognizer: UITapGestureRecognizer = { [unowned self] in 78 | let gesture = UITapGestureRecognizer() 79 | gesture.addTarget(self, action: #selector(tapGestureRecognizerHandler(_:))) 80 | 81 | return gesture 82 | }() 83 | 84 | lazy var pinchGestureRecognizer: UIPinchGestureRecognizer = { [unowned self] in 85 | let gesture = UIPinchGestureRecognizer() 86 | gesture.addTarget(self, action: #selector(pinchGestureRecognizerHandler(_:))) 87 | 88 | return gesture 89 | }() 90 | 91 | let cameraMan = CameraMan() 92 | 93 | var previewLayer: AVCaptureVideoPreviewLayer? 94 | weak var delegate: CameraViewDelegate? 95 | var animationTimer: Timer? 96 | var locationManager: LocationManager? 97 | var startOnFrontCamera: Bool = false 98 | 99 | private let minimumZoomFactor: CGFloat = 1.0 100 | private let maximumZoomFactor: CGFloat = 3.0 101 | 102 | private var currentZoomFactor: CGFloat = 1.0 103 | private var previousZoomFactor: CGFloat = 1.0 104 | 105 | public init(configuration: ImagePickerConfiguration? = nil) { 106 | if let configuration = configuration { 107 | self.configuration = configuration 108 | } 109 | super.init(nibName: nil, bundle: nil) 110 | } 111 | 112 | required init?(coder aDecoder: NSCoder) { 113 | fatalError("init(coder:) has not been implemented") 114 | } 115 | 116 | override func viewDidLoad() { 117 | super.viewDidLoad() 118 | 119 | if configuration.recordLocation { 120 | locationManager = LocationManager() 121 | } 122 | 123 | view.backgroundColor = configuration.mainColor 124 | 125 | view.addSubview(containerView) 126 | containerView.addSubview(blurView) 127 | 128 | [focusImageView, capturedImageView].forEach { 129 | view.addSubview($0) 130 | } 131 | 132 | view.addGestureRecognizer(tapGestureRecognizer) 133 | 134 | if configuration.allowPinchToZoom { 135 | view.addGestureRecognizer(pinchGestureRecognizer) 136 | } 137 | 138 | cameraMan.delegate = self 139 | cameraMan.setup(self.startOnFrontCamera) 140 | } 141 | 142 | override func viewDidAppear(_ animated: Bool) { 143 | super.viewDidAppear(animated) 144 | 145 | previewLayer?.connection?.videoOrientation = .portrait 146 | locationManager?.startUpdatingLocation() 147 | } 148 | 149 | override func viewDidDisappear(_ animated: Bool) { 150 | super.viewDidDisappear(animated) 151 | locationManager?.stopUpdatingLocation() 152 | } 153 | 154 | func setupPreviewLayer() { 155 | let layer = AVCaptureVideoPreviewLayer(session: cameraMan.session) 156 | 157 | layer.backgroundColor = configuration.mainColor.cgColor 158 | layer.autoreverses = true 159 | layer.videoGravity = AVLayerVideoGravity.resizeAspectFill 160 | 161 | view.layer.insertSublayer(layer, at: 0) 162 | layer.frame = view.layer.frame 163 | view.clipsToBounds = true 164 | 165 | previewLayer = layer 166 | } 167 | 168 | // MARK: - Layout 169 | 170 | override func viewDidLayoutSubviews() { 171 | super.viewDidLayoutSubviews() 172 | 173 | let centerX = view.bounds.width / 2 174 | 175 | noCameraLabel.center = CGPoint(x: centerX, 176 | y: view.bounds.height / 2 - 80) 177 | 178 | noCameraButton.center = CGPoint(x: centerX, 179 | y: noCameraLabel.frame.maxY + 20) 180 | 181 | blurView.frame = view.bounds 182 | containerView.frame = view.bounds 183 | capturedImageView.frame = view.bounds 184 | } 185 | 186 | // MARK: - Actions 187 | 188 | @objc func settingsButtonDidTap() { 189 | DispatchQueue.main.async { 190 | if let settingsURL = URL(string: UIApplication.openSettingsURLString) { 191 | UIApplication.shared.openURL(settingsURL) 192 | } 193 | } 194 | } 195 | 196 | // MARK: - Camera actions 197 | 198 | func rotateCamera() { 199 | UIView.animate(withDuration: 0.3, animations: { 200 | self.containerView.alpha = 1 201 | }, completion: { _ in 202 | self.cameraMan.switchCamera { 203 | UIView.animate(withDuration: 0.7, animations: { 204 | self.containerView.alpha = 0 205 | }) 206 | } 207 | }) 208 | } 209 | 210 | func flashCamera(_ title: String) { 211 | let mapping: [String: AVCaptureDevice.FlashMode] = [ 212 | "ON": .on, 213 | "OFF": .off 214 | ] 215 | 216 | cameraMan.flash(mapping[title] ?? .auto) 217 | } 218 | 219 | func takePicture(_ completion: @escaping () -> Void) { 220 | guard let previewLayer = previewLayer else { return } 221 | 222 | UIView.animate(withDuration: 0.1, animations: { 223 | self.capturedImageView.alpha = 1 224 | }, completion: { _ in 225 | UIView.animate(withDuration: 0.1, animations: { 226 | self.capturedImageView.alpha = 0 227 | }) 228 | }) 229 | 230 | cameraMan.takePhoto(previewLayer, location: locationManager?.latestLocation) { 231 | completion() 232 | self.delegate?.imageToLibrary() 233 | } 234 | } 235 | 236 | // MARK: - Timer methods 237 | 238 | @objc func timerDidFire() { 239 | UIView.animate(withDuration: 0.3, animations: { [unowned self] in 240 | self.focusImageView.alpha = 0 241 | }, completion: { _ in 242 | self.focusImageView.transform = CGAffineTransform.identity 243 | }) 244 | } 245 | 246 | // MARK: - Camera methods 247 | 248 | func focusTo(_ point: CGPoint) { 249 | let convertedPoint = CGPoint(x: point.x / UIScreen.main.bounds.width, 250 | y: point.y / UIScreen.main.bounds.height) 251 | 252 | cameraMan.focus(convertedPoint) 253 | 254 | focusImageView.center = point 255 | UIView.animate(withDuration: 0.5, animations: { 256 | self.focusImageView.alpha = 1 257 | self.focusImageView.transform = CGAffineTransform(scaleX: 0.6, y: 0.6) 258 | }, completion: { _ in 259 | self.animationTimer = Timer.scheduledTimer(timeInterval: 1, target: self, 260 | selector: #selector(CameraView.timerDidFire), userInfo: nil, repeats: false) 261 | }) 262 | } 263 | 264 | func zoomTo(_ zoomFactor: CGFloat) { 265 | guard let device = cameraMan.currentInput?.device else { return } 266 | 267 | let maximumDeviceZoomFactor = device.activeFormat.videoMaxZoomFactor 268 | let newZoomFactor = previousZoomFactor * zoomFactor 269 | currentZoomFactor = min(maximumZoomFactor, max(minimumZoomFactor, min(newZoomFactor, maximumDeviceZoomFactor))) 270 | 271 | cameraMan.zoom(currentZoomFactor) 272 | } 273 | 274 | // MARK: - Tap 275 | 276 | @objc func tapGestureRecognizerHandler(_ gesture: UITapGestureRecognizer) { 277 | let touch = gesture.location(in: view) 278 | 279 | focusImageView.transform = CGAffineTransform.identity 280 | animationTimer?.invalidate() 281 | focusTo(touch) 282 | } 283 | 284 | // MARK: - Pinch 285 | 286 | @objc func pinchGestureRecognizerHandler(_ gesture: UIPinchGestureRecognizer) { 287 | switch gesture.state { 288 | case .began: 289 | fallthrough 290 | case .changed: 291 | zoomTo(gesture.scale) 292 | case .ended: 293 | zoomTo(gesture.scale) 294 | previousZoomFactor = currentZoomFactor 295 | default: break 296 | } 297 | } 298 | 299 | // MARK: - Private helpers 300 | 301 | func showNoCamera(_ show: Bool) { 302 | [noCameraButton, noCameraLabel].forEach { 303 | show ? view.addSubview($0) : $0.removeFromSuperview() 304 | } 305 | } 306 | 307 | // CameraManDelegate 308 | func cameraManNotAvailable(_ cameraMan: CameraMan) { 309 | showNoCamera(true) 310 | focusImageView.isHidden = true 311 | delegate?.cameraNotAvailable() 312 | } 313 | 314 | func cameraMan(_ cameraMan: CameraMan, didChangeInput input: AVCaptureDeviceInput) { 315 | if !configuration.flashButtonAlwaysHidden { 316 | delegate?.setFlashButtonHidden(!input.device.hasFlash) 317 | } 318 | } 319 | 320 | func cameraManDidStart(_ cameraMan: CameraMan) { 321 | setupPreviewLayer() 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /Source/Configuration.swift: -------------------------------------------------------------------------------- 1 | import AVFoundation 2 | import UIKit 3 | 4 | @objc public class ImagePickerConfiguration: NSObject { 5 | 6 | // MARK: Colors 7 | 8 | @objc public var backgroundColor = UIColor(red: 0.15, green: 0.19, blue: 0.24, alpha: 1) 9 | @objc public var gallerySeparatorColor = UIColor.black.withAlphaComponent(0.6) 10 | @objc public var mainColor = UIColor(red: 0.09, green: 0.11, blue: 0.13, alpha: 1) 11 | @objc public var noImagesColor = UIColor(red: 0.86, green: 0.86, blue: 0.86, alpha: 1) 12 | @objc public var noCameraColor = UIColor(red: 0.86, green: 0.86, blue: 0.86, alpha: 1) 13 | @objc public var settingsColor = UIColor.white 14 | @objc public var bottomContainerColor = UIColor(red: 0.09, green: 0.11, blue: 0.13, alpha: 1) 15 | 16 | // MARK: Fonts 17 | 18 | @objc public var numberLabelFont = UIFont.systemFont(ofSize: 19, weight: UIFont.Weight.bold) 19 | @objc public var doneButton = UIFont.systemFont(ofSize: 19, weight: UIFont.Weight.medium) 20 | @objc public var flashButton = UIFont.systemFont(ofSize: 12, weight: UIFont.Weight.medium) 21 | @objc public var noImagesFont = UIFont.systemFont(ofSize: 18, weight: UIFont.Weight.medium) 22 | @objc public var noCameraFont = UIFont.systemFont(ofSize: 18, weight: UIFont.Weight.medium) 23 | @objc public var settingsFont = UIFont.systemFont(ofSize: 16, weight: UIFont.Weight.medium) 24 | 25 | // MARK: Titles 26 | 27 | @objc public var OKButtonTitle = "OK" 28 | @objc public var cancelButtonTitle = "Cancel" 29 | @objc public var doneButtonTitle = "Done" 30 | @objc public var noImagesTitle = "No images available" 31 | @objc public var noCameraTitle = "Camera is not available" 32 | @objc public var settingsTitle = "Settings" 33 | @objc public var requestPermissionTitle = "Permission denied" 34 | @objc public var requestPermissionMessage = "Please, allow the application to access to your photo library." 35 | 36 | // MARK: Dimensions 37 | 38 | @objc public var cellSpacing: CGFloat = 2 39 | @objc public var indicatorWidth: CGFloat = 41 40 | @objc public var indicatorHeight: CGFloat = 8 41 | 42 | // MARK: Custom behaviour 43 | 44 | @objc public var canRotateCamera = true 45 | @objc public var collapseCollectionViewWhileShot = true 46 | @objc public var recordLocation = true 47 | @objc public var allowMultiplePhotoSelection = true 48 | @objc public var allowVideoSelection = false 49 | @objc public var showsImageCountLabel = true 50 | @objc public var flashButtonAlwaysHidden = false 51 | @objc public var managesAudioSession = true 52 | @objc public var allowPinchToZoom = true 53 | @objc public var allowedOrientations = UIInterfaceOrientationMask.all 54 | @objc public var allowVolumeButtonsToTakePicture = true 55 | @objc public var useLowResolutionPreviewImage = false 56 | @objc public var galleryOnly = false 57 | 58 | // MARK: Images 59 | @objc public var indicatorView: UIView = { 60 | let view = UIView() 61 | view.backgroundColor = UIColor.white.withAlphaComponent(0.6) 62 | view.layer.cornerRadius = 4 63 | view.translatesAutoresizingMaskIntoConstraints = false 64 | return view 65 | }() 66 | 67 | override public init() {} 68 | } 69 | 70 | // MARK: - Orientation 71 | extension ImagePickerConfiguration { 72 | 73 | @objc public var rotationTransform: CGAffineTransform { 74 | let currentOrientation = UIDevice.current.orientation 75 | 76 | // check if current orientation is allowed 77 | switch currentOrientation { 78 | case .portrait: 79 | if allowedOrientations.contains(.portrait) { 80 | Helper.previousOrientation = currentOrientation 81 | } 82 | case .portraitUpsideDown: 83 | if allowedOrientations.contains(.portraitUpsideDown) { 84 | Helper.previousOrientation = currentOrientation 85 | } 86 | case .landscapeLeft: 87 | if allowedOrientations.contains(.landscapeLeft) { 88 | Helper.previousOrientation = currentOrientation 89 | } 90 | case .landscapeRight: 91 | if allowedOrientations.contains(.landscapeRight) { 92 | Helper.previousOrientation = currentOrientation 93 | } 94 | default: break 95 | } 96 | 97 | // set default orientation if current orientation is not allowed 98 | if Helper.previousOrientation == .unknown { 99 | if allowedOrientations.contains(.portrait) { 100 | Helper.previousOrientation = .portrait 101 | } else if allowedOrientations.contains(.landscapeLeft) { 102 | Helper.previousOrientation = .landscapeLeft 103 | } else if allowedOrientations.contains(.landscapeRight) { 104 | Helper.previousOrientation = .landscapeRight 105 | } else if allowedOrientations.contains(.portraitUpsideDown) { 106 | Helper.previousOrientation = .portraitUpsideDown 107 | } 108 | } 109 | 110 | return Helper.getTransform(fromDeviceOrientation: Helper.previousOrientation) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Source/Extensions/ConstraintsSetup.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | // MARK: - BottomContainer autolayout 4 | 5 | extension BottomContainerView { 6 | 7 | func setupConstraints() { 8 | 9 | for attribute: NSLayoutConstraint.Attribute in [.centerX, .centerY] { 10 | addConstraint(NSLayoutConstraint(item: pickerButton, attribute: attribute, 11 | relatedBy: .equal, toItem: self, attribute: attribute, 12 | multiplier: 1, constant: 0)) 13 | 14 | addConstraint(NSLayoutConstraint(item: borderPickerButton, attribute: attribute, 15 | relatedBy: .equal, toItem: self, attribute: attribute, 16 | multiplier: 1, constant: 0)) 17 | } 18 | 19 | for attribute: NSLayoutConstraint.Attribute in [.width, .left, .top] { 20 | addConstraint(NSLayoutConstraint(item: topSeparator, attribute: attribute, 21 | relatedBy: .equal, toItem: self, attribute: attribute, 22 | multiplier: 1, constant: 0)) 23 | } 24 | 25 | for attribute: NSLayoutConstraint.Attribute in [.width, .height] { 26 | addConstraint(NSLayoutConstraint(item: pickerButton, attribute: attribute, 27 | relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, 28 | multiplier: 1, constant: ButtonPicker.Dimensions.buttonSize)) 29 | 30 | addConstraint(NSLayoutConstraint(item: borderPickerButton, attribute: attribute, 31 | relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, 32 | multiplier: 1, constant: ButtonPicker.Dimensions.buttonBorderSize)) 33 | 34 | addConstraint(NSLayoutConstraint(item: stackView, attribute: attribute, 35 | relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, 36 | multiplier: 1, constant: ImageStackView.Dimensions.imageSize)) 37 | } 38 | 39 | addConstraint(NSLayoutConstraint(item: doneButton, attribute: .centerY, 40 | relatedBy: .equal, toItem: self, attribute: .centerY, 41 | multiplier: 1, constant: 0)) 42 | 43 | addConstraint(NSLayoutConstraint(item: stackView, attribute: .centerY, 44 | relatedBy: .equal, toItem: self, attribute: .centerY, 45 | multiplier: 1, constant: -2)) 46 | 47 | let screenSize = Helper.screenSizeForOrientation() 48 | 49 | addConstraint(NSLayoutConstraint(item: doneButton, attribute: .centerX, 50 | relatedBy: .equal, toItem: self, attribute: .right, 51 | multiplier: 1, constant: -(screenSize.width - (ButtonPicker.Dimensions.buttonBorderSize + screenSize.width)/2)/2)) 52 | 53 | addConstraint(NSLayoutConstraint(item: stackView, attribute: .centerX, 54 | relatedBy: .equal, toItem: self, attribute: .left, 55 | multiplier: 1, constant: screenSize.width/4 - ButtonPicker.Dimensions.buttonBorderSize/3)) 56 | 57 | addConstraint(NSLayoutConstraint(item: topSeparator, attribute: .height, 58 | relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, 59 | multiplier: 1, constant: 1)) 60 | } 61 | } 62 | 63 | // MARK: - TopView autolayout 64 | 65 | extension TopView { 66 | 67 | func setupConstraints() { 68 | addConstraint(NSLayoutConstraint(item: flashButton, attribute: .left, 69 | relatedBy: .equal, toItem: self, attribute: .left, 70 | multiplier: 1, constant: Dimensions.leftOffset)) 71 | 72 | addConstraint(NSLayoutConstraint(item: flashButton, attribute: .centerY, 73 | relatedBy: .equal, toItem: self, attribute: .centerY, 74 | multiplier: 1, constant: 0)) 75 | 76 | addConstraint(NSLayoutConstraint(item: flashButton, attribute: .width, 77 | relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, 78 | multiplier: 1, constant: 55)) 79 | 80 | if configuration.canRotateCamera { 81 | addConstraint(NSLayoutConstraint(item: rotateCamera, attribute: .right, 82 | relatedBy: .equal, toItem: self, attribute: .right, 83 | multiplier: 1, constant: Dimensions.rightOffset)) 84 | 85 | addConstraint(NSLayoutConstraint(item: rotateCamera, attribute: .centerY, 86 | relatedBy: .equal, toItem: self, attribute: .centerY, 87 | multiplier: 1, constant: 0)) 88 | 89 | addConstraint(NSLayoutConstraint(item: rotateCamera, attribute: .width, 90 | relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, 91 | multiplier: 1, constant: 55)) 92 | 93 | addConstraint(NSLayoutConstraint(item: rotateCamera, attribute: .height, 94 | relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, 95 | multiplier: 1, constant: 55)) 96 | } 97 | } 98 | } 99 | 100 | // MARK: - Controller autolayout 101 | 102 | extension ImagePickerController { 103 | 104 | func setupConstraints() { 105 | let attributes: [NSLayoutConstraint.Attribute] = [.bottom, .right, .width] 106 | let topViewAttributes: [NSLayoutConstraint.Attribute] = [.left, .width] 107 | 108 | for attribute in attributes { 109 | view.addConstraint(NSLayoutConstraint(item: bottomContainer, attribute: attribute, 110 | relatedBy: .equal, toItem: view, attribute: attribute, 111 | multiplier: 1, constant: 0)) 112 | } 113 | 114 | if configuration.galleryOnly { 115 | 116 | for attribute: NSLayoutConstraint.Attribute in [.left, .right] { 117 | view.addConstraint(NSLayoutConstraint(item: galleryView, attribute: attribute, 118 | relatedBy: .equal, toItem: view, attribute: attribute, 119 | multiplier: 1, constant: 0)) 120 | } 121 | let bottomHeightPadding: CGFloat 122 | if #available(iOS 11.0, *) { 123 | view.addConstraint(NSLayoutConstraint(item: galleryView, attribute: .top, 124 | relatedBy: .equal, toItem: view.safeAreaLayoutGuide, 125 | attribute: .top, 126 | multiplier: 1, constant: 0)) 127 | bottomHeightPadding = UIApplication.shared.keyWindow!.safeAreaInsets.bottom 128 | } else { 129 | view.addConstraint(NSLayoutConstraint(item: galleryView, attribute: .top, 130 | relatedBy: .equal, toItem: view, 131 | attribute: .top, 132 | multiplier: 1, constant: 0)) 133 | bottomHeightPadding = 0 134 | } 135 | view.addConstraint(NSLayoutConstraint(item: galleryView, attribute: .height, 136 | relatedBy: .equal, toItem: view, attribute: .height, 137 | multiplier: 1, constant: -(BottomContainerView.Dimensions.height + bottomHeightPadding))) 138 | 139 | } else { 140 | 141 | for attribute: NSLayoutConstraint.Attribute in [.left, .top, .width] { 142 | view.addConstraint(NSLayoutConstraint(item: cameraController.view!, attribute: attribute, 143 | relatedBy: .equal, toItem: view, attribute: attribute, 144 | multiplier: 1, constant: 0)) 145 | } 146 | 147 | for attribute in topViewAttributes { 148 | view.addConstraint(NSLayoutConstraint(item: topView, attribute: attribute, 149 | relatedBy: .equal, toItem: self.view, attribute: attribute, 150 | multiplier: 1, constant: 0)) 151 | } 152 | 153 | if #available(iOS 11.0, *) { 154 | view.addConstraint(NSLayoutConstraint(item: topView, attribute: .top, 155 | relatedBy: .equal, toItem: view.safeAreaLayoutGuide, 156 | attribute: .top, 157 | multiplier: 1, constant: 0)) 158 | } else { 159 | view.addConstraint(NSLayoutConstraint(item: topView, attribute: .top, 160 | relatedBy: .equal, toItem: view, 161 | attribute: .top, 162 | multiplier: 1, constant: 0)) 163 | } 164 | 165 | view.addConstraint(NSLayoutConstraint(item: topView, attribute: .height, 166 | relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, 167 | multiplier: 1, constant: TopView.Dimensions.height)) 168 | 169 | view.addConstraint(NSLayoutConstraint(item: cameraController.view!, attribute: .height, 170 | relatedBy: .equal, toItem: view, attribute: .height, 171 | multiplier: 1, constant: -BottomContainerView.Dimensions.height)) 172 | } 173 | 174 | if #available(iOS 11.0, *) { 175 | let heightPadding = UIApplication.shared.keyWindow!.safeAreaInsets.bottom 176 | view.addConstraint(NSLayoutConstraint(item: bottomContainer, attribute: .height, 177 | relatedBy: .equal, toItem: nil, 178 | attribute: .notAnAttribute, 179 | multiplier: 1, 180 | constant: BottomContainerView.Dimensions.height + heightPadding)) 181 | } else { 182 | view.addConstraint(NSLayoutConstraint(item: bottomContainer, attribute: .height, 183 | relatedBy: .equal, toItem: nil, 184 | attribute: .notAnAttribute, 185 | multiplier: 1, 186 | constant: BottomContainerView.Dimensions.height)) 187 | } 188 | } 189 | } 190 | 191 | extension ImageGalleryViewCell { 192 | 193 | func setupConstraints() { 194 | 195 | for attribute: NSLayoutConstraint.Attribute in [.width, .height, .centerX, .centerY] { 196 | addConstraint(NSLayoutConstraint(item: imageView, attribute: attribute, 197 | relatedBy: .equal, toItem: self, attribute: attribute, 198 | multiplier: 1, constant: 0)) 199 | 200 | addConstraint(NSLayoutConstraint(item: selectedImageView, attribute: attribute, 201 | relatedBy: .equal, toItem: self, attribute: attribute, 202 | multiplier: 1, constant: 0)) 203 | } 204 | } 205 | } 206 | 207 | extension ButtonPicker { 208 | 209 | func setupConstraints() { 210 | let attributes: [NSLayoutConstraint.Attribute] = [.centerX, .centerY] 211 | 212 | for attribute in attributes { 213 | addConstraint(NSLayoutConstraint(item: numberLabel, attribute: attribute, 214 | relatedBy: .equal, toItem: self, attribute: attribute, 215 | multiplier: 1, constant: 0)) 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /Source/Helper.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import AVFoundation 3 | 4 | struct Helper { 5 | 6 | static var previousOrientation = UIDeviceOrientation.unknown 7 | 8 | static func getTransform(fromDeviceOrientation orientation: UIDeviceOrientation) -> CGAffineTransform { 9 | switch orientation { 10 | case .landscapeLeft: 11 | return CGAffineTransform(rotationAngle: CGFloat.pi * 0.5) 12 | case .landscapeRight: 13 | return CGAffineTransform(rotationAngle: -(CGFloat.pi * 0.5)) 14 | case .portraitUpsideDown: 15 | return CGAffineTransform(rotationAngle: CGFloat.pi) 16 | default: 17 | return CGAffineTransform.identity 18 | } 19 | } 20 | 21 | static func getVideoOrientation(fromDeviceOrientation orientation: UIDeviceOrientation) -> AVCaptureVideoOrientation { 22 | switch orientation { 23 | case .landscapeLeft: 24 | return .landscapeRight 25 | case .landscapeRight: 26 | return .landscapeLeft 27 | case .portraitUpsideDown: 28 | return .portraitUpsideDown 29 | default: 30 | return .portrait 31 | } 32 | } 33 | 34 | static func videoOrientation() -> AVCaptureVideoOrientation { 35 | return getVideoOrientation(fromDeviceOrientation: previousOrientation) 36 | } 37 | 38 | static func screenSizeForOrientation() -> CGSize { 39 | switch UIDevice.current.orientation { 40 | case .landscapeLeft, .landscapeRight: 41 | return CGSize(width: UIScreen.main.bounds.height, 42 | height: UIScreen.main.bounds.width) 43 | default: 44 | return UIScreen.main.bounds.size 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Source/ImageGallery/ImageGalleryLayout.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class ImageGalleryLayout: UICollectionViewFlowLayout { 4 | 5 | let configuration: ImagePickerConfiguration 6 | 7 | init(configuration: ImagePickerConfiguration) { 8 | self.configuration = configuration 9 | super.init() 10 | } 11 | 12 | required init?(coder aDecoder: NSCoder) { 13 | fatalError("init(coder:) has not been implemented") 14 | } 15 | 16 | override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { 17 | guard let attributes = super.layoutAttributesForElements(in: rect) else { 18 | return super.layoutAttributesForElements(in: rect) 19 | } 20 | 21 | let newAttributes = attributes.map({ (attribute) -> UICollectionViewLayoutAttributes in 22 | // swiftlint:disable force_cast 23 | let newAttribute = attribute.copy() as! UICollectionViewLayoutAttributes 24 | newAttribute.transform = configuration.rotationTransform 25 | return newAttribute 26 | }) 27 | 28 | return newAttributes 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Source/ImageGallery/ImageGalleryView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Photos 3 | 4 | private func < (lhs: T?, rhs: T?) -> Bool { 5 | switch (lhs, rhs) { 6 | case let (someLhs?, someRhs?): 7 | return someLhs < someRhs 8 | case (nil, _?): 9 | return true 10 | default: 11 | return false 12 | } 13 | } 14 | 15 | protocol ImageGalleryPanGestureDelegate: class { 16 | 17 | func panGestureDidStart() 18 | func panGestureDidChange(_ translation: CGPoint) 19 | func panGestureDidEnd(_ translation: CGPoint, velocity: CGPoint) 20 | } 21 | 22 | open class ImageGalleryView: UIView { 23 | 24 | struct Dimensions { 25 | static let galleryHeight: CGFloat = 160 26 | static let galleryBarHeight: CGFloat = 24 27 | } 28 | 29 | var configuration = ImagePickerConfiguration() 30 | 31 | lazy open var collectionView: UICollectionView = { [unowned self] in 32 | let collectionView = UICollectionView(frame: CGRect.zero, 33 | collectionViewLayout: self.collectionViewLayout) 34 | collectionView.translatesAutoresizingMaskIntoConstraints = false 35 | collectionView.backgroundColor = self.configuration.mainColor 36 | collectionView.showsHorizontalScrollIndicator = false 37 | collectionView.dataSource = self 38 | collectionView.delegate = self 39 | 40 | return collectionView 41 | }() 42 | 43 | lazy var collectionViewLayout: UICollectionViewLayout = { [unowned self] in 44 | let layout = ImageGalleryLayout(configuration: self.configuration) 45 | layout.scrollDirection = configuration.galleryOnly ? .vertical : .horizontal 46 | layout.minimumInteritemSpacing = self.configuration.cellSpacing 47 | layout.minimumLineSpacing = 2 48 | layout.sectionInset = UIEdgeInsets.zero 49 | 50 | return layout 51 | }() 52 | 53 | lazy var topSeparator: UIView = { [unowned self] in 54 | let view = UIView() 55 | view.translatesAutoresizingMaskIntoConstraints = false 56 | view.addGestureRecognizer(self.panGestureRecognizer) 57 | view.backgroundColor = self.configuration.gallerySeparatorColor 58 | 59 | return view 60 | }() 61 | 62 | lazy var panGestureRecognizer: UIPanGestureRecognizer = { [unowned self] in 63 | let gesture = UIPanGestureRecognizer() 64 | gesture.addTarget(self, action: #selector(handlePanGestureRecognizer(_:))) 65 | 66 | return gesture 67 | }() 68 | 69 | open lazy var noImagesLabel: UILabel = { [unowned self] in 70 | let label = UILabel() 71 | label.font = self.configuration.noImagesFont 72 | label.textColor = self.configuration.noImagesColor 73 | label.text = self.configuration.noImagesTitle 74 | label.alpha = 0 75 | label.sizeToFit() 76 | self.addSubview(label) 77 | 78 | return label 79 | }() 80 | 81 | open lazy var selectedStack = ImageStack() 82 | lazy var assets = [PHAsset]() 83 | 84 | weak var delegate: ImageGalleryPanGestureDelegate? 85 | var collectionSize: CGSize? 86 | var shouldTransform = false 87 | var imagesBeforeLoading = 0 88 | var fetchResult: PHFetchResult? 89 | var imageLimit = 0 90 | 91 | // MARK: - Initializers 92 | 93 | public init(configuration: ImagePickerConfiguration? = nil) { 94 | if let configuration = configuration { 95 | self.configuration = configuration 96 | } 97 | super.init(frame: .zero) 98 | configure() 99 | } 100 | 101 | override init(frame: CGRect) { 102 | super.init(frame: frame) 103 | configure() 104 | } 105 | 106 | required public init?(coder aDecoder: NSCoder) { 107 | fatalError("init(coder:) has not been implemented") 108 | } 109 | 110 | func configure() { 111 | backgroundColor = configuration.mainColor 112 | 113 | collectionView.register(ImageGalleryViewCell.self, 114 | forCellWithReuseIdentifier: CollectionView.reusableIdentifier) 115 | 116 | if configuration.galleryOnly { 117 | addSubview(collectionView) 118 | } else { 119 | [collectionView, topSeparator].forEach { addSubview($0) } 120 | } 121 | 122 | topSeparator.addSubview(configuration.indicatorView) 123 | 124 | imagesBeforeLoading = 0 125 | fetchPhotos() 126 | } 127 | 128 | // MARK: - Layout 129 | 130 | open override func layoutSubviews() { 131 | super.layoutSubviews() 132 | updateNoImagesLabel() 133 | } 134 | 135 | func updateFrames() { 136 | let totalWidth = UIScreen.main.bounds.width 137 | frame.size.width = totalWidth 138 | let collectionFrame = frame.height == Dimensions.galleryBarHeight ? 100 + Dimensions.galleryBarHeight : frame.height 139 | topSeparator.frame = CGRect(x: 0, y: 0, width: totalWidth, height: Dimensions.galleryBarHeight) 140 | topSeparator.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleWidth] 141 | configuration.indicatorView.frame = CGRect(x: (totalWidth - configuration.indicatorWidth) / 2, y: (topSeparator.frame.height - configuration.indicatorHeight) / 2, 142 | width: configuration.indicatorWidth, height: configuration.indicatorHeight) 143 | 144 | collectionView.frame = CGRect(x: 0, 145 | y: topSeparator.superview != nil ? topSeparator.frame.height : 0, 146 | width: totalWidth, 147 | height: collectionFrame - topSeparator.frame.height) 148 | 149 | if configuration.galleryOnly { 150 | let cellSize = collectionView.bounds.width/3 - self.configuration.cellSpacing*2 151 | collectionSize = CGSize(width: cellSize, height: cellSize) 152 | } else { 153 | collectionSize = CGSize(width: collectionView.frame.height, height: collectionView.frame.height) 154 | } 155 | 156 | noImagesLabel.center = CGPoint(x: bounds.width / 2, y: (bounds.height + Dimensions.galleryBarHeight) / 2) 157 | 158 | collectionView.reloadData() 159 | } 160 | 161 | func updateNoImagesLabel() { 162 | let height = bounds.height 163 | let threshold = Dimensions.galleryBarHeight * 2 164 | 165 | UIView.animate(withDuration: 0.25, animations: { 166 | if threshold > height || self.collectionView.alpha != 0 { 167 | self.noImagesLabel.alpha = 0 168 | } else { 169 | self.noImagesLabel.center = CGPoint(x: self.bounds.width / 2, y: (height + Dimensions.galleryBarHeight) / 2) 170 | self.noImagesLabel.alpha = (height > threshold) ? 1 : (height - Dimensions.galleryBarHeight) / threshold 171 | } 172 | }) 173 | } 174 | 175 | // MARK: - Photos handler 176 | 177 | func fetchPhotos(_ completion: (() -> Void)? = nil) { 178 | AssetManager.fetch(withConfiguration: configuration) { assets in 179 | self.assets.removeAll() 180 | self.assets.append(contentsOf: assets) 181 | self.collectionView.reloadData() 182 | 183 | completion?() 184 | } 185 | } 186 | 187 | // MARK: - Pan gesture recognizer 188 | 189 | @objc func handlePanGestureRecognizer(_ gesture: UIPanGestureRecognizer) { 190 | guard let superview = superview else { return } 191 | 192 | let translation = gesture.translation(in: superview) 193 | let velocity = gesture.velocity(in: superview) 194 | 195 | switch gesture.state { 196 | case .began: 197 | delegate?.panGestureDidStart() 198 | case .changed: 199 | delegate?.panGestureDidChange(translation) 200 | case .ended: 201 | delegate?.panGestureDidEnd(translation, velocity: velocity) 202 | default: break 203 | } 204 | } 205 | 206 | func displayNoImagesMessage(_ hideCollectionView: Bool) { 207 | collectionView.alpha = hideCollectionView ? 0 : 1 208 | updateNoImagesLabel() 209 | } 210 | } 211 | 212 | // MARK: CollectionViewFlowLayout delegate methods 213 | 214 | extension ImageGalleryView: UICollectionViewDelegateFlowLayout { 215 | 216 | public func collectionView(_ collectionView: UICollectionView, 217 | layout collectionViewLayout: UICollectionViewLayout, 218 | sizeForItemAt indexPath: IndexPath) -> CGSize { 219 | guard let collectionSize = collectionSize else { return CGSize.zero } 220 | 221 | return collectionSize 222 | } 223 | } 224 | 225 | // MARK: CollectionView delegate methods 226 | 227 | extension ImageGalleryView: UICollectionViewDelegate { 228 | 229 | public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 230 | guard let cell = collectionView.cellForItem(at: indexPath) 231 | as? ImageGalleryViewCell else { return } 232 | if configuration.allowMultiplePhotoSelection == false { 233 | // Clear selected photos array 234 | for asset in self.selectedStack.assets { 235 | self.selectedStack.dropAsset(asset) 236 | } 237 | // Animate deselecting photos for any selected visible cells 238 | guard let visibleCells = collectionView.visibleCells as? [ImageGalleryViewCell] else { return } 239 | for cell in visibleCells where cell.selectedImageView.image != nil { 240 | UIView.animate(withDuration: 0.2, animations: { 241 | cell.selectedImageView.transform = CGAffineTransform(scaleX: 0.1, y: 0.1) 242 | }, completion: { _ in 243 | cell.selectedImageView.image = nil 244 | }) 245 | } 246 | } 247 | 248 | let asset = assets[(indexPath as NSIndexPath).row] 249 | 250 | AssetManager.resolveAsset(asset, size: CGSize(width: 100, height: 100), shouldPreferLowRes: configuration.useLowResolutionPreviewImage) { image in 251 | guard image != nil else { return } 252 | 253 | if cell.selectedImageView.image != nil { 254 | UIView.animate(withDuration: 0.2, animations: { 255 | cell.selectedImageView.transform = CGAffineTransform(scaleX: 0.1, y: 0.1) 256 | }, completion: { _ in 257 | cell.selectedImageView.image = nil 258 | }) 259 | self.selectedStack.dropAsset(asset) 260 | } else if self.imageLimit == 0 || self.imageLimit > self.selectedStack.assets.count { 261 | cell.selectedImageView.image = AssetManager.getImage("selectedImageGallery") 262 | cell.selectedImageView.transform = CGAffineTransform(scaleX: 0, y: 0) 263 | UIView.animate(withDuration: 0.2, animations: { 264 | cell.selectedImageView.transform = CGAffineTransform.identity 265 | }) 266 | self.selectedStack.pushAsset(asset) 267 | } 268 | } 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /Source/ImageGallery/ImageGalleryViewCell.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class ImageGalleryViewCell: UICollectionViewCell { 4 | 5 | lazy var imageView = UIImageView() 6 | lazy var selectedImageView = UIImageView() 7 | private var videoInfoView: VideoInfoView 8 | 9 | private let videoInfoBarHeight: CGFloat = 15 10 | var duration: TimeInterval? { 11 | didSet { 12 | if let duration = duration, duration > 0 { 13 | self.videoInfoView.duration = duration 14 | self.videoInfoView.isHidden = false 15 | } else { 16 | self.videoInfoView.isHidden = true 17 | } 18 | } 19 | } 20 | 21 | override init(frame: CGRect) { 22 | let videoBarFrame = CGRect(x: 0, y: frame.height - self.videoInfoBarHeight, 23 | width: frame.width, height: self.videoInfoBarHeight) 24 | videoInfoView = VideoInfoView(frame: videoBarFrame) 25 | super.init(frame: frame) 26 | 27 | for view in [imageView, selectedImageView, videoInfoView] as [UIView] { 28 | view.contentMode = .scaleAspectFill 29 | view.translatesAutoresizingMaskIntoConstraints = false 30 | view.clipsToBounds = true 31 | contentView.addSubview(view) 32 | } 33 | 34 | isAccessibilityElement = true 35 | accessibilityLabel = "Photo" 36 | 37 | setupConstraints() 38 | } 39 | 40 | required init?(coder aDecoder: NSCoder) { 41 | fatalError("init(coder:) has not been implemented") 42 | } 43 | 44 | // MARK: - Configuration 45 | 46 | func configureCell(_ image: UIImage) { 47 | imageView.image = image 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Source/ImageGallery/ImageGalleryViewDataSource.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension ImageGalleryView: UICollectionViewDataSource { 4 | 5 | struct CollectionView { 6 | static let reusableIdentifier = "imagesReusableIdentifier" 7 | } 8 | 9 | public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 10 | displayNoImagesMessage(assets.isEmpty) 11 | return assets.count 12 | } 13 | 14 | public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 15 | guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CollectionView.reusableIdentifier, 16 | for: indexPath) as? ImageGalleryViewCell else { return UICollectionViewCell() } 17 | 18 | let asset = assets[(indexPath as NSIndexPath).row] 19 | 20 | AssetManager.resolveAsset(asset, size: CGSize(width: 160, height: 240), shouldPreferLowRes: configuration.useLowResolutionPreviewImage) { image in 21 | if let image = image { 22 | cell.configureCell(image) 23 | 24 | if (indexPath as NSIndexPath).row == 0 && self.shouldTransform { 25 | cell.transform = CGAffineTransform(scaleX: 0, y: 0) 26 | 27 | UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: UIView.AnimationOptions(), animations: { 28 | cell.transform = CGAffineTransform.identity 29 | }) { _ in } 30 | 31 | self.shouldTransform = false 32 | } 33 | 34 | if self.selectedStack.containsAsset(asset) { 35 | cell.selectedImageView.image = AssetManager.getImage("selectedImageGallery") 36 | cell.selectedImageView.alpha = 1 37 | cell.selectedImageView.transform = CGAffineTransform.identity 38 | } else { 39 | cell.selectedImageView.image = nil 40 | } 41 | cell.duration = asset.duration 42 | } 43 | } 44 | 45 | return cell 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Source/ImageGallery/VideoInfoView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class VideoInfoView: UIView { 4 | 5 | var duration: TimeInterval? { 6 | didSet { 7 | videoInfoLabel.text = dateFormatter.string(from: duration ?? 0) 8 | } 9 | } 10 | 11 | private lazy var videoIcon: UIImageView = { 12 | var videoIcon = UIImageView(image: AssetManager.getImage("video")) 13 | videoIcon.frame = CGRect(x: 3, 14 | y: 0, 15 | width: self.bounds.height, 16 | height: self.bounds.height) 17 | videoIcon.contentMode = .scaleAspectFit 18 | return videoIcon 19 | }() 20 | 21 | private lazy var videoInfoLabel: UILabel = { 22 | let videoInfoLabel = UILabel(frame: CGRect(x: 0, 23 | y: 0, 24 | width: self.bounds.width - 5, 25 | height: self.bounds.height)) 26 | videoInfoLabel.font = UIFont.systemFont(ofSize: 10) 27 | videoInfoLabel.textColor = .white 28 | videoInfoLabel.textAlignment = .right 29 | videoInfoLabel.text = self.dateFormatter.string(from: self.duration ?? 0) 30 | return videoInfoLabel 31 | }() 32 | 33 | private lazy var dateFormatter: DateComponentsFormatter = { 34 | let formatter = DateComponentsFormatter() 35 | formatter.zeroFormattingBehavior = .pad 36 | formatter.allowedUnits = [.hour, .minute, .second] 37 | formatter.unitsStyle = .positional 38 | return formatter 39 | }() 40 | 41 | override init(frame: CGRect) { 42 | super.init(frame: frame) 43 | 44 | backgroundColor = UIColor(white: 0, alpha: 0.5) 45 | addSubview(self.videoIcon) 46 | addSubview(self.videoInfoLabel) 47 | } 48 | 49 | required init?(coder aDecoder: NSCoder) { 50 | fatalError("init(coder:) has not been implemented") 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Source/ImagePickerController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import MediaPlayer 3 | import Photos 4 | 5 | @objc public protocol ImagePickerDelegate: NSObjectProtocol { 6 | 7 | func wrapperDidPress(_ imagePicker: ImagePickerController, images: [UIImage]) 8 | func doneButtonDidPress(_ imagePicker: ImagePickerController, images: [UIImage]) 9 | func cancelButtonDidPress(_ imagePicker: ImagePickerController) 10 | } 11 | 12 | open class ImagePickerController: UIViewController { 13 | 14 | let configuration: ImagePickerConfiguration 15 | 16 | struct GestureConstants { 17 | static let maximumHeight: CGFloat = 200 18 | static let minimumHeight: CGFloat = 125 19 | static let velocity: CGFloat = 100 20 | } 21 | 22 | open lazy var galleryView: ImageGalleryView = { [unowned self] in 23 | let galleryView = ImageGalleryView(configuration: self.configuration) 24 | galleryView.delegate = self 25 | galleryView.selectedStack = self.stack 26 | galleryView.collectionView.layer.anchorPoint = CGPoint(x: 0, y: 0) 27 | galleryView.imageLimit = self.imageLimit 28 | 29 | return galleryView 30 | }() 31 | 32 | open lazy var bottomContainer: BottomContainerView = { [unowned self] in 33 | let view = BottomContainerView(configuration: self.configuration) 34 | view.backgroundColor = self.configuration.bottomContainerColor 35 | view.delegate = self 36 | 37 | return view 38 | }() 39 | 40 | open lazy var topView: TopView = { [unowned self] in 41 | let view = TopView(configuration: self.configuration) 42 | view.backgroundColor = UIColor.clear 43 | view.delegate = self 44 | 45 | return view 46 | }() 47 | 48 | lazy var cameraController: CameraView = { [unowned self] in 49 | let controller = CameraView(configuration: self.configuration) 50 | controller.delegate = self 51 | controller.startOnFrontCamera = self.startOnFrontCamera 52 | 53 | return controller 54 | }() 55 | 56 | lazy var panGestureRecognizer: UIPanGestureRecognizer = { [unowned self] in 57 | let gesture = UIPanGestureRecognizer() 58 | gesture.addTarget(self, action: #selector(panGestureRecognizerHandler(_:))) 59 | 60 | return gesture 61 | }() 62 | 63 | lazy var volumeView: MPVolumeView = { [unowned self] in 64 | let view = MPVolumeView() 65 | view.frame = CGRect(x: 0, y: 0, width: 1, height: 1) 66 | 67 | return view 68 | }() 69 | 70 | var volume = AVAudioSession.sharedInstance().outputVolume 71 | 72 | @objc open weak var delegate: ImagePickerDelegate? 73 | open var stack = ImageStack() 74 | open var imageLimit = 0 75 | open var preferredImageSize: CGSize? 76 | open var startOnFrontCamera = false 77 | var totalSize: CGSize { return UIScreen.main.bounds.size } 78 | var initialFrame: CGRect? 79 | var initialContentOffset: CGPoint? 80 | var numberOfCells: Int? 81 | var statusBarHidden = true 82 | 83 | fileprivate var isTakingPicture = false 84 | open var doneButtonTitle: String? { 85 | didSet { 86 | if let doneButtonTitle = doneButtonTitle { 87 | bottomContainer.doneButton.setTitle(doneButtonTitle, for: UIControl.State()) 88 | } 89 | } 90 | } 91 | 92 | // MARK: - Initialization 93 | 94 | @objc public required init(configuration: ImagePickerConfiguration = ImagePickerConfiguration()) { 95 | self.configuration = configuration 96 | super.init(nibName: nil, bundle: nil) 97 | } 98 | 99 | public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 100 | self.configuration = ImagePickerConfiguration() 101 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 102 | } 103 | 104 | public required init?(coder aDecoder: NSCoder) { 105 | self.configuration = ImagePickerConfiguration() 106 | super.init(coder: aDecoder) 107 | } 108 | 109 | // MARK: - View lifecycle 110 | 111 | open override func viewDidLoad() { 112 | super.viewDidLoad() 113 | 114 | let addSubview: (UIView) -> Void = { subview in 115 | self.view.addSubview(subview) 116 | subview.translatesAutoresizingMaskIntoConstraints = false 117 | } 118 | 119 | if !configuration.galleryOnly { 120 | addSubview(cameraController.view) 121 | addSubview(topView) 122 | cameraController.view.addGestureRecognizer(panGestureRecognizer) 123 | } 124 | 125 | for subview in [galleryView, bottomContainer] { 126 | addSubview(subview) 127 | } 128 | 129 | view.addSubview(volumeView) 130 | view.sendSubviewToBack(volumeView) 131 | 132 | view.backgroundColor = UIColor.white 133 | view.backgroundColor = configuration.mainColor 134 | 135 | subscribe() 136 | setupConstraints() 137 | } 138 | 139 | open override func viewWillAppear(_ animated: Bool) { 140 | super.viewWillAppear(animated) 141 | 142 | if configuration.managesAudioSession { 143 | _ = try? AVAudioSession.sharedInstance().setActive(true) 144 | } 145 | 146 | statusBarHidden = UIApplication.shared.isStatusBarHidden 147 | 148 | self.handleRotation(nil) 149 | } 150 | 151 | open override func viewDidAppear(_ animated: Bool) { 152 | super.viewDidAppear(animated) 153 | 154 | let galleryHeight: CGFloat = UIScreen.main.nativeBounds.height == 960 155 | ? ImageGalleryView.Dimensions.galleryBarHeight : GestureConstants.minimumHeight 156 | 157 | galleryView.collectionView.transform = CGAffineTransform.identity 158 | galleryView.collectionView.contentInset = UIEdgeInsets.zero 159 | 160 | if !configuration.galleryOnly { 161 | galleryView.frame = CGRect(x: 0, 162 | y: totalSize.height - bottomContainer.frame.height - galleryHeight, 163 | width: totalSize.width, 164 | height: galleryHeight) 165 | } 166 | galleryView.updateFrames() 167 | checkStatus() 168 | 169 | initialFrame = galleryView.frame 170 | initialContentOffset = galleryView.collectionView.contentOffset 171 | 172 | applyOrientationTransforms() 173 | 174 | UIAccessibility.post(notification: UIAccessibility.Notification.screenChanged, argument: bottomContainer) 175 | } 176 | 177 | open func resetAssets() { 178 | self.stack.resetAssets([]) 179 | } 180 | 181 | func checkStatus() { 182 | let currentStatus = PHPhotoLibrary.authorizationStatus() 183 | guard currentStatus != .authorized else { return } 184 | 185 | if currentStatus == .notDetermined { hideViews() } 186 | 187 | PHPhotoLibrary.requestAuthorization { (authorizationStatus) -> Void in 188 | DispatchQueue.main.async { 189 | if authorizationStatus == .denied { 190 | self.presentAskPermissionAlert() 191 | } else if authorizationStatus == .authorized { 192 | self.permissionGranted() 193 | } 194 | } 195 | } 196 | } 197 | 198 | func presentAskPermissionAlert() { 199 | let alertController = UIAlertController(title: configuration.requestPermissionTitle, message: configuration.requestPermissionMessage, preferredStyle: .alert) 200 | 201 | let alertAction = UIAlertAction(title: configuration.OKButtonTitle, style: .default) { _ in 202 | if let settingsURL = URL(string: UIApplication.openSettingsURLString) { 203 | UIApplication.shared.openURL(settingsURL) 204 | } 205 | } 206 | 207 | let cancelAction = UIAlertAction(title: configuration.cancelButtonTitle, style: .cancel) { _ in 208 | self.dismiss(animated: true, completion: nil) 209 | } 210 | 211 | alertController.addAction(alertAction) 212 | alertController.addAction(cancelAction) 213 | 214 | present(alertController, animated: true, completion: nil) 215 | } 216 | 217 | func hideViews() { 218 | enableGestures(false) 219 | } 220 | 221 | func permissionGranted() { 222 | galleryView.fetchPhotos() 223 | enableGestures(true) 224 | } 225 | 226 | // MARK: - Notifications 227 | 228 | deinit { 229 | if configuration.managesAudioSession { 230 | _ = try? AVAudioSession.sharedInstance().setActive(false) 231 | } 232 | 233 | NotificationCenter.default.removeObserver(self) 234 | } 235 | 236 | func subscribe() { 237 | NotificationCenter.default.addObserver(self, 238 | selector: #selector(adjustButtonTitle(_:)), 239 | name: NSNotification.Name(rawValue: ImageStack.Notifications.imageDidPush), 240 | object: nil) 241 | 242 | NotificationCenter.default.addObserver(self, 243 | selector: #selector(adjustButtonTitle(_:)), 244 | name: NSNotification.Name(rawValue: ImageStack.Notifications.imageDidDrop), 245 | object: nil) 246 | 247 | NotificationCenter.default.addObserver(self, 248 | selector: #selector(dismissIfNeeded), 249 | name: NSNotification.Name(rawValue: ImageStack.Notifications.imageDidPush), 250 | object: nil) 251 | 252 | NotificationCenter.default.addObserver(self, 253 | selector: #selector(didReloadAssets(_:)), 254 | name: NSNotification.Name(rawValue: ImageStack.Notifications.stackDidReload), 255 | object: nil) 256 | 257 | NotificationCenter.default.addObserver(self, 258 | selector: #selector(volumeChanged(_:)), 259 | name: NSNotification.Name(rawValue: "AVSystemController_SystemVolumeDidChangeNotification"), 260 | object: nil) 261 | 262 | NotificationCenter.default.addObserver(self, 263 | selector: #selector(handleRotation(_:)), 264 | name: UIDevice.orientationDidChangeNotification, 265 | object: nil) 266 | } 267 | 268 | @objc func didReloadAssets(_ notification: Notification) { 269 | adjustButtonTitle(notification) 270 | galleryView.collectionView.reloadData() 271 | galleryView.collectionView.setContentOffset(CGPoint.zero, animated: false) 272 | } 273 | 274 | @objc func volumeChanged(_ notification: Notification) { 275 | guard configuration.allowVolumeButtonsToTakePicture, 276 | let slider = volumeView.subviews.filter({ $0 is UISlider }).first as? UISlider, 277 | let userInfo = (notification as NSNotification).userInfo, 278 | let changeReason = userInfo["AVSystemController_AudioVolumeChangeReasonNotificationParameter"] as? String, changeReason == "ExplicitVolumeChange" else { return } 279 | 280 | slider.setValue(volume, animated: false) 281 | takePicture() 282 | } 283 | 284 | @objc func adjustButtonTitle(_ notification: Notification) { 285 | guard let sender = notification.object as? ImageStack else { return } 286 | 287 | let title = !sender.assets.isEmpty ? 288 | configuration.doneButtonTitle : configuration.cancelButtonTitle 289 | bottomContainer.doneButton.setTitle(title, for: UIControl.State()) 290 | } 291 | 292 | @objc func dismissIfNeeded() { 293 | // If only one image is requested and a push occures, automatically dismiss the ImagePicker 294 | if imageLimit == 1 { 295 | doneButtonDidPress() 296 | } 297 | } 298 | 299 | // MARK: - Helpers 300 | 301 | open override var prefersStatusBarHidden: Bool { 302 | return statusBarHidden 303 | } 304 | 305 | open func collapseGalleryView(_ completion: (() -> Void)?) { 306 | galleryView.collectionViewLayout.invalidateLayout() 307 | UIView.animate(withDuration: 0.3, animations: { 308 | self.updateGalleryViewFrames(self.galleryView.topSeparator.frame.height) 309 | self.galleryView.collectionView.transform = CGAffineTransform.identity 310 | self.galleryView.collectionView.contentInset = UIEdgeInsets.zero 311 | }, completion: { _ in 312 | completion?() 313 | }) 314 | } 315 | 316 | open func showGalleryView() { 317 | galleryView.collectionViewLayout.invalidateLayout() 318 | UIView.animate(withDuration: 0.3, animations: { 319 | self.updateGalleryViewFrames(GestureConstants.minimumHeight) 320 | self.galleryView.collectionView.transform = CGAffineTransform.identity 321 | self.galleryView.collectionView.contentInset = UIEdgeInsets.zero 322 | }) 323 | } 324 | 325 | open func expandGalleryView() { 326 | galleryView.collectionViewLayout.invalidateLayout() 327 | 328 | UIView.animate(withDuration: 0.3, animations: { 329 | self.updateGalleryViewFrames(GestureConstants.maximumHeight) 330 | 331 | let scale = (GestureConstants.maximumHeight - ImageGalleryView.Dimensions.galleryBarHeight) / (GestureConstants.minimumHeight - ImageGalleryView.Dimensions.galleryBarHeight) 332 | self.galleryView.collectionView.transform = CGAffineTransform(scaleX: scale, y: scale) 333 | 334 | let value = self.view.frame.width * (scale - 1) / scale 335 | self.galleryView.collectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: value) 336 | }) 337 | } 338 | 339 | func updateGalleryViewFrames(_ constant: CGFloat) { 340 | galleryView.frame.origin.y = totalSize.height - bottomContainer.frame.height - constant 341 | galleryView.frame.size.height = constant 342 | } 343 | 344 | func enableGestures(_ enabled: Bool) { 345 | galleryView.alpha = enabled ? 1 : 0 346 | bottomContainer.pickerButton.isEnabled = enabled 347 | bottomContainer.tapGestureRecognizer.isEnabled = enabled 348 | topView.flashButton.isEnabled = enabled 349 | topView.rotateCamera.isEnabled = configuration.canRotateCamera 350 | } 351 | 352 | fileprivate func isBelowImageLimit() -> Bool { 353 | return (imageLimit == 0 || imageLimit > galleryView.selectedStack.assets.count) 354 | } 355 | 356 | fileprivate func takePicture() { 357 | guard isBelowImageLimit() && !isTakingPicture else { return } 358 | isTakingPicture = true 359 | bottomContainer.pickerButton.isEnabled = false 360 | bottomContainer.stackView.startLoader() 361 | let action: () -> Void = { [weak self] in 362 | guard let `self` = self else { return } 363 | self.cameraController.takePicture { self.isTakingPicture = false } 364 | } 365 | 366 | if configuration.collapseCollectionViewWhileShot { 367 | collapseGalleryView(action) 368 | } else { 369 | action() 370 | } 371 | } 372 | } 373 | 374 | // MARK: - Action methods 375 | 376 | extension ImagePickerController: BottomContainerViewDelegate { 377 | 378 | func pickerButtonDidPress() { 379 | takePicture() 380 | } 381 | 382 | func doneButtonDidPress() { 383 | var images: [UIImage] 384 | if let preferredImageSize = preferredImageSize { 385 | images = AssetManager.resolveAssets(stack.assets, size: preferredImageSize) 386 | } else { 387 | images = AssetManager.resolveAssets(stack.assets) 388 | } 389 | 390 | delegate?.doneButtonDidPress(self, images: images) 391 | } 392 | 393 | func cancelButtonDidPress() { 394 | delegate?.cancelButtonDidPress(self) 395 | } 396 | 397 | func imageStackViewDidPress() { 398 | var images: [UIImage] 399 | if let preferredImageSize = preferredImageSize { 400 | images = AssetManager.resolveAssets(stack.assets, size: preferredImageSize) 401 | } else { 402 | images = AssetManager.resolveAssets(stack.assets) 403 | } 404 | 405 | delegate?.wrapperDidPress(self, images: images) 406 | } 407 | } 408 | 409 | extension ImagePickerController: CameraViewDelegate { 410 | 411 | func setFlashButtonHidden(_ hidden: Bool) { 412 | if configuration.flashButtonAlwaysHidden { 413 | topView.flashButton.isHidden = hidden 414 | } 415 | } 416 | 417 | func imageToLibrary() { 418 | guard let collectionSize = galleryView.collectionSize else { return } 419 | 420 | galleryView.fetchPhotos { 421 | guard let asset = self.galleryView.assets.first else { return } 422 | if self.configuration.allowMultiplePhotoSelection == false { 423 | self.stack.assets.removeAll() 424 | } 425 | self.stack.pushAsset(asset) 426 | } 427 | 428 | galleryView.shouldTransform = true 429 | bottomContainer.pickerButton.isEnabled = true 430 | 431 | UIView.animate(withDuration: 0.3, animations: { 432 | self.galleryView.collectionView.transform = CGAffineTransform(translationX: collectionSize.width, y: 0) 433 | }, completion: { _ in 434 | self.galleryView.collectionView.transform = CGAffineTransform.identity 435 | }) 436 | } 437 | 438 | func cameraNotAvailable() { 439 | topView.flashButton.isHidden = true 440 | topView.rotateCamera.isHidden = true 441 | bottomContainer.pickerButton.isEnabled = false 442 | } 443 | 444 | // MARK: - Rotation 445 | 446 | open override var supportedInterfaceOrientations: UIInterfaceOrientationMask { 447 | return .portrait 448 | } 449 | 450 | @objc public func handleRotation(_ note: Notification?) { 451 | applyOrientationTransforms() 452 | } 453 | 454 | func applyOrientationTransforms() { 455 | let rotate = configuration.rotationTransform 456 | 457 | UIView.animate(withDuration: 0.25, animations: { 458 | [self.topView.rotateCamera, self.bottomContainer.pickerButton, 459 | self.bottomContainer.stackView, self.bottomContainer.doneButton].forEach { 460 | $0.transform = rotate 461 | } 462 | 463 | self.galleryView.collectionViewLayout.invalidateLayout() 464 | 465 | let translate: CGAffineTransform 466 | if Helper.previousOrientation.isLandscape { 467 | translate = CGAffineTransform(translationX: -20, y: 15) 468 | } else { 469 | translate = CGAffineTransform.identity 470 | } 471 | 472 | self.topView.flashButton.transform = rotate.concatenating(translate) 473 | }) 474 | } 475 | } 476 | 477 | // MARK: - TopView delegate methods 478 | 479 | extension ImagePickerController: TopViewDelegate { 480 | 481 | func flashButtonDidPress(_ title: String) { 482 | cameraController.flashCamera(title) 483 | } 484 | 485 | func rotateDeviceDidPress() { 486 | cameraController.rotateCamera() 487 | } 488 | } 489 | 490 | // MARK: - Pan gesture handler 491 | 492 | extension ImagePickerController: ImageGalleryPanGestureDelegate { 493 | 494 | func panGestureDidStart() { 495 | guard let collectionSize = galleryView.collectionSize else { return } 496 | 497 | initialFrame = galleryView.frame 498 | initialContentOffset = galleryView.collectionView.contentOffset 499 | if let contentOffset = initialContentOffset { numberOfCells = Int(contentOffset.x / collectionSize.width) } 500 | } 501 | 502 | @objc func panGestureRecognizerHandler(_ gesture: UIPanGestureRecognizer) { 503 | let translation = gesture.translation(in: view) 504 | let velocity = gesture.velocity(in: view) 505 | 506 | if gesture.location(in: view).y > galleryView.frame.origin.y - 25 { 507 | gesture.state == .began ? panGestureDidStart() : panGestureDidChange(translation) 508 | } 509 | 510 | if gesture.state == .ended { 511 | panGestureDidEnd(translation, velocity: velocity) 512 | } 513 | } 514 | 515 | func panGestureDidChange(_ translation: CGPoint) { 516 | guard let initialFrame = initialFrame else { return } 517 | 518 | let galleryHeight = initialFrame.height - translation.y 519 | 520 | if galleryHeight >= GestureConstants.maximumHeight { return } 521 | 522 | if galleryHeight <= ImageGalleryView.Dimensions.galleryBarHeight { 523 | updateGalleryViewFrames(ImageGalleryView.Dimensions.galleryBarHeight) 524 | } else if galleryHeight >= GestureConstants.minimumHeight { 525 | let scale = (galleryHeight - ImageGalleryView.Dimensions.galleryBarHeight) / (GestureConstants.minimumHeight - ImageGalleryView.Dimensions.galleryBarHeight) 526 | galleryView.collectionView.transform = CGAffineTransform(scaleX: scale, y: scale) 527 | galleryView.frame.origin.y = initialFrame.origin.y + translation.y 528 | galleryView.frame.size.height = initialFrame.height - translation.y 529 | 530 | let value = view.frame.width * (scale - 1) / scale 531 | galleryView.collectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: value) 532 | } else { 533 | galleryView.frame.origin.y = initialFrame.origin.y + translation.y 534 | galleryView.frame.size.height = initialFrame.height - translation.y 535 | } 536 | 537 | galleryView.updateNoImagesLabel() 538 | } 539 | 540 | func panGestureDidEnd(_ translation: CGPoint, velocity: CGPoint) { 541 | guard let initialFrame = initialFrame else { return } 542 | let galleryHeight = initialFrame.height - translation.y 543 | if galleryView.frame.height < GestureConstants.minimumHeight && velocity.y < 0 { 544 | showGalleryView() 545 | } else if velocity.y < -GestureConstants.velocity { 546 | expandGalleryView() 547 | } else if velocity.y > GestureConstants.velocity || galleryHeight < GestureConstants.minimumHeight { 548 | collapseGalleryView(nil) 549 | } 550 | } 551 | } 552 | -------------------------------------------------------------------------------- /Source/LocationManager.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import CoreLocation 3 | 4 | class LocationManager: NSObject, CLLocationManagerDelegate { 5 | var locationManager = CLLocationManager() 6 | var latestLocation: CLLocation? 7 | 8 | override init() { 9 | super.init() 10 | locationManager.delegate = self 11 | locationManager.desiredAccuracy = kCLLocationAccuracyBest 12 | locationManager.requestWhenInUseAuthorization() 13 | } 14 | 15 | func startUpdatingLocation() { 16 | locationManager.startUpdatingLocation() 17 | } 18 | 19 | func stopUpdatingLocation() { 20 | locationManager.stopUpdatingLocation() 21 | } 22 | 23 | // MARK: - CLLocationManagerDelegate 24 | 25 | func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { 26 | // Pick the location with best (= smallest value) horizontal accuracy 27 | latestLocation = locations.sorted { $0.horizontalAccuracy < $1.horizontalAccuracy }.first 28 | } 29 | 30 | func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { 31 | if status == .authorizedAlways || status == .authorizedWhenInUse { 32 | locationManager.startUpdatingLocation() 33 | } else { 34 | locationManager.stopUpdatingLocation() 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Source/TopView/TopView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | protocol TopViewDelegate: class { 4 | 5 | func flashButtonDidPress(_ title: String) 6 | func rotateDeviceDidPress() 7 | } 8 | 9 | open class TopView: UIView { 10 | 11 | struct Dimensions { 12 | static let leftOffset: CGFloat = 11 13 | static let rightOffset: CGFloat = 7 14 | static let height: CGFloat = 34 15 | } 16 | 17 | var configuration = ImagePickerConfiguration() 18 | 19 | var currentFlashIndex = 0 20 | let flashButtonTitles = ["AUTO", "ON", "OFF"] 21 | 22 | open lazy var flashButton: UIButton = { [unowned self] in 23 | let button = UIButton() 24 | button.setImage(AssetManager.getImage("AUTO"), for: UIControl.State()) 25 | button.setTitle("AUTO", for: UIControl.State()) 26 | button.titleEdgeInsets = UIEdgeInsets(top: 0, left: 4, bottom: 0, right: 0) 27 | button.setTitleColor(UIColor.white, for: UIControl.State()) 28 | button.setTitleColor(UIColor.white, for: .highlighted) 29 | button.titleLabel?.font = self.configuration.flashButton 30 | button.addTarget(self, action: #selector(flashButtonDidPress(_:)), for: .touchUpInside) 31 | button.contentHorizontalAlignment = .left 32 | button.accessibilityLabel = "Flash mode is auto" 33 | button.accessibilityHint = "Double-tap to change flash mode" 34 | 35 | return button 36 | }() 37 | 38 | open lazy var rotateCamera: UIButton = { [unowned self] in 39 | let button = UIButton() 40 | button.accessibilityLabel = "" 41 | button.accessibilityHint = "Double-tap to rotate camera" 42 | button.setImage(AssetManager.getImage("cameraIcon"), for: UIControl.State()) 43 | button.addTarget(self, action: #selector(rotateCameraButtonDidPress(_:)), for: .touchUpInside) 44 | button.imageView?.contentMode = .center 45 | 46 | return button 47 | }() 48 | 49 | weak var delegate: TopViewDelegate? 50 | 51 | // MARK: - Initializers 52 | 53 | public init(configuration: ImagePickerConfiguration? = nil) { 54 | if let configuration = configuration { 55 | self.configuration = configuration 56 | } 57 | super.init(frame: .zero) 58 | configure() 59 | } 60 | 61 | override public init(frame: CGRect) { 62 | super.init(frame: frame) 63 | configure() 64 | } 65 | 66 | required public init?(coder aDecoder: NSCoder) { 67 | fatalError("init(coder:) has not been implemented") 68 | } 69 | 70 | func configure() { 71 | var buttons: [UIButton] = [flashButton] 72 | 73 | if configuration.canRotateCamera { 74 | buttons.append(rotateCamera) 75 | } 76 | 77 | for button in buttons { 78 | button.layer.shadowColor = UIColor.black.cgColor 79 | button.layer.shadowOpacity = 0.5 80 | button.layer.shadowOffset = CGSize(width: 0, height: 1) 81 | button.layer.shadowRadius = 1 82 | button.translatesAutoresizingMaskIntoConstraints = false 83 | addSubview(button) 84 | } 85 | 86 | flashButton.isHidden = configuration.flashButtonAlwaysHidden 87 | 88 | setupConstraints() 89 | } 90 | 91 | // MARK: - Action methods 92 | 93 | @objc func flashButtonDidPress(_ button: UIButton) { 94 | currentFlashIndex += 1 95 | currentFlashIndex = currentFlashIndex % flashButtonTitles.count 96 | 97 | switch currentFlashIndex { 98 | case 1: 99 | button.setTitleColor(UIColor(red: 0.98, green: 0.98, blue: 0.45, alpha: 1), for: UIControl.State()) 100 | button.setTitleColor(UIColor(red: 0.52, green: 0.52, blue: 0.24, alpha: 1), for: .highlighted) 101 | 102 | default: 103 | button.setTitleColor(UIColor.white, for: UIControl.State()) 104 | button.setTitleColor(UIColor.white, for: .highlighted) 105 | } 106 | 107 | let newTitle = flashButtonTitles[currentFlashIndex] 108 | 109 | button.setImage(AssetManager.getImage(newTitle), for: UIControl.State()) 110 | button.setTitle(newTitle, for: UIControl.State()) 111 | button.accessibilityLabel = "Flash mode is \(newTitle)" 112 | 113 | delegate?.flashButtonDidPress(newTitle) 114 | } 115 | 116 | @objc func rotateCameraButtonDidPress(_ button: UIButton) { 117 | delegate?.rotateDeviceDidPress() 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /SupportFiles/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 | --------------------------------------------------------------------------------