├── Images
├── AUTO@3x.png
├── OFF@3x.png
├── ON@3x.png
├── video@3x.png
├── focusIcon@3x.png
├── cameraIcon@3x.png
└── selectedImageGallery@3x.png
├── Resources
├── ImagePickerIcon.png
└── ImagePickerPresentation.png
├── Demo
└── ImagePickerDemo
│ ├── ImagePickerDemo
│ ├── Images.xcassets
│ │ ├── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ ├── Icon.png
│ │ │ ├── Icon-40.png
│ │ │ ├── Icon-72.png
│ │ │ ├── Icon-76.png
│ │ │ ├── Icon@2x.png
│ │ │ ├── Icon-40@2x.png
│ │ │ ├── Icon-40@3x.png
│ │ │ ├── Icon-60@2x.png
│ │ │ ├── Icon-60@3x.png
│ │ │ ├── Icon-72@2x.png
│ │ │ ├── Icon-76@2x.png
│ │ │ ├── Icon-83.5@2x.png
│ │ │ ├── Icon-Small.png
│ │ │ ├── Icon-Small-50.png
│ │ │ ├── Icon-Small@2x.png
│ │ │ ├── Icon-Small@3x.png
│ │ │ ├── Icon-Small-50@2x.png
│ │ │ └── Contents.json
│ ├── AppDelegate.swift
│ ├── Info.plist
│ ├── ViewController.swift
│ └── Base.lproj
│ │ └── LaunchScreen.xib
│ ├── Podfile
│ ├── ImagePickerDemo.xcodeproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── project.pbxproj
│ ├── ImagePickerDemo.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ └── Podfile.lock
├── ImagePicker.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── ImagePicker.xcscmblueprint
├── xcshareddata
│ └── xcschemes
│ │ └── ImagePicker-iOS.xcscheme
└── project.pbxproj
├── .gitignore
├── ImagePicker.podspec
├── SupportFiles
└── Info.plist
├── CONTRIBUTING.md
├── Package.swift
├── Source
├── ImageGallery
│ ├── ImageGalleryLayout.swift
│ ├── ImageGalleryViewCell.swift
│ ├── VideoInfoView.swift
│ ├── ImageGalleryViewDataSource.swift
│ └── ImageGalleryView.swift
├── BottomView
│ ├── ImageStack.swift
│ ├── ButtonPicker.swift
│ ├── BottomContainerView.swift
│ └── StackView.swift
├── LocationManager.swift
├── Helper.swift
├── AssetManager.swift
├── TopView
│ └── TopView.swift
├── Configuration.swift
├── CameraView
│ ├── CameraMan.swift
│ └── CameraView.swift
├── Extensions
│ └── ConstraintsSetup.swift
└── ImagePickerController.swift
├── LICENSE.md
├── .swiftlint.yml
├── README.md
└── CHANGELOG.md
/Images/AUTO@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperoslo/ImagePicker/HEAD/Images/AUTO@3x.png
--------------------------------------------------------------------------------
/Images/OFF@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperoslo/ImagePicker/HEAD/Images/OFF@3x.png
--------------------------------------------------------------------------------
/Images/ON@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperoslo/ImagePicker/HEAD/Images/ON@3x.png
--------------------------------------------------------------------------------
/Images/video@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperoslo/ImagePicker/HEAD/Images/video@3x.png
--------------------------------------------------------------------------------
/Images/focusIcon@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperoslo/ImagePicker/HEAD/Images/focusIcon@3x.png
--------------------------------------------------------------------------------
/Images/cameraIcon@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperoslo/ImagePicker/HEAD/Images/cameraIcon@3x.png
--------------------------------------------------------------------------------
/Resources/ImagePickerIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperoslo/ImagePicker/HEAD/Resources/ImagePickerIcon.png
--------------------------------------------------------------------------------
/Images/selectedImageGallery@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperoslo/ImagePicker/HEAD/Images/selectedImageGallery@3x.png
--------------------------------------------------------------------------------
/Resources/ImagePickerPresentation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperoslo/ImagePicker/HEAD/Resources/ImagePickerPresentation.png
--------------------------------------------------------------------------------
/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/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/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperoslo/ImagePicker/HEAD/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon.png
--------------------------------------------------------------------------------
/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperoslo/ImagePicker/HEAD/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-40.png
--------------------------------------------------------------------------------
/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperoslo/ImagePicker/HEAD/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-72.png
--------------------------------------------------------------------------------
/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperoslo/ImagePicker/HEAD/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-76.png
--------------------------------------------------------------------------------
/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperoslo/ImagePicker/HEAD/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon@2x.png
--------------------------------------------------------------------------------
/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperoslo/ImagePicker/HEAD/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/HEAD/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/HEAD/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/HEAD/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png
--------------------------------------------------------------------------------
/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-72@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperoslo/ImagePicker/HEAD/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-72@2x.png
--------------------------------------------------------------------------------
/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperoslo/ImagePicker/HEAD/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/HEAD/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-83.5@2x.png
--------------------------------------------------------------------------------
/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-Small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperoslo/ImagePicker/HEAD/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-Small.png
--------------------------------------------------------------------------------
/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-Small-50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperoslo/ImagePicker/HEAD/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-Small-50.png
--------------------------------------------------------------------------------
/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperoslo/ImagePicker/HEAD/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/HEAD/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png
--------------------------------------------------------------------------------
/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperoslo/ImagePicker/HEAD/Demo/ImagePickerDemo/ImagePickerDemo/Images.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png
--------------------------------------------------------------------------------
/ImagePicker.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Demo/ImagePickerDemo/ImagePickerDemo.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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | [](https://github.com/Carthage/Carthage)
4 | [](http://cocoadocs.org/docsets/ImagePicker)
5 | [](http://cocoadocs.org/docsets/ImagePicker)
6 |
7 | ## Description
8 |
9 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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)*
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------