├── screenshots └── image.png ├── PhotoSolution ├── flashOn.png ├── arrowIcon.png ├── backIcon.png ├── cameraIcon.png ├── cancelIcon.png ├── editIcon.png ├── flashOff.png ├── saveIcon.png ├── switchIcon.png ├── tickIcon.png ├── settingsToClose.png ├── settingsToOpen.png ├── AlbumCell.swift ├── PhotoNavigationController.swift ├── Album.swift ├── Photo.swift ├── UIImage+Extension.swift ├── PhotoCell.swift ├── ImageViewCell.xib ├── AlbumTableViewController.swift ├── PhotoSolution.swift ├── ImageViewCell.swift ├── ImageEditView.swift ├── ImageEditView.xib ├── AlbumCell.xib ├── ImageEditorViewController.swift ├── PhotoCell.xib ├── PhotoCollectionViewController.swift ├── PhotoStoryboard.storyboard ├── CameraViewController.xib └── CameraViewController.swift ├── PhotoSolutionDemo ├── Assets.xcassets │ ├── Contents.json │ ├── icon.imageset │ │ ├── icon.png │ │ └── Contents.json │ ├── addIcon.imageset │ │ ├── add-1.png │ │ └── Contents.json │ ├── deleteIcon.imageset │ │ ├── delete.png │ │ └── Contents.json │ └── AppIcon.appiconset │ │ ├── ItunesArtwork@2x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ ├── Icon-App-20x20@2x-1.png │ │ ├── Icon-App-29x29@2x-1.png │ │ ├── Icon-App-40x40@2x-1.png │ │ ├── Icon-App-83.5x83.5@2x.png │ │ └── Contents.json ├── PickerCell.swift ├── Info.plist ├── AppDelegate.swift ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── PickerCell.xib └── PostViewController.swift ├── PhotoSolutionDemo.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── project.pbxproj ├── PhotoSolution.podspec ├── LICENSE ├── .gitignore └── README.md /screenshots/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/screenshots/image.png -------------------------------------------------------------------------------- /PhotoSolution/flashOn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolution/flashOn.png -------------------------------------------------------------------------------- /PhotoSolution/arrowIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolution/arrowIcon.png -------------------------------------------------------------------------------- /PhotoSolution/backIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolution/backIcon.png -------------------------------------------------------------------------------- /PhotoSolution/cameraIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolution/cameraIcon.png -------------------------------------------------------------------------------- /PhotoSolution/cancelIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolution/cancelIcon.png -------------------------------------------------------------------------------- /PhotoSolution/editIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolution/editIcon.png -------------------------------------------------------------------------------- /PhotoSolution/flashOff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolution/flashOff.png -------------------------------------------------------------------------------- /PhotoSolution/saveIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolution/saveIcon.png -------------------------------------------------------------------------------- /PhotoSolution/switchIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolution/switchIcon.png -------------------------------------------------------------------------------- /PhotoSolution/tickIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolution/tickIcon.png -------------------------------------------------------------------------------- /PhotoSolution/settingsToClose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolution/settingsToClose.png -------------------------------------------------------------------------------- /PhotoSolution/settingsToOpen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolution/settingsToOpen.png -------------------------------------------------------------------------------- /PhotoSolutionDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /PhotoSolutionDemo/Assets.xcassets/icon.imageset/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolutionDemo/Assets.xcassets/icon.imageset/icon.png -------------------------------------------------------------------------------- /PhotoSolutionDemo/Assets.xcassets/addIcon.imageset/add-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolutionDemo/Assets.xcassets/addIcon.imageset/add-1.png -------------------------------------------------------------------------------- /PhotoSolutionDemo/Assets.xcassets/deleteIcon.imageset/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolutionDemo/Assets.xcassets/deleteIcon.imageset/delete.png -------------------------------------------------------------------------------- /PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png -------------------------------------------------------------------------------- /PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x-1.png -------------------------------------------------------------------------------- /PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-1.png -------------------------------------------------------------------------------- /PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x-1.png -------------------------------------------------------------------------------- /PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mark-Ma-1988/PhotoSolution/HEAD/PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /PhotoSolutionDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PhotoSolutionDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PhotoSolutionDemo/Assets.xcassets/icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PhotoSolutionDemo/Assets.xcassets/addIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "add-1.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PhotoSolutionDemo/Assets.xcassets/deleteIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "delete.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PhotoSolution/AlbumCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlbumCell.swift 3 | // NG POC 4 | // 5 | // Created by MA XINGCHEN on 2/7/18. 6 | // Copyright © 2018 mark. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AlbumCell: UITableViewCell { 12 | 13 | @IBOutlet weak var poster: UIImageView! 14 | @IBOutlet weak var nameLabel: UILabel! 15 | @IBOutlet weak var amountLabel: UILabel! 16 | 17 | private let posterSize = CGFloat(200) 18 | 19 | func configViewWithData(album: Album){ 20 | album.getPosterPhoto(posterSize: posterSize) {image in 21 | self.poster.image = image 22 | } 23 | nameLabel.text = album.getAlbumName() 24 | amountLabel.text = "(\(album.getPhotoCount()))" 25 | } 26 | 27 | override func awakeFromNib() { 28 | super.awakeFromNib() 29 | // Initialization code 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /PhotoSolution.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = "PhotoSolution" 3 | spec.version = "1.5.7" 4 | spec.summary = "PhotoSolution is an all-in-one photo solution for your iOS app, the UI is similar to WeChat" 5 | build_tag = spec.version 6 | spec.homepage = "https://github.com/Mark-Ma-1988/PhotoSolution" 7 | spec.license = 'MIT' 8 | spec.author = { "Mark Ma" => "maxch1988@gmail.com" } 9 | spec.source = { 10 | :git => "https://github.com/Mark-Ma-1988/PhotoSolution.git", 11 | :tag => build_tag.to_s 12 | } 13 | spec.platform = :ios, '9.0' 14 | spec.module_name = 'PhotoSolution' 15 | spec.source_files = 'PhotoSolution/*.{swift}' 16 | spec.swift_version = '4.2' 17 | spec.requires_arc = true 18 | spec.resource_bundles = { 19 | 'PhotoSolution' => ['PhotoSolution/*.{xib,png,storyboard}'] 20 | } 21 | end -------------------------------------------------------------------------------- /PhotoSolutionDemo/PickerCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PickerCell.swift 3 | // PhotoSolutionDemo 4 | // 5 | // Created by MA XINGCHEN on 8/7/18. 6 | // Copyright © 2018 mark. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol PickerCellDelegate { 12 | func deleteClick(_ cell: UICollectionViewCell) 13 | } 14 | 15 | class PickerCell: UICollectionViewCell { 16 | 17 | @IBOutlet weak var deleteIcon: UIImageView! 18 | @IBOutlet weak var imageView: UIImageView! 19 | var delegate: PickerCellDelegate? 20 | 21 | override func awakeFromNib() { 22 | super.awakeFromNib() 23 | imageView.backgroundColor = UIColor.lightGray 24 | let tickGesture=UITapGestureRecognizer(target: self, action: #selector(deleteThisCell(_:))) 25 | tickGesture.numberOfTapsRequired = 1 26 | deleteIcon.isUserInteractionEnabled = true 27 | deleteIcon.addGestureRecognizer(tickGesture) 28 | } 29 | 30 | @objc private func deleteThisCell(_ gesture: UITapGestureRecognizer) { 31 | self.delegate?.deleteClick(self) 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Mark Ma 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PhotoSolution/PhotoNavigationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotoNavigationController.swift 3 | // PhotoSolutionDemo 4 | // 5 | // Created by MA XINGCHEN on 25/7/18. 6 | // Copyright © 2018 mark. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PhotoNavigationController: UINavigationController { 12 | 13 | var albums: [Album]? 14 | var solutionDelegate: PhotoSolutionDelegate? 15 | var maxPhotos: Int! 16 | var customization: PhotoSolutionCustomization! 17 | var podBundle: Bundle! 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | self.navigationBar.barTintColor = customization.navigationBarBackgroundColor 22 | self.navigationBar.tintColor = customization.navigationBarTextColor 23 | let textAttributes = [NSAttributedString.Key.foregroundColor: customization.navigationBarTextColor] 24 | self.navigationBar.titleTextAttributes = textAttributes 25 | switch customization.statusBarColor { 26 | case .black: 27 | self.navigationBar.barStyle = UIBarStyle.default 28 | case .white: 29 | self.navigationBar.barStyle = UIBarStyle.blackOpaque 30 | } 31 | self.performSegue(withIdentifier: "showDefaultPhotos", sender: nil) 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | # Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots/**/*.png 68 | fastlane/test_output 69 | -------------------------------------------------------------------------------- /PhotoSolution/Album.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Album.swift 3 | // NG POC 4 | // 5 | // Created by MA XINGCHEN on 2/7/18. 6 | // Copyright © 2018 mark. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Photos 11 | 12 | class Album{ 13 | 14 | private var collection: PHAssetCollection 15 | private var options:PHFetchOptions 16 | 17 | init(collection: PHAssetCollection) { 18 | self.collection = collection 19 | options = PHFetchOptions() 20 | options.predicate = NSPredicate(format: "mediaType = %d",PHAssetMediaType.image.rawValue) 21 | } 22 | 23 | func getAlbumName() -> String{ 24 | if let name = collection.localizedTitle{ 25 | return name 26 | }else{ 27 | return "???" 28 | } 29 | 30 | } 31 | 32 | func getPhotos() -> [Photo]{ 33 | let assetsFetchResults:PHFetchResult = PHAsset.fetchAssets(in: collection, options: options) 34 | var photoList = [Photo]() 35 | assetsFetchResults.enumerateObjects({ asset, idx, stop in 36 | photoList.append(Photo(asset: asset, index: idx)) 37 | }) 38 | return photoList 39 | } 40 | 41 | func getPhotoCount() -> Int{ 42 | let collectionResult = PHAsset.fetchAssets(in: collection, options: options) 43 | return collectionResult.count 44 | } 45 | 46 | func getPosterPhoto(posterSize: CGFloat,callback: @escaping (UIImage) -> Void) { 47 | let assetsFetchResults:PHFetchResult = PHAsset.fetchAssets(in: collection, options: options) 48 | let firstPhoto = Photo(asset: assetsFetchResults.firstObject!, index: 0) 49 | firstPhoto.getThumbnail{ image in 50 | callback(image) 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /PhotoSolutionDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 4 21 | LSRequiresIPhoneOS 22 | 23 | NSCameraUsageDescription 24 | Photo Solution need to use your camera to take photos 25 | NSPhotoLibraryAddUsageDescription 26 | Photo Solution need to save the photos you take into your local album 27 | NSPhotoLibraryUsageDescription 28 | Photo Solution need to get your photos and then you can browse them 29 | UILaunchStoryboardName 30 | LaunchScreen 31 | UIMainStoryboardFile 32 | Main 33 | UIRequiredDeviceCapabilities 34 | 35 | armv7 36 | 37 | UISupportedInterfaceOrientations 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationLandscapeLeft 41 | UIInterfaceOrientationLandscapeRight 42 | 43 | UISupportedInterfaceOrientations~ipad 44 | 45 | UIInterfaceOrientationPortrait 46 | UIInterfaceOrientationPortraitUpsideDown 47 | UIInterfaceOrientationLandscapeLeft 48 | UIInterfaceOrientationLandscapeRight 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /PhotoSolution/Photo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Photo.swift 3 | // NG POC 4 | // 5 | // Created by MA XINGCHEN on 1/7/18. 6 | // Copyright © 2018 mark. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Photos 11 | 12 | class Photo{ 13 | 14 | private var asset: PHAsset 15 | var selected = false 16 | var selectedOrder: Int = 0 17 | var index: Int! 18 | private let compressedSize = 1000 19 | private let thumbnailSize = 220 20 | 21 | init(asset: PHAsset,index: Int) { 22 | self.asset = asset 23 | self.index = index 24 | } 25 | 26 | func isImage() -> Bool{ 27 | return asset.mediaType == PHAssetMediaType.image 28 | } 29 | 30 | func getOriginalImage(callback: @escaping (UIImage) -> Void) { 31 | let manager = PHImageManager.default() 32 | let option = PHImageRequestOptions() 33 | option.isSynchronous = true 34 | manager.requestImage(for: asset, targetSize:PHImageManagerMaximumSize, contentMode: .aspectFit, options: option) { (originImage, info) in 35 | callback(originImage!) 36 | } 37 | } 38 | 39 | func getCompressedImage(callback: @escaping (UIImage) -> Void) { 40 | let manager = PHImageManager.default() 41 | let option = PHImageRequestOptions() 42 | option.isSynchronous = true 43 | option.resizeMode = .fast 44 | manager.requestImage(for: asset, targetSize: CGSize.init(width: compressedSize, height: compressedSize), contentMode: .aspectFit, options: option) { (originImage, info) in 45 | callback(originImage!) 46 | } 47 | } 48 | 49 | func getThumbnail(callback: @escaping (UIImage) -> Void){ 50 | let manager = PHImageManager.default() 51 | let option = PHImageRequestOptions() 52 | option.isSynchronous = false 53 | option.deliveryMode = .highQualityFormat 54 | manager.requestImage(for: asset, targetSize: CGSize.init(width: thumbnailSize, height: thumbnailSize), contentMode: .aspectFit, options: option) { (thumbnailImage, info) in 55 | callback(thumbnailImage!) 56 | } 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /PhotoSolution/UIImage+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Extension.swift 3 | // PhotoSolutionDemo 4 | // 5 | // Created by MA XINGCHEN on 18/1/19. 6 | // Copyright © 2019 mark. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension UIImage { 13 | 14 | func rotate(radians: Float) -> UIImage? { 15 | var newSize = CGRect(origin: CGPoint.zero, size: self.size).applying(CGAffineTransform(rotationAngle: CGFloat(radians))).size 16 | // Trim off the extremely small float value to prevent core graphics from rounding it up 17 | newSize.width = floor(newSize.width) 18 | newSize.height = floor(newSize.height) 19 | 20 | UIGraphicsBeginImageContextWithOptions(newSize, true, self.scale) 21 | let context = UIGraphicsGetCurrentContext()! 22 | 23 | // Move origin to middle 24 | context.translateBy(x: newSize.width/2, y: newSize.height/2) 25 | // Rotate around middle 26 | context.rotate(by: CGFloat(radians)) 27 | // Draw the image at its center 28 | self.draw(in: CGRect(x: -self.size.width/2, y: -self.size.height/2, width: self.size.width, height: self.size.height)) 29 | 30 | let newImage = UIGraphicsGetImageFromCurrentImageContext() 31 | UIGraphicsEndImageContext() 32 | 33 | return newImage 34 | } 35 | 36 | func rescaleImage(toPX: CGFloat) -> UIImage? { 37 | var size: CGSize = self.size 38 | if size.width <= toPX && size.height <= toPX { 39 | return self 40 | } 41 | let scale: CGFloat = size.width / size.height 42 | if size.width > size.height { 43 | size.width = toPX 44 | size.height = size.width / scale 45 | } else { 46 | size.height = toPX 47 | size.width = size.height * scale 48 | } 49 | return rescaleImage(to: size) 50 | } 51 | 52 | func rescaleImage(to size: CGSize) -> UIImage? { 53 | let rect: CGRect? = CGRect(origin: CGPoint.zero, size: size) 54 | UIGraphicsBeginImageContext((rect?.size)!) 55 | draw(in: rect ?? CGRect.zero) 56 | let resImage: UIImage? = UIGraphicsGetImageFromCurrentImageContext() 57 | UIGraphicsEndImageContext() 58 | return resImage 59 | } 60 | 61 | 62 | 63 | } 64 | -------------------------------------------------------------------------------- /PhotoSolutionDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // PhotoSolutionDemo 4 | // 5 | // Created by MA XINGCHEN on 8/7/18. 6 | // Copyright © 2018 mark. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /PhotoSolution/PhotoCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotoCell.swift 3 | // NG POC 4 | // 5 | // Created by MA XINGCHEN on 2/7/18. 6 | // Copyright © 2018 mark. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol PhotoCellDelegate { 12 | func cellClick(_ cell: UICollectionViewCell) 13 | } 14 | 15 | class PhotoCell: UICollectionViewCell { 16 | 17 | private var photo: Photo? 18 | private var markerColor: UIColor? 19 | var delegate: PhotoCellDelegate? 20 | @IBOutlet weak var tickImage: UIImageView! 21 | @IBOutlet weak var numberLabel: UILabel! 22 | 23 | @IBOutlet weak var coverView: UIView! 24 | @IBOutlet weak var imageView: UIImageView! 25 | @IBOutlet private weak var clickArea: UIView! 26 | 27 | func configViewWithData(photo: Photo){ 28 | self.photo = photo 29 | self.photo!.getThumbnail{ image in 30 | self.imageView.image = image 31 | } 32 | if self.photo!.selected{ 33 | select(number: self.photo!.selectedOrder, animation: numberLabel.isHidden) 34 | }else{ 35 | disSelect() 36 | } 37 | 38 | } 39 | 40 | func select(number: Int, animation: Bool){ 41 | numberLabel.text = "\(number)" 42 | tickImage.isHidden = true 43 | if animation{ 44 | UIView.animate(withDuration: 0.5, animations: { 45 | self.numberLabel.isHidden = false 46 | }) { finished in 47 | 48 | } 49 | }else{ 50 | numberLabel.isHidden = false 51 | } 52 | } 53 | 54 | func disSelect(){ 55 | numberLabel.isHidden = true 56 | tickImage.isHidden = false 57 | } 58 | 59 | override func awakeFromNib() { 60 | super.awakeFromNib() 61 | let tickGesture=UITapGestureRecognizer(target: self, action: #selector(tickThisCell(_:))) 62 | tickGesture.numberOfTapsRequired = 1 63 | clickArea.isUserInteractionEnabled = true 64 | clickArea.addGestureRecognizer(tickGesture) 65 | numberLabel.layer.cornerRadius = numberLabel.frame.size.width/2 66 | numberLabel.layer.masksToBounds = true 67 | if let color = markerColor{ 68 | numberLabel.backgroundColor = color 69 | } 70 | } 71 | 72 | @objc private func tickThisCell(_ gesture: UITapGestureRecognizer) { 73 | self.delegate?.cellClick(self) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /PhotoSolutionDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@2x.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@3x.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-40x40@2x.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@3x.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-60x60@2x.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@3x.png", 49 | "scale" : "3x" 50 | }, 51 | { 52 | "size" : "20x20", 53 | "idiom" : "ipad", 54 | "filename" : "Icon-App-20x20@1x.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@2x-1.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "29x29", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-29x29@1x.png", 67 | "scale" : "1x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@2x-1.png", 73 | "scale" : "2x" 74 | }, 75 | { 76 | "size" : "40x40", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-40x40@1x.png", 79 | "scale" : "1x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@2x-1.png", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "size" : "76x76", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-76x76@1x.png", 91 | "scale" : "1x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@2x.png", 97 | "scale" : "2x" 98 | }, 99 | { 100 | "size" : "83.5x83.5", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-83.5x83.5@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "1024x1024", 107 | "idiom" : "ios-marketing", 108 | "filename" : "ItunesArtwork@2x.png", 109 | "scale" : "1x" 110 | } 111 | ], 112 | "info" : { 113 | "version" : 1, 114 | "author" : "xcode" 115 | } 116 | } -------------------------------------------------------------------------------- /PhotoSolution/ImageViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /PhotoSolution/AlbumTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlbumTableViewController.swift 3 | // PhotoSolutionDemo 4 | // 5 | // Created by MA XINGCHEN on 25/7/18. 6 | // Copyright © 2018 mark. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AlbumTableViewController: UIViewController { 12 | 13 | @IBOutlet weak var albumTableView: UITableView! 14 | private var albums: [Album]! 15 | private let reuseIdentifier = "AlbumCellIdentifier" 16 | var rowHeight: CGFloat! 17 | var photoNavigationController: PhotoNavigationController! 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | photoNavigationController = self.navigationController as? PhotoNavigationController 22 | self.title = photoNavigationController.customization.titleForAlbum 23 | if UIDevice.current.userInterfaceIdiom == .pad{ 24 | rowHeight = 120 25 | }else{ 26 | rowHeight = 70 27 | } 28 | self.albums = photoNavigationController.albums! 29 | albumTableView.register(UINib(nibName: "AlbumCell", bundle: photoNavigationController.podBundle), forCellReuseIdentifier: reuseIdentifier) 30 | albumTableView.tableFooterView = UIView() 31 | albumTableView.separatorColor = UIColor.lightGray 32 | albumTableView.bounces = false 33 | } 34 | 35 | @IBAction func cancelClick(_ sender: UIBarButtonItem) { 36 | photoNavigationController.solutionDelegate?.pickerCancel() 37 | photoNavigationController.dismiss(animated: true, completion: nil) 38 | } 39 | 40 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 41 | if segue.identifier == "showAlbumPhotos" { 42 | if let photoCollectionViewController = segue.destination as? PhotoCollectionViewController { 43 | let selectedIndex = sender as! Int 44 | photoCollectionViewController.selectedAlbumIndex = selectedIndex 45 | photoCollectionViewController.title = albums[selectedIndex].getAlbumName() 46 | } 47 | } 48 | } 49 | 50 | } 51 | 52 | extension AlbumTableViewController: UITableViewDataSource{ 53 | 54 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{ 55 | return albums!.count 56 | } 57 | 58 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat{ 59 | return rowHeight 60 | } 61 | 62 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 63 | let cell=tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as! AlbumCell 64 | cell.configViewWithData(album: albums[indexPath.row]) 65 | return cell 66 | } 67 | 68 | } 69 | 70 | extension AlbumTableViewController: UITableViewDelegate{ 71 | 72 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 73 | self.performSegue(withIdentifier: "showAlbumPhotos", sender: indexPath.row) 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /PhotoSolutionDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /PhotoSolution/PhotoSolution.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotoSolution.swift 3 | // PhotoSolutionDemo 4 | // 5 | // Created by MA XINGCHEN on 25/7/18. 6 | // Copyright © 2018 mark. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @objc 12 | public protocol PhotoSolutionDelegate { 13 | 14 | func returnImages(_ images: [UIImage]) 15 | func pickerCancel() 16 | 17 | } 18 | 19 | @objc 20 | public class PhotoSolutionCustomization: NSObject{ 21 | 22 | @objc public var markerColor: UIColor = UIColor.blue 23 | @objc public var navigationBarBackgroundColor: UIColor = UIColor.darkGray 24 | @objc public var navigationBarTextColor: UIColor = UIColor.white 25 | @objc public var titleForAlbum: String = "Album" 26 | @objc public var alertTextForPhotoAccess: String = "Your App Would Like to Access Your Photos" 27 | @objc public var settingButtonTextForPhotoAccess: String = "Setting" 28 | @objc public var cancelButtonTextForPhotoAccess: String = "Cancel" 29 | @objc public var alertTextForCameraAccess: String = "Your App Would Like to Access the Camera" 30 | @objc public var settingButtonTextForCameraAccess: String = "Setting" 31 | @objc public var cancelButtonTextForCameraAccess: String = "Cancel" 32 | @objc public var returnImageSize: ReturnImageSize = .original 33 | @objc public var statusBarColor: StatusBarColor = .white 34 | 35 | @objc public enum ReturnImageSize: Int { 36 | case compressed 37 | case original 38 | } 39 | 40 | @objc public enum StatusBarColor: Int { 41 | case black 42 | case white 43 | } 44 | 45 | } 46 | 47 | @objc 48 | public class PhotoSolution: NSObject{ 49 | 50 | @objc public var delegate: PhotoSolutionDelegate? 51 | @objc public var customization = PhotoSolutionCustomization() 52 | var podBundle: Bundle! 53 | 54 | public override init(){ 55 | let frameworkBundle = Bundle(for: PhotoSolution.self) 56 | let url = frameworkBundle.resourceURL!.appendingPathComponent("PhotoSolution.bundle") 57 | podBundle = Bundle(url: url) 58 | } 59 | 60 | @objc 61 | public func getPhotoPicker(maxPhotos: Int) -> UIViewController{ 62 | 63 | let storyBoard = UIStoryboard(name: "PhotoStoryboard", bundle: podBundle) 64 | let photoNavigationController: PhotoNavigationController = storyBoard.instantiateViewController(withIdentifier: "PhotoNavigationController") as! PhotoNavigationController 65 | photoNavigationController.podBundle = podBundle 66 | photoNavigationController.solutionDelegate = self.delegate 67 | photoNavigationController.customization = self.customization 68 | photoNavigationController.maxPhotos = maxPhotos 69 | return photoNavigationController 70 | } 71 | 72 | @objc 73 | public func getCamera() -> UIViewController{ 74 | let cameraViewController = CameraViewController(nibName: "CameraViewController", bundle: podBundle) 75 | cameraViewController.customization = self.customization 76 | cameraViewController.solutionDelegate = self.delegate 77 | cameraViewController.podBundle = podBundle 78 | return cameraViewController 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /PhotoSolution/ImageViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageViewCell.swift 3 | // NG POC 4 | // 5 | // Created by MA XINGCHEN on 8/7/18. 6 | // Copyright © 2018 mark. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ImageViewCell: UICollectionViewCell { 12 | 13 | @IBOutlet weak var imageView: UIImageView! 14 | private let maxZoom = CGFloat(3) 15 | 16 | func configViewWithData(photo: Photo){ 17 | self.imageView.transform.a = 1 18 | self.imageView.transform.d = 1 19 | // self.imageView.transform.tx = 0 20 | // self.imageView.transform.ty = 0 21 | DispatchQueue.global().async { 22 | photo.getOriginalImage { image in 23 | DispatchQueue.main.async{ 24 | self.imageView.image = image 25 | } 26 | } 27 | } 28 | } 29 | 30 | override func awakeFromNib() { 31 | super.awakeFromNib() 32 | 33 | let pinchGestureRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(self.pinchView(_:))) 34 | imageView.addGestureRecognizer(pinchGestureRecognizer) 35 | 36 | // let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panView(_:))) 37 | // imageView.addGestureRecognizer(panGestureRecognizer) 38 | 39 | imageView.isUserInteractionEnabled = true 40 | imageView.isMultipleTouchEnabled = true 41 | 42 | } 43 | 44 | // @objc func panView(_ panGestureRecognizer: UIPanGestureRecognizer) { 45 | // let view = panGestureRecognizer.view! 46 | // if view.transform.a > 1 && view.transform.d > 1{ 47 | // if panGestureRecognizer.state == .began || panGestureRecognizer.state == .changed { 48 | // let translation = panGestureRecognizer.translation(in: view.superview) 49 | // view.center = CGPoint(x: view.center.x + translation.x, y: view.center.y + translation.y) 50 | // panGestureRecognizer.setTranslation(CGPoint.zero, in: view.superview) 51 | // } 52 | // } 53 | // } 54 | 55 | @objc func pinchView(_ pinchGestureRecognizer: UIPinchGestureRecognizer) { 56 | let view = pinchGestureRecognizer.view! 57 | if pinchGestureRecognizer.state == .began || pinchGestureRecognizer.state == .changed { 58 | view.transform = view.transform.scaledBy(x: pinchGestureRecognizer.scale, y: pinchGestureRecognizer.scale) 59 | pinchGestureRecognizer.scale = 1 60 | }else if pinchGestureRecognizer.state == .ended{ 61 | if view.transform.a < 1 || view.transform.d < 1{ 62 | UIView.animate(withDuration: 0.3, animations: { 63 | self.imageView.transform.a = 1 64 | self.imageView.transform.d = 1 65 | }) { finished in 66 | } 67 | }else if view.transform.a > maxZoom || view.transform.d > maxZoom{ 68 | UIView.animate(withDuration: 0.5, animations: { 69 | self.imageView.transform.a = self.maxZoom 70 | self.imageView.transform.d = self.maxZoom 71 | }) { finished in 72 | } 73 | } 74 | 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /PhotoSolution/ImageEditView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageEditView.swift 3 | // PhotoSolutionDemo 4 | // 5 | // Created by MA XINGCHEN on 20/12/18. 6 | // Copyright © 2018 mark. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | protocol ImageEditViewDelegate { 13 | func imageCompleted(_ editedImage: UIImage) 14 | } 15 | 16 | class ImageEditView: UIView { 17 | 18 | @IBOutlet weak var imageView: UIImageView! 19 | 20 | @IBOutlet weak var editButton: UIImageView! 21 | @IBOutlet weak var saveButton: UIImageView! 22 | @IBOutlet weak var cancelButton: UIImageView! 23 | 24 | var podBundle: Bundle! 25 | var currentImage: UIImage! 26 | var delegate: ImageEditViewDelegate? 27 | 28 | func setupImage(edittingImage: UIImage, fromCamera: Bool){ 29 | editButton.isHidden = true 30 | currentImage = edittingImage 31 | imageView.image = currentImage 32 | if fromCamera{ 33 | setToolButtonsForCamera() 34 | }else{ 35 | hideToolButtons() 36 | } 37 | } 38 | 39 | func hideToolButtons(){ 40 | editButton.isHidden = true 41 | saveButton.isHidden = true 42 | cancelButton.isHidden = true 43 | } 44 | 45 | func showToolButtons(){ 46 | editButton.isHidden = false 47 | saveButton.isHidden = false 48 | cancelButton.isHidden = false 49 | } 50 | 51 | // override init(frame: CGRect) { 52 | // super.init(frame: frame) 53 | // self.commonInit() 54 | // } 55 | // 56 | // required init?(coder aDecoder: NSCoder) { 57 | // super.init(coder: aDecoder) 58 | // self.commonInit() 59 | // } 60 | 61 | // fileprivate func commonInit() { 62 | // } 63 | 64 | func setToolButtonsForCamera(){ 65 | let editGesture=UITapGestureRecognizer(target: self, action: #selector(editAction(_:))) 66 | editGesture.numberOfTapsRequired = 1 67 | editButton.isUserInteractionEnabled = true 68 | editButton.addGestureRecognizer(editGesture) 69 | 70 | let cancelGesture=UITapGestureRecognizer(target: self, action: #selector(cancelAction(_:))) 71 | cancelGesture.numberOfTapsRequired = 1 72 | cancelButton.isUserInteractionEnabled = true 73 | cancelButton.addGestureRecognizer(cancelGesture) 74 | 75 | let saveGesture=UITapGestureRecognizer(target: self, action: #selector(saveAction(_:))) 76 | saveGesture.numberOfTapsRequired = 1 77 | saveButton.isUserInteractionEnabled = true 78 | saveButton.addGestureRecognizer(saveGesture) 79 | } 80 | 81 | @objc private func editAction(_ gesture: UITapGestureRecognizer) { 82 | 83 | } 84 | 85 | @objc private func cancelAction(_ gesture: UITapGestureRecognizer) { 86 | self.removeFromSuperview() 87 | } 88 | 89 | @objc private func saveAction(_ gesture: UITapGestureRecognizer) { 90 | PHPhotoLibrary.shared().performChanges({ 91 | PHAssetChangeRequest.creationRequestForAsset(from: self.currentImage) 92 | }) { (isSuccess: Bool, error: Error?) in 93 | if isSuccess { 94 | DispatchQueue.main.async{ 95 | self.delegate?.imageCompleted(self.currentImage) 96 | } 97 | } else{ 98 | print("save fail", error!.localizedDescription) 99 | } 100 | } 101 | } 102 | 103 | /* 104 | // Only override draw() if you perform custom drawing. 105 | // An empty implementation adversely affects performance during animation. 106 | override func draw(_ rect: CGRect) { 107 | // Drawing code 108 | } 109 | */ 110 | 111 | } 112 | -------------------------------------------------------------------------------- /PhotoSolutionDemo/PickerCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /PhotoSolutionDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PhotoSolution 2 | PhotoSolution 3 | 4 | 5 | 6 | 7 | 8 | 9 | ## Features 10 | - Customised camera, include basic features like zooming, flashlight, focus, rotate between front and back camera. 11 | - Pick multiple images (you can set maximum amount) from the local photo library 12 | - Support both Objective-C and Swift 13 | - Browse all the albums 14 | - View, expand and edit an image in its original size 15 | - Compress images 16 | - Comtomize some UI properties, like color and title. 17 | - AutoLayout, support both portrait and landscape for all the different screen sizes. 18 | 19 | ## Installation 20 | 21 | **PhotoSolution** is available through [CocoaPods](http://cocoapods.org). To install 22 | it, simply add the following line to your Podfile: 23 | 24 | ```ruby 25 | use_frameworks! 26 | ``` 27 | and then add: 28 | 29 | ```ruby 30 | pod 'PhotoSolution' 31 | ``` 32 | 33 | ## Related Permissions (Your need to add them in Info.plist firstly) 34 | - Privacy - Photo Library Usage Description 35 | - Privacy - Photo Library Additions Usage Description 36 | - Privacy - Camera Usage Description 37 | 38 | ## Basic Usage 39 | 40 | ### Objective-C 41 | 42 | ```objective-c 43 | //import it 44 | @import PhotoSolution; 45 | 46 | //define delegate 47 | @interface YourViewController () 48 | 49 | //initilize 50 | PhotoSolution* photoSolution = [[PhotoSolution alloc] init]; 51 | photoSolution.delegate = self; 52 | 53 | //take photo 54 | [self presentViewController: [photoSolution getCamera] animated:YES completion:nil]; 55 | 56 | //pick photos from local library and define maximum picking amount, like 9 57 | [self presentViewController: [photoSolution getPhotoPickerWithMaxPhotos:9] animated:YES completion:nil]; 58 | 59 | //implement delegate method 60 | -(void)returnImages:(NSArray *)images{ 61 | // deal with the return images 62 | } 63 | 64 | -(void)pickerCancel{ 65 | // when user cancel picking photo 66 | } 67 | ``` 68 | 69 | ### Swift 70 | ```swift 71 | //import it 72 | import PhotoSolution 73 | 74 | //initilize 75 | let photoSolution = PhotoSolution() 76 | photoSolution.delegate = self 77 | 78 | //take photo 79 | self.present(photoSolution.getCamera(), animated: true, completion: nil) 80 | 81 | //pick photos from local library and define maximum picking amount, like 9 82 | self.present(photoSolution.getPhotoPicker(maxPhotos: 9), animated: true, completion: nil) 83 | 84 | //implement delegate method 85 | extension YourViewController: PhotoSolutionDelegate{ 86 | func returnImages(_ images: [UIImage]) { 87 | // deal with the return images 88 | } 89 | 90 | func pickerCancel() { 91 | // when user cancel 92 | } 93 | } 94 | ``` 95 | 96 | ## Customization (optional) 97 | 98 | ### Objective-C 99 | ```objective-c 100 | photoSolution.customization.markerColor = [UIColor colorWithRed:0.14 green:0.72 blue:0.30 alpha:1.0]; 101 | photoSolution.customization.navigationBarBackgroundColor = UIColor.darkGrayColor; 102 | photoSolution.customization.navigationBarTextColor = UIColor.whiteColor; 103 | photoSolution.customization.titleForAlbum = @"Album"; 104 | photoSolution.customization.alertTextForPhotoAccess = @"Your App Would Like to Access Your Photos"; 105 | photoSolution.customization.settingButtonTextForPhotoAccess = @"Setting"; 106 | photoSolution.customization.cancelButtonTextForPhotoAccess = @"Cancel"; 107 | photoSolution.customization.alertTextForCameraAccess = @"Your App Would Like to Access Your Photos"; 108 | photoSolution.customization.settingButtonTextForCameraAccess = @"Setting"; 109 | photoSolution.customization.cancelButtonTextForCameraAccess = @"Cancel"; 110 | photoSolution.customization.returnImageSize = ReturnImageSizeCompressed; 111 | photoSolution.customization.statusBarColor = StatusBarColorWhite; 112 | ``` 113 | 114 | ### Swift 115 | ```swift 116 | photoSolution.customization.markerColor = UIColor(red:0.14, green:0.72, blue:0.30, alpha:1.0) 117 | photoSolution.customization.navigationBarBackgroundColor = UIColor.darkGray 118 | photoSolution.customization.navigationBarTextColor = UIColor.white 119 | photoSolution.customization.titleForAlbum = "Album" 120 | photoSolution.customization.alertTextForPhotoAccess = "Your App Would Like to Access Your Photos" 121 | photoSolution.customization.settingButtonTextForPhotoAccess = "Setting" 122 | photoSolution.customization.cancelButtonTextForPhotoAccess = "Cancel" 123 | photoSolution.customization.alertTextForCameraAccess = "Your App Would Like to Access Your Photos" 124 | photoSolution.customization.settingButtonTextForCameraAccess = "Setting" 125 | photoSolution.customization.cancelButtonTextForCameraAccess = "Cancel" 126 | photoSolution.customization.returnImageSize = .compressed 127 | photoSolution.customization.statusBarColor = .white 128 | ``` 129 | 130 | 131 | ## Author 132 | 133 | [Mark Ma](https://www.linkedin.com/in/xingchen-mark-ma-72a74678/), a software developer focused on mobile application solution, including server side backend development (restful API, database, AWS deployment) and client side app development (iOS && Android). 134 | 135 | ## License 136 | MIT 137 | 138 | -------------------------------------------------------------------------------- /PhotoSolution/ImageEditView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /PhotoSolution/AlbumCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 35 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /PhotoSolution/ImageEditorViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageEditorViewController.swift 3 | // PhotoSolutionDemo 4 | // 5 | // Created by MA XINGCHEN on 25/7/18. 6 | // Copyright © 2018 mark. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ImageEditorViewController: UIViewController { 12 | 13 | @IBOutlet weak var statusCoverView: UIView! 14 | @IBOutlet weak var clickButton: UIBarButtonItem! 15 | @IBOutlet weak var topNavigationBar: UINavigationBar! 16 | private var imageCollectionView: UICollectionView? 17 | private let navigationBarHeight = CGFloat(40) 18 | var currentPhotoList = [Photo]() 19 | var currentIndex: Int? 20 | private let imageCellReuseIdentifier = "ImageViewCell" 21 | var customization: PhotoSolutionCustomization! 22 | var podBundle: Bundle! 23 | 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | self.automaticallyAdjustsScrollViewInsets = false 27 | setupBars() 28 | setupImageCollectionView() 29 | NotificationCenter.default.addObserver(self, selector:#selector(orientation), name: UIDevice.orientationDidChangeNotification, object: nil) 30 | } 31 | 32 | @objc func orientation() { 33 | if imageCollectionView != nil{ 34 | imageCollectionView!.isHidden = true 35 | imageCollectionView!.dataSource = nil 36 | imageCollectionView!.delegate = nil 37 | imageCollectionView = nil 38 | setupImageCollectionView() 39 | } 40 | } 41 | 42 | override var prefersStatusBarHidden: Bool { 43 | return true 44 | } 45 | 46 | private func setupBars(){ 47 | clickButton.tintColor = customization.navigationBarTextColor 48 | topNavigationBar.barTintColor = customization.navigationBarBackgroundColor 49 | } 50 | 51 | private func setupImageCollectionView(){ 52 | let flowLayout = UICollectionViewFlowLayout() 53 | flowLayout.itemSize = self.view.frame.size 54 | flowLayout.scrollDirection = .horizontal 55 | imageCollectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: flowLayout) 56 | self.view.addSubview(imageCollectionView!) 57 | self.view.sendSubviewToBack(imageCollectionView!) 58 | //imageCollectionView!.translatesAutoresizingMaskIntoConstraints = false 59 | // self.view.addConstraint(NSLayoutConstraint(item: imageCollectionView!, attribute: .leading, relatedBy: .equal, toItem: self.view , attribute: .leading, multiplier: 1, constant: 0)) 60 | // self.view.addConstraint(NSLayoutConstraint(item: imageCollectionView!, attribute: .trailing, relatedBy: .equal, toItem: self.view , attribute: .trailing, multiplier: 1, constant: 0)) 61 | // self.view.addConstraint(NSLayoutConstraint(item: imageCollectionView!, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1, constant: 0)) 62 | // self.view.addConstraint(NSLayoutConstraint(item: imageCollectionView!, attribute: .bottom, relatedBy: .equal, toItem: self.view , attribute: .bottom, multiplier: 1, constant: 0)) 63 | 64 | let dataCellNib = UINib(nibName: imageCellReuseIdentifier, bundle: podBundle) 65 | imageCollectionView!.register(dataCellNib, forCellWithReuseIdentifier: imageCellReuseIdentifier) 66 | imageCollectionView!.isScrollEnabled = true 67 | imageCollectionView!.backgroundColor = UIColor.black 68 | imageCollectionView!.delegate = self 69 | imageCollectionView!.dataSource = self 70 | imageCollectionView!.isPagingEnabled = true 71 | imageCollectionView!.reloadData() 72 | //imageCollectionView!.layoutIfNeeded() 73 | imageCollectionView!.scrollToItem(at: IndexPath(item: currentIndex!, section: 0), at: .centeredHorizontally, animated: false) 74 | } 75 | 76 | @IBAction func closeClick(_ sender: UIBarButtonItem) { 77 | self.dismiss(animated: true, completion: nil) 78 | } 79 | 80 | } 81 | 82 | extension ImageEditorViewController: UICollectionViewDataSource{ 83 | 84 | func numberOfSections(in collectionView: UICollectionView) -> Int { 85 | return 1 86 | } 87 | 88 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 89 | return currentPhotoList.count 90 | } 91 | 92 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 93 | return imageCollectionView!.frame.size 94 | } 95 | 96 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 97 | let cell = imageCollectionView!.dequeueReusableCell(withReuseIdentifier: imageCellReuseIdentifier, for: indexPath) as! ImageViewCell 98 | cell.configViewWithData(photo: currentPhotoList[indexPath.row]) 99 | cell.tag = indexPath.row 100 | return cell 101 | } 102 | 103 | } 104 | 105 | extension ImageEditorViewController: UICollectionViewDelegateFlowLayout{ 106 | 107 | func collectionView(_ collectionView: UICollectionView, 108 | layout collectionViewLayout: UICollectionViewLayout, 109 | insetForSectionAt section: Int) -> UIEdgeInsets { 110 | return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) 111 | } 112 | 113 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat{ 114 | return 0 115 | } 116 | 117 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat{ 118 | return 0 119 | } 120 | 121 | } 122 | 123 | extension ImageEditorViewController: UICollectionViewDelegate{ 124 | 125 | func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath){ 126 | } 127 | 128 | func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath){ 129 | topNavigationBar.isHidden = !self.topNavigationBar.isHidden 130 | statusCoverView.isHidden = !self.statusCoverView.isHidden 131 | } 132 | 133 | func scrollViewDidEndDecelerating(_ scrollView: UIScrollView){ 134 | if let lastTag = imageCollectionView?.visibleCells.first?.tag{ 135 | currentIndex = lastTag 136 | } 137 | } 138 | 139 | func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { 140 | } 141 | 142 | func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { 143 | } 144 | 145 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /PhotoSolutionDemo/PostViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // PhotoSolutionDemo 4 | // 5 | // Created by MA XINGCHEN on 8/7/18. 6 | // Copyright © 2018 mark. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PostViewController: UIViewController { 12 | 13 | @IBOutlet weak var pickedPhotoCollectionView: UICollectionView! 14 | private var cellSize: CGFloat! 15 | private let pickerCellReuseIdentifier = "PickerCell" 16 | private let space = CGFloat(8) 17 | var currentImages: [UIImage] = [UIImage]() 18 | private let maxPhotos = 9 19 | let photoSolution = PhotoSolution() 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | configPhotoSolution() 24 | cellSize = (pickedPhotoCollectionView.frame.width - 4 * space) / 3 25 | let dataCellNib = UINib(nibName: pickerCellReuseIdentifier, bundle: nil) 26 | pickedPhotoCollectionView.register(dataCellNib, forCellWithReuseIdentifier: pickerCellReuseIdentifier) 27 | pickedPhotoCollectionView.isScrollEnabled = true 28 | pickedPhotoCollectionView.bounces = false 29 | pickedPhotoCollectionView.reloadData() 30 | } 31 | 32 | func configPhotoSolution(){ 33 | photoSolution.customization.markerColor = UIColor.blue 34 | photoSolution.customization.navigationBarBackgroundColor = UIColor.darkGray 35 | photoSolution.customization.navigationBarTextColor = UIColor.white 36 | photoSolution.customization.titleForAlbum = "Album" 37 | photoSolution.customization.alertTextForPhotoAccess = "Your App Would Like to Access Your Photos" 38 | photoSolution.customization.settingButtonTextForPhotoAccess = "Setting" 39 | photoSolution.customization.cancelButtonTextForPhotoAccess = "Cancel" 40 | photoSolution.customization.alertTextForCameraAccess = "Your App Would Like to Access Your Photos" 41 | photoSolution.customization.settingButtonTextForCameraAccess = "Setting" 42 | photoSolution.customization.cancelButtonTextForCameraAccess = "Cancel" 43 | photoSolution.customization.returnImageSize = .compressed 44 | photoSolution.customization.statusBarColor = .white 45 | photoSolution.delegate = self 46 | } 47 | 48 | func calulateImageFileSize(_ image: UIImage?) { 49 | var data: Data? = image!.pngData() 50 | var dataLength = Double((data?.count ?? 0)) * 1.0 51 | let typeArray = ["bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] 52 | var index: Int = 0 53 | while dataLength > 1024 { 54 | dataLength /= 1024.0 55 | index += 1 56 | } 57 | print("image = \(dataLength) \(typeArray[index]) ") 58 | } 59 | 60 | func getPhotos() { 61 | var alertController: UIAlertController 62 | alertController = UIAlertController(title: nil, message: nil, preferredStyle: .alert) 63 | let takeAction = UIAlertAction(title: "Take a photo", style: .default, handler: { action in 64 | self.present(self.photoSolution.getCamera(), animated: true, completion: nil) 65 | }) 66 | let findAction = UIAlertAction(title: "From my album", style: .default, handler: { action in 67 | let remainPhotos = self.maxPhotos - self.currentImages.count 68 | self.present(self.photoSolution.getPhotoPicker(maxPhotos: remainPhotos), animated: true, completion: nil) 69 | }) 70 | let cancleAction = UIAlertAction(title: "Cancel", style: .cancel, handler: { action in 71 | }) 72 | alertController.addAction(takeAction) 73 | alertController.addAction(findAction) 74 | alertController.addAction(cancleAction) 75 | self.present(alertController, animated: true, completion: nil) 76 | } 77 | 78 | } 79 | 80 | 81 | extension PostViewController: UICollectionViewDataSource{ 82 | 83 | func numberOfSections(in collectionView: UICollectionView) -> Int { 84 | return 1 85 | } 86 | 87 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 88 | if currentImages.count < maxPhotos{ 89 | return currentImages.count + 1 90 | }else{ 91 | return maxPhotos 92 | } 93 | } 94 | 95 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 96 | return CGSize(width: cellSize ,height: cellSize) 97 | } 98 | 99 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 100 | let cell = pickedPhotoCollectionView!.dequeueReusableCell(withReuseIdentifier: pickerCellReuseIdentifier, for: indexPath) as! PickerCell 101 | cell.delegate = self 102 | cell.tag = indexPath.row 103 | if indexPath.row == currentImages.count{ 104 | cell.imageView.image = UIImage(named: "addIcon") 105 | cell.deleteIcon.isHidden = true 106 | }else{ 107 | cell.imageView.image = currentImages[indexPath.row] 108 | cell.deleteIcon.isHidden = false 109 | } 110 | return cell 111 | } 112 | } 113 | 114 | extension PostViewController: UICollectionViewDelegateFlowLayout{ 115 | 116 | func collectionView(_ collectionView: UICollectionView, 117 | layout collectionViewLayout: UICollectionViewLayout, 118 | insetForSectionAt section: Int) -> UIEdgeInsets { 119 | return UIEdgeInsets(top: space, left: space, bottom: space, right: space) 120 | } 121 | 122 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat{ 123 | return space 124 | } 125 | 126 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat{ 127 | return space 128 | } 129 | 130 | } 131 | 132 | extension PostViewController: UICollectionViewDelegate{ 133 | 134 | func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath){ 135 | } 136 | 137 | func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath){ 138 | if indexPath.row == currentImages.count{ 139 | getPhotos() 140 | } 141 | } 142 | } 143 | 144 | extension PostViewController: PickerCellDelegate{ 145 | 146 | func deleteClick(_ cell: UICollectionViewCell) { 147 | currentImages.remove(at: cell.tag) 148 | pickedPhotoCollectionView.reloadData() 149 | } 150 | 151 | } 152 | 153 | extension PostViewController: PhotoSolutionDelegate{ 154 | 155 | func returnImages(_ images: [UIImage]) { 156 | for image in images { 157 | if currentImages.count < maxPhotos{ 158 | currentImages.append(image) 159 | } 160 | } 161 | //calulateImageFileSize(images.first) 162 | pickedPhotoCollectionView.reloadData() 163 | } 164 | 165 | func pickerCancel() { 166 | print("User close it!") 167 | } 168 | 169 | } 170 | -------------------------------------------------------------------------------- /PhotoSolution/PhotoCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 44 | 45 | 46 | 47 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /PhotoSolution/PhotoCollectionViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotoCollectionViewController.swift 3 | // PhotoSolutionDemo 4 | // 5 | // Created by MA XINGCHEN on 25/7/18. 6 | // Copyright © 2018 mark. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | class PhotoCollectionViewController: UIViewController { 13 | 14 | @IBOutlet weak var bottomNavigationBar: UINavigationBar! 15 | @IBOutlet weak var photoCollectionView: UICollectionView! 16 | @IBOutlet weak var doneButton: UIBarButtonItem! 17 | var selectedAlbumIndex: Int? 18 | private var photoNavigationController: PhotoNavigationController! 19 | private var albums: [Album]? 20 | private var maxAmount = 9 21 | private var currentPhotoList = [Photo]() 22 | private var currentSelectedPhotoList = [Photo]() 23 | private let activityIndicator = UIActivityIndicatorView(style:.gray) 24 | private var cellSize: CGFloat! 25 | private let photoCellReuseIdentifier = "PhotoCell" 26 | private let space = CGFloat(2.5) 27 | private var lastShowIndex: IndexPath? 28 | 29 | override func viewDidLoad() { 30 | super.viewDidLoad() 31 | photoNavigationController = self.navigationController as? PhotoNavigationController 32 | maxAmount = photoNavigationController.maxPhotos 33 | bottomNavigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default) 34 | bottomNavigationBar.shadowImage = UIImage() 35 | doneButton.tintColor = photoNavigationController.customization.navigationBarTextColor 36 | self.view.backgroundColor = photoNavigationController.customization.navigationBarBackgroundColor 37 | bottomNavigationBar.barTintColor = photoNavigationController.customization.navigationBarBackgroundColor 38 | 39 | setupCollectionView() 40 | if let albums = photoNavigationController.albums{ 41 | self.albums = albums 42 | showPhotoCollection() 43 | }else{ 44 | getPhotos() 45 | } 46 | NotificationCenter.default.addObserver(self, selector:#selector(orientation), name: UIDevice.orientationDidChangeNotification, object: nil) 47 | } 48 | 49 | @objc func orientation() { 50 | if self.currentPhotoList.count>0{ 51 | if lastShowIndex != nil{ 52 | photoCollectionView.scrollToItem(at: lastShowIndex!, at: .bottom, animated: false) 53 | }else{ 54 | photoCollectionView.scrollToItem(at: IndexPath(item: self.currentPhotoList.count-1, section: 0), at: .bottom, animated: false) 55 | } 56 | } 57 | } 58 | 59 | private func setupCollectionView(){ 60 | if UIDevice.current.userInterfaceIdiom == .pad{ 61 | cellSize = (self.view.frame.width-7*space)/6 62 | }else{ 63 | cellSize = (self.view.frame.width-5*space)/4 64 | } 65 | let dataCellNib = UINib(nibName: photoCellReuseIdentifier, bundle: photoNavigationController.podBundle) 66 | photoCollectionView.register(dataCellNib, forCellWithReuseIdentifier: photoCellReuseIdentifier) 67 | activityIndicator.center = photoCollectionView.center 68 | self.view.addSubview(activityIndicator) 69 | } 70 | 71 | private func getPhotos(){ 72 | activityIndicator.startAnimating() 73 | PHPhotoLibrary.requestAuthorization { status in 74 | switch status { 75 | case .authorized: 76 | let collections = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .any, options: nil) 77 | var maxPhotoAmount = 0 78 | var maxPhotoAmountIndex = 0 79 | self.albums = [Album]() 80 | collections.enumerateObjects { (collection, idx, stop) in 81 | let album = Album(collection: collection) 82 | let photoAmount = album.getPhotoCount() 83 | if photoAmount > 0 84 | { 85 | self.albums!.append(album) 86 | if photoAmount > maxPhotoAmount{ 87 | maxPhotoAmountIndex = self.albums!.count - 1 88 | maxPhotoAmount = photoAmount 89 | } 90 | } 91 | } 92 | self.photoNavigationController.albums = self.albums 93 | self.selectedAlbumIndex = maxPhotoAmountIndex 94 | self.showPhotoCollection() 95 | case .denied, .restricted: 96 | self.goToPhotoAccessSetting() 97 | case .notDetermined: 98 | self.goToPhotoAccessSetting() 99 | @unknown default: 100 | fatalError() 101 | } 102 | } 103 | } 104 | 105 | func goToPhotoAccessSetting(){ 106 | let alert = UIAlertController(title: nil, message: photoNavigationController.customization.alertTextForPhotoAccess, preferredStyle: .alert) 107 | alert.addAction( UIAlertAction(title: photoNavigationController.customization.settingButtonTextForPhotoAccess, style: .cancel, handler: { action in 108 | UIApplication.shared.openURL(NSURL(string:UIApplication.openSettingsURLString)! as URL) 109 | })) 110 | alert.addAction( UIAlertAction(title: photoNavigationController.customization.cancelButtonTextForPhotoAccess, style: .default, handler: { action in 111 | self.photoNavigationController.solutionDelegate?.pickerCancel() 112 | self.photoNavigationController.dismiss(animated: true, completion: nil) 113 | })) 114 | self.present(alert, animated: true, completion: nil) 115 | } 116 | 117 | func showPhotoCollection(){ 118 | self.currentSelectedPhotoList.removeAll() 119 | self.currentPhotoList = self.albums![selectedAlbumIndex!].getPhotos() 120 | DispatchQueue.main.async{ 121 | self.title = self.albums![self.selectedAlbumIndex!].getAlbumName() 122 | self.photoCollectionView.reloadData() 123 | let lastIndexPath = IndexPath(item: self.currentPhotoList.count-1, section: 0) 124 | self.photoCollectionView.scrollToItem(at: lastIndexPath, at: .bottom, animated: false) 125 | self.activityIndicator.stopAnimating() 126 | } 127 | } 128 | 129 | @IBAction func cancelClick(_ sender: UIBarButtonItem) { 130 | photoNavigationController.solutionDelegate?.pickerCancel() 131 | photoNavigationController.dismiss(animated: true, completion: nil) 132 | } 133 | 134 | @IBAction func doneClick(_ sender: UIBarButtonItem) { 135 | var resultImages = [UIImage]() 136 | for photo in currentSelectedPhotoList{ 137 | if photoNavigationController.customization.returnImageSize == .compressed{ 138 | photo.getCompressedImage { image in 139 | resultImages.append(image) 140 | if resultImages.count == self.currentSelectedPhotoList.count{ 141 | self.photoNavigationController.dismiss(animated: false) { 142 | self.photoNavigationController.solutionDelegate?.returnImages(resultImages) 143 | } 144 | } 145 | } 146 | }else{ 147 | photo.getOriginalImage { image in 148 | resultImages.append(image) 149 | if resultImages.count == self.currentSelectedPhotoList.count{ 150 | self.photoNavigationController.dismiss(animated: false) { 151 | self.photoNavigationController.solutionDelegate?.returnImages(resultImages) 152 | } 153 | } 154 | } 155 | } 156 | } 157 | } 158 | 159 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 160 | if segue.identifier == "showPhoto" { 161 | if let imageEditorViewController = segue.destination as? ImageEditorViewController { 162 | let selectedIndex = sender as! Int 163 | imageEditorViewController.currentPhotoList = currentPhotoList 164 | imageEditorViewController.currentIndex = selectedIndex 165 | imageEditorViewController.customization = photoNavigationController.customization 166 | imageEditorViewController.podBundle = photoNavigationController.podBundle 167 | } 168 | } 169 | } 170 | } 171 | 172 | extension PhotoCollectionViewController: UICollectionViewDataSource{ 173 | 174 | func numberOfSections(in collectionView: UICollectionView) -> Int { 175 | return 1 176 | } 177 | 178 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 179 | return currentPhotoList.count 180 | } 181 | 182 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 183 | return CGSize(width: cellSize ,height: cellSize) 184 | } 185 | 186 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 187 | let cell = photoCollectionView.dequeueReusableCell(withReuseIdentifier: photoCellReuseIdentifier, for: indexPath) as! PhotoCell 188 | cell.numberLabel.backgroundColor = photoNavigationController.customization.markerColor 189 | cell.configViewWithData(photo: currentPhotoList[indexPath.row]) 190 | cell.tag = indexPath.row 191 | cell.delegate = self 192 | if currentSelectedPhotoList.count == maxAmount && !currentPhotoList[indexPath.row].selected{ 193 | cell.coverView.isHidden = false 194 | cell.isUserInteractionEnabled = false 195 | }else{ 196 | cell.coverView.isHidden = true 197 | cell.isUserInteractionEnabled = true 198 | } 199 | return cell 200 | } 201 | } 202 | 203 | extension PhotoCollectionViewController: UICollectionViewDelegateFlowLayout{ 204 | 205 | func collectionView(_ collectionView: UICollectionView, 206 | layout collectionViewLayout: UICollectionViewLayout, 207 | insetForSectionAt section: Int) -> UIEdgeInsets { 208 | return UIEdgeInsets(top: space, left: space, bottom: space, right: space) 209 | } 210 | 211 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat{ 212 | return space 213 | } 214 | 215 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat{ 216 | return space 217 | } 218 | 219 | } 220 | 221 | extension PhotoCollectionViewController: UICollectionViewDelegate{ 222 | 223 | func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath){ 224 | } 225 | 226 | func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath){ 227 | self.performSegue(withIdentifier: "showPhoto", sender: indexPath.row) 228 | } 229 | 230 | func scrollViewDidEndDecelerating(_ scrollView: UIScrollView){ 231 | if let lastTag = photoCollectionView.visibleCells.first?.tag{ 232 | lastShowIndex = IndexPath(item: lastTag, section: 0) 233 | } 234 | } 235 | 236 | } 237 | 238 | extension PhotoCollectionViewController: PhotoCellDelegate{ 239 | 240 | func cellClick(_ cell: UICollectionViewCell) { 241 | let clickedPhoto = currentPhotoList[cell.tag] 242 | if clickedPhoto.selected{ 243 | clickedPhoto.selected = false 244 | 245 | currentSelectedPhotoList.remove(at: clickedPhoto.selectedOrder - 1) 246 | if clickedPhoto.selectedOrder < currentSelectedPhotoList.count + 1{ 247 | for index in clickedPhoto.selectedOrder-1...currentSelectedPhotoList.count-1 { 248 | currentSelectedPhotoList[index].selectedOrder = index + 1 249 | } 250 | } 251 | if currentSelectedPhotoList.count == maxAmount - 1{ 252 | photoCollectionView.reloadData() 253 | }else{ 254 | let indexPaths = currentSelectedPhotoList.map ({ element -> IndexPath in 255 | return IndexPath(item: element.index, section: 0) 256 | }) 257 | photoCollectionView.reloadItems(at: [IndexPath(item: cell.tag, section: 0)]) 258 | photoCollectionView.reloadItems(at: indexPaths) 259 | } 260 | }else if currentSelectedPhotoList.count < maxAmount{ 261 | currentSelectedPhotoList.append(clickedPhoto) 262 | clickedPhoto.selected = true 263 | clickedPhoto.selectedOrder = currentSelectedPhotoList.count 264 | if currentSelectedPhotoList.count < maxAmount{ 265 | photoCollectionView.reloadItems(at: [IndexPath(item: cell.tag, section: 0)]) 266 | }else{ 267 | photoCollectionView.reloadData() 268 | } 269 | }else{ 270 | print("send alert") 271 | } 272 | if currentSelectedPhotoList.count > 0{ 273 | doneButton.isEnabled = true 274 | }else{ 275 | doneButton.isEnabled = false 276 | } 277 | } 278 | 279 | } 280 | -------------------------------------------------------------------------------- /PhotoSolution/PhotoStoryboard.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | -------------------------------------------------------------------------------- /PhotoSolution/CameraViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 86 | 92 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | -------------------------------------------------------------------------------- /PhotoSolution/CameraViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CameraViewController.swift 3 | // PhotoSolutionDemo 4 | // 5 | // Created by MA XINGCHEN on 13/12/18. 6 | // Copyright © 2018 mark. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | import Photos 12 | 13 | class CameraViewController: UIViewController { 14 | 15 | var solutionDelegate:PhotoSolutionDelegate? 16 | var customization: PhotoSolutionCustomization! 17 | var podBundle: Bundle! 18 | var inCameraView: Bool! 19 | var imageEditView: ImageEditView! 20 | var lastFaceFocusPoint: CGPoint! 21 | var cameraArea: UIView! 22 | 23 | @IBOutlet weak var settingsView: UIView! 24 | @IBOutlet weak var rotateCameraButton: UIImageView! 25 | @IBOutlet weak var cancelCameraButton: UIImageView! 26 | @IBOutlet weak var takePhotoButton: UIImageView! 27 | @IBOutlet weak var flashLightButton: UIImageView! 28 | @IBOutlet weak var settingButton: UIImageView! 29 | 30 | @IBOutlet weak var redSlider: UISlider! 31 | @IBOutlet weak var greenSlider: UISlider! 32 | @IBOutlet weak var blueSlider: UISlider! 33 | @IBOutlet weak var autoSwitch: UISwitch! 34 | @IBOutlet weak var durationSlider: UISlider! 35 | @IBOutlet weak var isoSlider: UISlider! 36 | 37 | var captureSession: AVCaptureSession! 38 | var currentCaptureDevice: AVCaptureDevice! 39 | var frontCamera: AVCaptureDevice! 40 | var backCamera: AVCaptureDevice! 41 | var stillImageOutput: AVCaptureStillImageOutput! 42 | var previewLayer: AVCaptureVideoPreviewLayer! 43 | var timer: Timer! 44 | 45 | let focusView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) 46 | 47 | override var prefersStatusBarHidden: Bool { 48 | return true 49 | } 50 | 51 | override open var shouldAutorotate: Bool { 52 | return false 53 | } 54 | 55 | override func viewDidLoad() { 56 | super.viewDidLoad() 57 | inCameraView = true 58 | settingsView.isHidden = true 59 | setupUI() 60 | setupSettingsRange() 61 | setupFrontAndBackCamera() 62 | setupSessionAndOutput() 63 | AVCaptureDevice.requestAccess(for: AVMediaType.video) { response in 64 | if response { 65 | PHPhotoLibrary.requestAuthorization { status in 66 | switch status { 67 | case .authorized: 68 | self.setupInput(isBackCamera: true) 69 | self.setFaceDetection() 70 | case .denied, .restricted: 71 | self.goToPhotoAccessSetting() 72 | case .notDetermined: 73 | self.goToPhotoAccessSetting() 74 | @unknown default: 75 | fatalError() 76 | } 77 | } 78 | } else { 79 | self.goToCameraAccessSetting() 80 | } 81 | } 82 | NotificationCenter.default.addObserver(self, selector:#selector(didChangeOrientation), name: UIDevice.orientationDidChangeNotification, object: nil) 83 | } 84 | 85 | override func viewDidAppear(_ animated: Bool) { 86 | super.viewDidAppear(animated) 87 | if previewLayer == nil{ 88 | setupPreviewLayer() 89 | } 90 | if (captureSession.isRunning == false) { 91 | captureSession.startRunning() 92 | } 93 | timer = Timer.scheduledTimer(timeInterval: Double(0.7), target: self, selector: #selector(getLatestCameraStatus), userInfo: nil, repeats: true) 94 | } 95 | 96 | override func viewDidDisappear(_ animated: Bool) { 97 | super.viewDidDisappear(animated) 98 | if (captureSession.isRunning == true) { 99 | captureSession.stopRunning() 100 | } 101 | timer.invalidate() 102 | } 103 | 104 | private func setFaceDetection(){ 105 | let metadataOutput = AVCaptureMetadataOutput() 106 | metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main) 107 | 108 | if captureSession.canAddOutput(metadataOutput) { 109 | captureSession.addOutput(metadataOutput) 110 | } 111 | metadataOutput.metadataObjectTypes = [AVMetadataObject.ObjectType.face] 112 | } 113 | 114 | @objc private func getLatestCameraStatus(timer: Timer) -> Void{ 115 | if !settingsView.isHidden && autoSwitch.isOn{ 116 | initSlidersValue() 117 | } 118 | } 119 | 120 | func restoreCurrentCamera(){ 121 | autoSwitch.setOn(true, animated: true) 122 | isoSlider.isEnabled = false 123 | durationSlider.isEnabled = false 124 | redSlider.isEnabled = false 125 | greenSlider.isEnabled = false 126 | blueSlider.isEnabled = false 127 | do{ 128 | try currentCaptureDevice.lockForConfiguration() 129 | currentCaptureDevice.exposureMode = .continuousAutoExposure 130 | if currentCaptureDevice.isWhiteBalanceModeSupported(.continuousAutoWhiteBalance){ 131 | currentCaptureDevice.whiteBalanceMode = .continuousAutoWhiteBalance 132 | } 133 | if currentCaptureDevice.isFocusModeSupported(.continuousAutoFocus){ 134 | currentCaptureDevice.focusMode = .continuousAutoFocus 135 | } 136 | currentCaptureDevice.unlockForConfiguration() 137 | initSlidersValue() 138 | }catch { 139 | print("Error in auto exposure") 140 | } 141 | } 142 | 143 | func initSlidersValue(){ 144 | let currentISO = currentCaptureDevice.iso 145 | let currentDuration = currentCaptureDevice.exposureDuration.seconds 146 | let minISO = currentCaptureDevice.activeFormat.minISO 147 | let maxISO = currentCaptureDevice.activeFormat.maxISO 148 | isoSlider.value = (currentISO - minISO)/(maxISO - minISO) 149 | durationSlider.value = Float(currentDuration) 150 | 151 | let minWhiteBalanceGain = Float(1.0) 152 | let maxWhiteBalanceGain = currentCaptureDevice.maxWhiteBalanceGain 153 | redSlider.value = (currentCaptureDevice.deviceWhiteBalanceGains.redGain - minWhiteBalanceGain)/(maxWhiteBalanceGain - minWhiteBalanceGain) 154 | greenSlider.value = (currentCaptureDevice.deviceWhiteBalanceGains.greenGain - minWhiteBalanceGain)/(maxWhiteBalanceGain - minWhiteBalanceGain) 155 | blueSlider.value = (currentCaptureDevice.deviceWhiteBalanceGains.blueGain - minWhiteBalanceGain)/(maxWhiteBalanceGain - minWhiteBalanceGain) 156 | } 157 | 158 | @IBAction func autoSwitch(_ sender: UISwitch) { 159 | if autoSwitch.isOn{ 160 | restoreCurrentCamera() 161 | }else{ 162 | isoSlider.isEnabled = true 163 | durationSlider.isEnabled = true 164 | redSlider.isEnabled = true 165 | greenSlider.isEnabled = true 166 | blueSlider.isEnabled = true 167 | } 168 | } 169 | 170 | func setupSettingsRange(){ 171 | isoSlider.minimumValue = 0.01 172 | isoSlider.maximumValue = 0.99 173 | durationSlider.minimumValue = 0.0001 174 | durationSlider.maximumValue = 0.1999 175 | } 176 | 177 | func customExposure(){ 178 | do{ 179 | try currentCaptureDevice.lockForConfiguration() 180 | let minISO = currentCaptureDevice.activeFormat.minISO 181 | let maxISO = currentCaptureDevice.activeFormat.maxISO 182 | let clampedISO = isoSlider.value * (maxISO - minISO) + minISO 183 | let sTime = CMTime(seconds: Double(durationSlider.value), preferredTimescale: 1000000) 184 | currentCaptureDevice.setExposureModeCustom(duration: sTime, iso: clampedISO, completionHandler: { (time) -> Void in 185 | //Todo 186 | }) 187 | currentCaptureDevice.unlockForConfiguration() 188 | }catch { 189 | print("Error in customExposure") 190 | } 191 | } 192 | 193 | func customWhiteBalance(){ 194 | do{ 195 | try currentCaptureDevice.lockForConfiguration() 196 | let minWhiteBalanceGain = Float(1.0) 197 | let maxWhiteBalanceGain = currentCaptureDevice.maxWhiteBalanceGain 198 | let redValue = redSlider.value * (maxWhiteBalanceGain - minWhiteBalanceGain) + minWhiteBalanceGain 199 | let greenValue = greenSlider.value * (maxWhiteBalanceGain - minWhiteBalanceGain) + minWhiteBalanceGain 200 | let blueValue = blueSlider.value * (maxWhiteBalanceGain - minWhiteBalanceGain) + minWhiteBalanceGain 201 | let whiteBalanceGains = AVCaptureDevice.WhiteBalanceGains(redGain: redValue, greenGain: greenValue, blueGain: blueValue) 202 | currentCaptureDevice.setWhiteBalanceModeLocked(with: whiteBalanceGains) { (CMTime) in 203 | //Todo 204 | } 205 | currentCaptureDevice.unlockForConfiguration() 206 | }catch { 207 | print("Error in whiteBalance") 208 | } 209 | } 210 | 211 | @IBAction func durationSlide(_ sender: UISlider) { 212 | customExposure() 213 | } 214 | 215 | @IBAction func ISOSlide(_ sender: UISlider) { 216 | customExposure() 217 | } 218 | 219 | @IBAction func redSlide(_ sender: UISlider) { 220 | customWhiteBalance() 221 | } 222 | 223 | @IBAction func greenSlide(_ sender: UISlider) { 224 | customWhiteBalance() 225 | } 226 | 227 | @IBAction func blueSlide(_ sender: UISlider) { 228 | customWhiteBalance() 229 | } 230 | 231 | 232 | func setupFrontAndBackCamera(){ 233 | if let devices = AVCaptureDevice.devices(for: AVMediaType.video) as [AVCaptureDevice]? { 234 | for device in devices { 235 | if(device.position == AVCaptureDevice.Position.back) { 236 | backCamera = device 237 | }else if(device.position == AVCaptureDevice.Position.front){ 238 | frontCamera = device 239 | } 240 | } 241 | currentCaptureDevice = backCamera 242 | } 243 | } 244 | 245 | func setupSessionAndOutput(){ 246 | captureSession = AVCaptureSession() 247 | captureSession.sessionPreset = AVCaptureSession.Preset.high 248 | stillImageOutput = AVCaptureStillImageOutput() 249 | //stillImageOutput.outputSettings = [AVVideoCodecKey: AVVideoCodecJPEG] 250 | if captureSession.canAddOutput(stillImageOutput) { 251 | captureSession.addOutput(stillImageOutput) 252 | }else{ 253 | //Todo 254 | } 255 | if let connection = self.stillImageOutput.connection(with: AVMediaType.video) { 256 | if (self.currentCaptureDevice?.activeFormat.isVideoStabilizationModeSupported(.auto))! { 257 | connection.preferredVideoStabilizationMode = .auto 258 | } 259 | } 260 | } 261 | 262 | func setupInput(isBackCamera: Bool){ 263 | if(previewLayer != nil){ 264 | let animation: CATransition = CATransition() 265 | animation.duration = 0.5 266 | animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) 267 | animation.type = CATransitionType(rawValue: "oglFlip") 268 | if(isBackCamera){ 269 | animation.subtype = CATransitionSubtype.fromLeft 270 | }else{ 271 | animation.subtype = CATransitionSubtype.fromRight 272 | } 273 | previewLayer.add(animation, forKey: nil) 274 | } 275 | do { 276 | self.captureSession.beginConfiguration() 277 | if let inputs = captureSession.inputs as? [AVCaptureDeviceInput] { 278 | for input in inputs { 279 | captureSession.removeInput(input) 280 | } 281 | } 282 | if(isBackCamera){ 283 | currentCaptureDevice = backCamera 284 | }else{ 285 | currentCaptureDevice = frontCamera 286 | } 287 | try self.captureSession.addInput(AVCaptureDeviceInput(device: self.currentCaptureDevice)) 288 | self.captureSession.commitConfiguration() 289 | DispatchQueue.main.async{ 290 | if self.currentCaptureDevice.isFlashModeSupported(.auto){ 291 | self.flashLightButton.isHidden = false 292 | self.setupFlashMode(isOn: false) 293 | }else{ 294 | self.flashLightButton.isHidden = true 295 | } 296 | self.restoreCurrentCamera() 297 | } 298 | } catch { 299 | print("Error") 300 | } 301 | } 302 | 303 | func setupUI(){ 304 | rotateCameraButton.image = UIImage(named: "switchIcon", in: self.podBundle, compatibleWith: nil) 305 | takePhotoButton.image = UIImage(named: "cameraIcon", in: self.podBundle, compatibleWith: nil) 306 | cancelCameraButton.image = UIImage(named: "cancelIcon", in: self.podBundle, compatibleWith: nil) 307 | flashLightButton.image = UIImage(named: "flashOff", in: self.podBundle, compatibleWith: nil) 308 | settingButton.image = UIImage(named: "settingsToOpen", in: self.podBundle, compatibleWith: nil) 309 | 310 | settingsView.layer.masksToBounds = true 311 | settingsView.layer.cornerRadius = 15 312 | settingsView.layer.borderWidth = 2 313 | settingsView.layer.borderColor = UIColor.white.cgColor 314 | 315 | let tapRotateCameraButtonRecognizer = UITapGestureRecognizer(target: self, action: #selector(rotateCameraButtonTapped(tapGestureRecognizer:))) 316 | rotateCameraButton.isUserInteractionEnabled = true 317 | rotateCameraButton.addGestureRecognizer(tapRotateCameraButtonRecognizer) 318 | 319 | let tapCancelCameraButtonRecognizer = UITapGestureRecognizer(target: self, action: #selector(cancelCameraButtonTapped(tapGestureRecognizer:))) 320 | cancelCameraButton.isUserInteractionEnabled = true 321 | cancelCameraButton.addGestureRecognizer(tapCancelCameraButtonRecognizer) 322 | 323 | let tapTakePhotoButtonRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapTakePhotoButtonTapped(tapGestureRecognizer:))) 324 | takePhotoButton.isUserInteractionEnabled = true 325 | takePhotoButton.addGestureRecognizer(tapTakePhotoButtonRecognizer) 326 | 327 | let tapFlashLightButtonRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapFlashLightButtonTapped(tapGestureRecognizer:))) 328 | flashLightButton.isUserInteractionEnabled = true 329 | flashLightButton.addGestureRecognizer(tapFlashLightButtonRecognizer) 330 | 331 | let tapSettingButtonRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapSettingButtonTapped(tapGestureRecognizer:))) 332 | settingButton.isUserInteractionEnabled = true 333 | settingButton.addGestureRecognizer(tapSettingButtonRecognizer) 334 | 335 | let tapSettingsViewRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapSettingsViewTapped(tapGestureRecognizer:))) 336 | settingsView.isUserInteractionEnabled = true 337 | settingsView.addGestureRecognizer(tapSettingsViewRecognizer) 338 | 339 | imageEditView = UINib(nibName: "ImageEditView", bundle: self.podBundle).instantiate(withOwner: nil, options: nil)[0] as? ImageEditView 340 | } 341 | 342 | @objc func tapSettingButtonTapped(tapGestureRecognizer: UITapGestureRecognizer){ 343 | if !settingsView.isHidden{ 344 | settingsView.isHidden = true 345 | settingButton.image = UIImage(named: "settingsToOpen", in: self.podBundle, compatibleWith: nil) 346 | }else{ 347 | settingsView.isHidden = false 348 | settingButton.image = UIImage(named: "settingsToClose", in: self.podBundle, compatibleWith: nil) 349 | initSlidersValue() 350 | } 351 | } 352 | 353 | @objc func rotateCameraButtonTapped(tapGestureRecognizer: UITapGestureRecognizer){ 354 | if(currentCaptureDevice == frontCamera){ 355 | setupInput(isBackCamera: true) 356 | }else{ 357 | setupInput(isBackCamera: false) 358 | } 359 | } 360 | 361 | @objc func cancelCameraButtonTapped(tapGestureRecognizer: UITapGestureRecognizer){ 362 | self.solutionDelegate?.pickerCancel() 363 | self.dismiss(animated: true, completion: nil) 364 | } 365 | 366 | @objc func tapSettingsViewTapped(tapGestureRecognizer: UITapGestureRecognizer){ 367 | let point: CGPoint = tapGestureRecognizer.location(in: cameraArea) 368 | focus(at: point) 369 | } 370 | 371 | func imageRotatedByDegrees(oldImage: UIImage, deg degrees: CGFloat) -> UIImage { 372 | //Calculate the size of the rotated view's containing box for our drawing space 373 | let rotatedViewBox: UIView = UIView(frame: CGRect(x: 0, y: 0, width: oldImage.size.width, height: oldImage.size.height)) 374 | let t: CGAffineTransform = CGAffineTransform(rotationAngle: degrees * CGFloat.pi / 180) 375 | rotatedViewBox.transform = t 376 | let rotatedSize: CGSize = rotatedViewBox.frame.size 377 | //Create the bitmap context 378 | UIGraphicsBeginImageContext(rotatedSize) 379 | let bitmap: CGContext = UIGraphicsGetCurrentContext()! 380 | //Move the origin to the middle of the image so we will rotate and scale around the center. 381 | bitmap.translateBy(x: rotatedSize.width / 2, y: rotatedSize.height / 2) 382 | //Rotate the image context 383 | bitmap.rotate(by: (degrees * CGFloat.pi / 180)) 384 | //Now, draw the rotated/scaled image into the context 385 | bitmap.scaleBy(x: 1.0, y: -1.0) 386 | bitmap.draw(oldImage.cgImage!, in: CGRect(x: -oldImage.size.width / 2, y: -oldImage.size.height / 2, width: oldImage.size.width, height: oldImage.size.height)) 387 | let newImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()! 388 | UIGraphicsEndImageContext() 389 | return newImage 390 | } 391 | 392 | @objc func tapTakePhotoButtonTapped(tapGestureRecognizer: UITapGestureRecognizer){ 393 | self.stillImageOutput.captureStillImageAsynchronously(from: self.stillImageOutput.connection(with: AVMediaType.video)!) { (buffer, error) in 394 | if buffer == nil { 395 | return 396 | }else{ 397 | let originalImageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(buffer!) 398 | let originalImage = UIImage(data: originalImageData!) 399 | 400 | //compress or not 401 | var image = originalImage?.rescaleImage(toPX: 1500) 402 | 403 | if UIDevice.current.orientation == .landscapeLeft{ 404 | image = image!.rotate(radians: -.pi/2) 405 | }else if UIDevice.current.orientation == .landscapeRight{ 406 | image = image!.rotate(radians: .pi/2) 407 | } 408 | 409 | self.imageEditView.frame = self.cameraArea.frame 410 | self.imageEditView.setupImage(edittingImage: image!, fromCamera: true) 411 | self.imageEditView.delegate = self 412 | self.view.addSubview(self.imageEditView) 413 | self.inCameraView = false 414 | } 415 | } 416 | } 417 | 418 | @objc func tapFlashLightButtonTapped(tapGestureRecognizer: UITapGestureRecognizer){ 419 | setupFlashMode(isOn: currentCaptureDevice.flashMode != .on) 420 | } 421 | 422 | func setupFlashMode(isOn: Bool){ 423 | do { 424 | try currentCaptureDevice.lockForConfiguration() 425 | if isOn{ 426 | currentCaptureDevice.flashMode = .auto 427 | }else{ 428 | currentCaptureDevice.flashMode = .off 429 | } 430 | currentCaptureDevice.unlockForConfiguration() 431 | DispatchQueue.main.async{ 432 | if isOn{ 433 | self.flashLightButton.image = UIImage(named: "flashOn", in: self.podBundle, compatibleWith: nil) 434 | }else{ 435 | self.flashLightButton.image = UIImage(named: "flashOff", in: self.podBundle, compatibleWith: nil) 436 | 437 | } 438 | } 439 | } catch { 440 | print("Error in FlashLight") 441 | } 442 | } 443 | 444 | func setupPreviewLayer() { 445 | var topSafeAreaPadding: CGFloat = 0 446 | var bottomSafeAreaPadding: CGFloat = 0 447 | if #available(iOS 11.0, *) { 448 | topSafeAreaPadding = UIApplication.shared.keyWindow!.safeAreaInsets.top 449 | bottomSafeAreaPadding = UIApplication.shared.keyWindow!.safeAreaInsets.bottom 450 | } 451 | cameraArea = UIView.init(frame:CGRect.init(x: 0, y: topSafeAreaPadding, width: self.view.frame.width, height: self.view.frame.height-topSafeAreaPadding-bottomSafeAreaPadding)) 452 | self.view.addSubview(cameraArea) 453 | self.view.sendSubviewToBack(cameraArea) 454 | self.previewLayer = AVCaptureVideoPreviewLayer(session: self.captureSession) 455 | self.previewLayer?.bounds = self.cameraArea.bounds 456 | self.previewLayer?.position = CGPoint(x: self.cameraArea.bounds.midX, y: self.cameraArea.bounds.midY) 457 | self.previewLayer?.videoGravity = AVLayerVideoGravity.resizeAspect 458 | self.cameraArea.layer.addSublayer(self.previewLayer!) 459 | 460 | let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.focusGesture(_:))) 461 | self.cameraArea.addGestureRecognizer(tapGesture) 462 | focus(at: CGPoint(x: self.cameraArea.bounds.midX, y: self.cameraArea.bounds.midY)) 463 | 464 | let pinchGestureRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(self.pinchView(_:))) 465 | self.cameraArea.addGestureRecognizer(pinchGestureRecognizer) 466 | self.cameraArea.isUserInteractionEnabled = true 467 | self.cameraArea.isMultipleTouchEnabled = true 468 | 469 | focusView.layer.borderWidth = 1.0 470 | focusView.layer.borderColor = UIColor.yellow.cgColor 471 | focusView.backgroundColor = UIColor.clear 472 | self.cameraArea.addSubview(focusView) 473 | focusView.isHidden = true 474 | } 475 | 476 | @objc func pinchView(_ pinchGestureRecognizer: UIPinchGestureRecognizer) { 477 | let pinchVelocityDividerFactor: CGFloat = 20 478 | if pinchGestureRecognizer.state == .changed { 479 | do{ 480 | try currentCaptureDevice.lockForConfiguration() 481 | let desiredZoomFactor: CGFloat = currentCaptureDevice.videoZoomFactor + CGFloat(atan2f(Float(pinchGestureRecognizer.velocity), Float(pinchVelocityDividerFactor))) 482 | currentCaptureDevice.videoZoomFactor = max(1.0, min(desiredZoomFactor, currentCaptureDevice.activeFormat.videoMaxZoomFactor)) 483 | currentCaptureDevice.unlockForConfiguration() 484 | }catch { 485 | print("Error in Focus") 486 | } 487 | } 488 | } 489 | 490 | @objc func focusGesture(_ gesture: UITapGestureRecognizer) { 491 | let point: CGPoint = gesture.location(in: cameraArea) 492 | focus(at: point) 493 | } 494 | 495 | func focus(at point: CGPoint) { 496 | let size: CGSize = cameraArea.bounds.size 497 | let focusPoint = CGPoint(x: point.y / size.height, y: 1 - point.x / size.width) 498 | do { 499 | try currentCaptureDevice.lockForConfiguration() 500 | if currentCaptureDevice.isFocusModeSupported(.autoFocus) { 501 | currentCaptureDevice.focusPointOfInterest = focusPoint 502 | currentCaptureDevice.focusMode = .continuousAutoFocus 503 | } 504 | if currentCaptureDevice.isExposureModeSupported(.autoExpose) { 505 | currentCaptureDevice.exposurePointOfInterest = focusPoint 506 | currentCaptureDevice.exposureMode = .continuousAutoExposure 507 | } 508 | currentCaptureDevice.unlockForConfiguration() 509 | DispatchQueue.main.async{ 510 | self.focusView.center = point 511 | self.focusView.isHidden = false 512 | UIView.animate(withDuration: 0.3, animations: { 513 | self.focusView.transform = CGAffineTransform(scaleX: 1.5, y: 1.5) 514 | }) { finished in 515 | UIView.animate(withDuration: 0.3, animations: { 516 | self.focusView.transform = CGAffineTransform.identity 517 | }) { finished in 518 | self.focusView.isHidden = true 519 | } 520 | } 521 | } 522 | } catch { 523 | print("Error in Focus") 524 | } 525 | } 526 | 527 | func goToPhotoAccessSetting(){ 528 | let alert = UIAlertController(title: nil, message: customization.alertTextForPhotoAccess, preferredStyle: .alert) 529 | alert.addAction( UIAlertAction(title: customization.settingButtonTextForPhotoAccess, style: .cancel, handler: { action in 530 | UIApplication.shared.openURL(NSURL(string:UIApplication.openSettingsURLString)! as URL) 531 | })) 532 | alert.addAction( UIAlertAction(title: customization.cancelButtonTextForPhotoAccess, style: .default, handler: { action in 533 | self.solutionDelegate?.pickerCancel() 534 | self.dismiss(animated: true, completion: nil) 535 | })) 536 | self.present(alert, animated: true, completion: nil) 537 | } 538 | 539 | func goToCameraAccessSetting(){ 540 | let alert = UIAlertController(title: nil, message: self.customization.alertTextForCameraAccess, preferredStyle: .alert) 541 | alert.addAction( UIAlertAction(title: self.customization.settingButtonTextForCameraAccess, style: .cancel, handler: { action in 542 | UIApplication.shared.openURL(NSURL(string:UIApplication.openSettingsURLString)! as URL) 543 | })) 544 | alert.addAction( UIAlertAction(title: self.customization.cancelButtonTextForCameraAccess, style: .default, handler: { action in 545 | self.solutionDelegate?.pickerCancel() 546 | self.dismiss(animated: true, completion: nil) 547 | })) 548 | self.present(alert, animated: true, completion: nil) 549 | } 550 | 551 | @objc func didChangeOrientation() { 552 | UIView.animate(withDuration: 0.3, animations: { 553 | if UIDevice.current.orientation == .landscapeLeft{ 554 | self.rotateCameraButton.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi / 2)) 555 | self.flashLightButton.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi / 2)) 556 | }else if UIDevice.current.orientation == .landscapeRight{ 557 | self.rotateCameraButton.transform = CGAffineTransform(rotationAngle: CGFloat(-Double.pi / 2)) 558 | self.flashLightButton.transform = CGAffineTransform(rotationAngle: CGFloat(-Double.pi / 2)) 559 | }else{ 560 | self.rotateCameraButton.transform = CGAffineTransform.identity 561 | self.flashLightButton.transform = CGAffineTransform.identity 562 | } 563 | }) 564 | } 565 | 566 | func distanceBetween(_ p1: CGPoint, and p2: CGPoint) -> CGFloat { 567 | return sqrt(pow(p2.x - p1.x, 2) + pow(p2.y - p1.y, 2)) 568 | } 569 | 570 | } 571 | 572 | extension CameraViewController: ImageEditViewDelegate{ 573 | 574 | func imageCompleted(_ editedImage: UIImage) { 575 | self.dismiss(animated: false) { 576 | self.solutionDelegate?.returnImages([editedImage]) 577 | } 578 | } 579 | 580 | } 581 | 582 | extension CameraViewController: AVCaptureMetadataOutputObjectsDelegate{ 583 | 584 | func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection){ 585 | for metadataObject in metadataObjects { 586 | if metadataObject.type == AVMetadataObject.ObjectType.face { 587 | let transformedMetadataObject = previewLayer.transformedMetadataObject(for: metadataObject) 588 | let currentFacePoint = CGPoint(x: (transformedMetadataObject?.bounds.midX)!, y: (transformedMetadataObject?.bounds.midY)!) 589 | if lastFaceFocusPoint == nil || distanceBetween(currentFacePoint,and: lastFaceFocusPoint) > 100{ 590 | focus(at: currentFacePoint) 591 | lastFaceFocusPoint = currentFacePoint 592 | } 593 | } 594 | } 595 | } 596 | 597 | } 598 | -------------------------------------------------------------------------------- /PhotoSolutionDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 51; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 955E0C8521C1EB1E009FF57B /* CameraViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955E0C8321C1EB1E009FF57B /* CameraViewController.swift */; }; 11 | 955E0C8621C1EB1E009FF57B /* CameraViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 955E0C8421C1EB1E009FF57B /* CameraViewController.xib */; }; 12 | 959B7AF720F37B67008CBDB3 /* tickIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 959B7AF620F37B66008CBDB3 /* tickIcon.png */; }; 13 | 95A1DD7C21073E060092E04D /* PhotoStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 95A1DD7B21073E060092E04D /* PhotoStoryboard.storyboard */; }; 14 | 95A1DD822107EA530092E04D /* PhotoSolution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95A1DD812107EA530092E04D /* PhotoSolution.swift */; }; 15 | 95A1DD842107F66F0092E04D /* PhotoCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95A1DD832107F66F0092E04D /* PhotoCollectionViewController.swift */; }; 16 | 95A1DD862107F69D0092E04D /* AlbumTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95A1DD852107F69D0092E04D /* AlbumTableViewController.swift */; }; 17 | 95A1DD882107F7240092E04D /* ImageEditorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95A1DD872107F7240092E04D /* ImageEditorViewController.swift */; }; 18 | 95A1DD8A21080D350092E04D /* PhotoNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95A1DD8921080D350092E04D /* PhotoNavigationController.swift */; }; 19 | 95A6C72321E715EC00009240 /* backIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 95A6C72221E715EC00009240 /* backIcon.png */; }; 20 | 95A6C72521E7166200009240 /* editIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 95A6C72421E7166200009240 /* editIcon.png */; }; 21 | 95A6C72721E7177500009240 /* saveIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 95A6C72621E7177500009240 /* saveIcon.png */; }; 22 | 95CA3FEB2203C73200C90652 /* settingsToOpen.png in Resources */ = {isa = PBXBuildFile; fileRef = 95CA3FE92203C73200C90652 /* settingsToOpen.png */; }; 23 | 95CA3FEC2203C73200C90652 /* settingsToClose.png in Resources */ = {isa = PBXBuildFile; fileRef = 95CA3FEA2203C73200C90652 /* settingsToClose.png */; }; 24 | 95D7C69021CB20D40086D533 /* ImageEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95D7C68F21CB20D40086D533 /* ImageEditView.swift */; }; 25 | 95D7C69721CB3AA70086D533 /* ImageEditView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 95D7C69621CB3AA70086D533 /* ImageEditView.xib */; }; 26 | 95D7C69F21CB60090086D533 /* cancelIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 95D7C69E21CB60090086D533 /* cancelIcon.png */; }; 27 | 95D7C6A521CB627A0086D533 /* switchIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 95D7C6A421CB627A0086D533 /* switchIcon.png */; }; 28 | 95D8DCD921F03FDF00A656E2 /* cameraIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 95D8DCD821F03FDE00A656E2 /* cameraIcon.png */; }; 29 | 95D8DCDC21F042EA00A656E2 /* flashOff.png in Resources */ = {isa = PBXBuildFile; fileRef = 95D8DCDA21F042EA00A656E2 /* flashOff.png */; }; 30 | 95D8DCDD21F042EA00A656E2 /* flashOn.png in Resources */ = {isa = PBXBuildFile; fileRef = 95D8DCDB21F042EA00A656E2 /* flashOn.png */; }; 31 | 95D8DCE221F14E1600A656E2 /* UIImage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95D8DCE121F14E1600A656E2 /* UIImage+Extension.swift */; }; 32 | 95E696A620F230F2003BDCBE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95E696A520F230F2003BDCBE /* AppDelegate.swift */; }; 33 | 95E696A820F230F2003BDCBE /* PostViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95E696A720F230F2003BDCBE /* PostViewController.swift */; }; 34 | 95E696AB20F230F2003BDCBE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 95E696A920F230F2003BDCBE /* Main.storyboard */; }; 35 | 95E696AD20F230F3003BDCBE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 95E696AC20F230F3003BDCBE /* Assets.xcassets */; }; 36 | 95E696B020F230F3003BDCBE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 95E696AE20F230F3003BDCBE /* LaunchScreen.storyboard */; }; 37 | 95E696C020F2357F003BDCBE /* Photo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95E696BE20F2357E003BDCBE /* Photo.swift */; }; 38 | 95E696C120F2357F003BDCBE /* Album.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95E696BF20F2357E003BDCBE /* Album.swift */; }; 39 | 95E696C820F23589003BDCBE /* PhotoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95E696C220F23589003BDCBE /* PhotoCell.swift */; }; 40 | 95E696C920F23589003BDCBE /* PhotoCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 95E696C320F23589003BDCBE /* PhotoCell.xib */; }; 41 | 95E696CA20F23589003BDCBE /* AlbumCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95E696C420F23589003BDCBE /* AlbumCell.swift */; }; 42 | 95E696CB20F23589003BDCBE /* AlbumCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 95E696C520F23589003BDCBE /* AlbumCell.xib */; }; 43 | 95E696CC20F23589003BDCBE /* ImageViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95E696C620F23589003BDCBE /* ImageViewCell.swift */; }; 44 | 95E696CD20F23589003BDCBE /* ImageViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 95E696C720F23589003BDCBE /* ImageViewCell.xib */; }; 45 | 95E696CF20F23591003BDCBE /* arrowIcon.png in Resources */ = {isa = PBXBuildFile; fileRef = 95E696CE20F23591003BDCBE /* arrowIcon.png */; }; 46 | 95E696D220F2397A003BDCBE /* PickerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95E696D020F2397A003BDCBE /* PickerCell.swift */; }; 47 | 95E696D320F2397A003BDCBE /* PickerCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 95E696D120F2397A003BDCBE /* PickerCell.xib */; }; 48 | /* End PBXBuildFile section */ 49 | 50 | /* Begin PBXFileReference section */ 51 | 1ECC4B92CDC612B5AD10E48B /* Pods_PhotoSolutionDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PhotoSolutionDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | 955E0C8321C1EB1E009FF57B /* CameraViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraViewController.swift; sourceTree = ""; }; 53 | 955E0C8421C1EB1E009FF57B /* CameraViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CameraViewController.xib; sourceTree = ""; }; 54 | 959B7AF620F37B66008CBDB3 /* tickIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = tickIcon.png; sourceTree = ""; }; 55 | 95A1DD7B21073E060092E04D /* PhotoStoryboard.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = PhotoStoryboard.storyboard; sourceTree = ""; }; 56 | 95A1DD812107EA530092E04D /* PhotoSolution.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoSolution.swift; sourceTree = ""; }; 57 | 95A1DD832107F66F0092E04D /* PhotoCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoCollectionViewController.swift; sourceTree = ""; }; 58 | 95A1DD852107F69D0092E04D /* AlbumTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumTableViewController.swift; sourceTree = ""; }; 59 | 95A1DD872107F7240092E04D /* ImageEditorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageEditorViewController.swift; sourceTree = ""; }; 60 | 95A1DD8921080D350092E04D /* PhotoNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoNavigationController.swift; sourceTree = ""; }; 61 | 95A6C72221E715EC00009240 /* backIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = backIcon.png; sourceTree = ""; }; 62 | 95A6C72421E7166200009240 /* editIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = editIcon.png; sourceTree = ""; }; 63 | 95A6C72621E7177500009240 /* saveIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = saveIcon.png; sourceTree = ""; }; 64 | 95CA3FE92203C73200C90652 /* settingsToOpen.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = settingsToOpen.png; sourceTree = ""; }; 65 | 95CA3FEA2203C73200C90652 /* settingsToClose.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = settingsToClose.png; sourceTree = ""; }; 66 | 95D7C68F21CB20D40086D533 /* ImageEditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageEditView.swift; sourceTree = ""; }; 67 | 95D7C69621CB3AA70086D533 /* ImageEditView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ImageEditView.xib; sourceTree = ""; }; 68 | 95D7C69E21CB60090086D533 /* cancelIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cancelIcon.png; sourceTree = ""; }; 69 | 95D7C6A421CB627A0086D533 /* switchIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = switchIcon.png; sourceTree = ""; }; 70 | 95D8DCD821F03FDE00A656E2 /* cameraIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cameraIcon.png; sourceTree = ""; }; 71 | 95D8DCDA21F042EA00A656E2 /* flashOff.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = flashOff.png; sourceTree = ""; }; 72 | 95D8DCDB21F042EA00A656E2 /* flashOn.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = flashOn.png; sourceTree = ""; }; 73 | 95D8DCE121F14E1600A656E2 /* UIImage+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Extension.swift"; sourceTree = ""; }; 74 | 95E696A220F230F2003BDCBE /* PhotoSolutionDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PhotoSolutionDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 75 | 95E696A520F230F2003BDCBE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 76 | 95E696A720F230F2003BDCBE /* PostViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostViewController.swift; sourceTree = ""; }; 77 | 95E696AA20F230F2003BDCBE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 78 | 95E696AC20F230F3003BDCBE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 79 | 95E696AF20F230F3003BDCBE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 80 | 95E696B120F230F3003BDCBE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 81 | 95E696BE20F2357E003BDCBE /* Photo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Photo.swift; sourceTree = ""; }; 82 | 95E696BF20F2357E003BDCBE /* Album.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Album.swift; sourceTree = ""; }; 83 | 95E696C220F23589003BDCBE /* PhotoCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoCell.swift; sourceTree = ""; }; 84 | 95E696C320F23589003BDCBE /* PhotoCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = PhotoCell.xib; sourceTree = ""; }; 85 | 95E696C420F23589003BDCBE /* AlbumCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumCell.swift; sourceTree = ""; }; 86 | 95E696C520F23589003BDCBE /* AlbumCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AlbumCell.xib; sourceTree = ""; }; 87 | 95E696C620F23589003BDCBE /* ImageViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageViewCell.swift; sourceTree = ""; }; 88 | 95E696C720F23589003BDCBE /* ImageViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ImageViewCell.xib; sourceTree = ""; }; 89 | 95E696CE20F23591003BDCBE /* arrowIcon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = arrowIcon.png; sourceTree = ""; }; 90 | 95E696D020F2397A003BDCBE /* PickerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickerCell.swift; sourceTree = ""; }; 91 | 95E696D120F2397A003BDCBE /* PickerCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PickerCell.xib; sourceTree = ""; }; 92 | /* End PBXFileReference section */ 93 | 94 | /* Begin PBXFrameworksBuildPhase section */ 95 | 95E6969F20F230F2003BDCBE /* Frameworks */ = { 96 | isa = PBXFrameworksBuildPhase; 97 | buildActionMask = 2147483647; 98 | files = ( 99 | ); 100 | runOnlyForDeploymentPostprocessing = 0; 101 | }; 102 | /* End PBXFrameworksBuildPhase section */ 103 | 104 | /* Begin PBXGroup section */ 105 | 572B29433962EEF43B399049 /* Frameworks */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 1ECC4B92CDC612B5AD10E48B /* Pods_PhotoSolutionDemo.framework */, 109 | ); 110 | name = Frameworks; 111 | sourceTree = ""; 112 | }; 113 | 95D8DCE821F18D2500A656E2 /* Icons */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | 95CA3FEA2203C73200C90652 /* settingsToClose.png */, 117 | 95CA3FE92203C73200C90652 /* settingsToOpen.png */, 118 | 95D7C6A421CB627A0086D533 /* switchIcon.png */, 119 | 95A6C72421E7166200009240 /* editIcon.png */, 120 | 95D8DCDA21F042EA00A656E2 /* flashOff.png */, 121 | 95D8DCDB21F042EA00A656E2 /* flashOn.png */, 122 | 95D8DCD821F03FDE00A656E2 /* cameraIcon.png */, 123 | 95D7C69E21CB60090086D533 /* cancelIcon.png */, 124 | 95A6C72621E7177500009240 /* saveIcon.png */, 125 | 95A6C72221E715EC00009240 /* backIcon.png */, 126 | 95E696CE20F23591003BDCBE /* arrowIcon.png */, 127 | 959B7AF620F37B66008CBDB3 /* tickIcon.png */, 128 | ); 129 | name = Icons; 130 | sourceTree = ""; 131 | }; 132 | 95E6969920F230F2003BDCBE = { 133 | isa = PBXGroup; 134 | children = ( 135 | 95E696B720F230F9003BDCBE /* PhotoSolution */, 136 | 95E696A420F230F2003BDCBE /* PhotoSolutionDemo */, 137 | 95E696A320F230F2003BDCBE /* Products */, 138 | 572B29433962EEF43B399049 /* Frameworks */, 139 | ); 140 | sourceTree = ""; 141 | }; 142 | 95E696A320F230F2003BDCBE /* Products */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | 95E696A220F230F2003BDCBE /* PhotoSolutionDemo.app */, 146 | ); 147 | name = Products; 148 | sourceTree = ""; 149 | }; 150 | 95E696A420F230F2003BDCBE /* PhotoSolutionDemo */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | 95E696A520F230F2003BDCBE /* AppDelegate.swift */, 154 | 95E696A720F230F2003BDCBE /* PostViewController.swift */, 155 | 95E696A920F230F2003BDCBE /* Main.storyboard */, 156 | 95E696D020F2397A003BDCBE /* PickerCell.swift */, 157 | 95E696D120F2397A003BDCBE /* PickerCell.xib */, 158 | 95E696AC20F230F3003BDCBE /* Assets.xcassets */, 159 | 95E696AE20F230F3003BDCBE /* LaunchScreen.storyboard */, 160 | 95E696B120F230F3003BDCBE /* Info.plist */, 161 | ); 162 | path = PhotoSolutionDemo; 163 | sourceTree = ""; 164 | }; 165 | 95E696B720F230F9003BDCBE /* PhotoSolution */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | 95A1DD812107EA530092E04D /* PhotoSolution.swift */, 169 | 95D8DCE121F14E1600A656E2 /* UIImage+Extension.swift */, 170 | 95D7C68F21CB20D40086D533 /* ImageEditView.swift */, 171 | 95D7C69621CB3AA70086D533 /* ImageEditView.xib */, 172 | 955E0C8321C1EB1E009FF57B /* CameraViewController.swift */, 173 | 955E0C8421C1EB1E009FF57B /* CameraViewController.xib */, 174 | 95A1DD7B21073E060092E04D /* PhotoStoryboard.storyboard */, 175 | 95A1DD8921080D350092E04D /* PhotoNavigationController.swift */, 176 | 95A1DD832107F66F0092E04D /* PhotoCollectionViewController.swift */, 177 | 95A1DD852107F69D0092E04D /* AlbumTableViewController.swift */, 178 | 95A1DD872107F7240092E04D /* ImageEditorViewController.swift */, 179 | 95E696BE20F2357E003BDCBE /* Photo.swift */, 180 | 95E696BF20F2357E003BDCBE /* Album.swift */, 181 | 95E696C220F23589003BDCBE /* PhotoCell.swift */, 182 | 95E696C320F23589003BDCBE /* PhotoCell.xib */, 183 | 95E696C420F23589003BDCBE /* AlbumCell.swift */, 184 | 95E696C520F23589003BDCBE /* AlbumCell.xib */, 185 | 95E696C620F23589003BDCBE /* ImageViewCell.swift */, 186 | 95E696C720F23589003BDCBE /* ImageViewCell.xib */, 187 | 95D8DCE821F18D2500A656E2 /* Icons */, 188 | ); 189 | path = PhotoSolution; 190 | sourceTree = ""; 191 | }; 192 | /* End PBXGroup section */ 193 | 194 | /* Begin PBXNativeTarget section */ 195 | 95E696A120F230F2003BDCBE /* PhotoSolutionDemo */ = { 196 | isa = PBXNativeTarget; 197 | buildConfigurationList = 95E696B420F230F3003BDCBE /* Build configuration list for PBXNativeTarget "PhotoSolutionDemo" */; 198 | buildPhases = ( 199 | EE526C02B3A2227D7888EF85 /* [CP] Check Pods Manifest.lock */, 200 | 95E6969E20F230F2003BDCBE /* Sources */, 201 | 95E6969F20F230F2003BDCBE /* Frameworks */, 202 | 95E696A020F230F2003BDCBE /* Resources */, 203 | ); 204 | buildRules = ( 205 | ); 206 | dependencies = ( 207 | ); 208 | name = PhotoSolutionDemo; 209 | productName = PhotoSolutionDemo; 210 | productReference = 95E696A220F230F2003BDCBE /* PhotoSolutionDemo.app */; 211 | productType = "com.apple.product-type.application"; 212 | }; 213 | /* End PBXNativeTarget section */ 214 | 215 | /* Begin PBXProject section */ 216 | 95E6969A20F230F2003BDCBE /* Project object */ = { 217 | isa = PBXProject; 218 | attributes = { 219 | LastSwiftUpdateCheck = 0940; 220 | LastUpgradeCheck = 0940; 221 | ORGANIZATIONNAME = mark; 222 | TargetAttributes = { 223 | 95E696A120F230F2003BDCBE = { 224 | CreatedOnToolsVersion = 9.4.1; 225 | LastSwiftMigration = 1020; 226 | }; 227 | }; 228 | }; 229 | buildConfigurationList = 95E6969D20F230F2003BDCBE /* Build configuration list for PBXProject "PhotoSolutionDemo" */; 230 | compatibilityVersion = "Xcode 10.0"; 231 | developmentRegion = en; 232 | hasScannedForEncodings = 0; 233 | knownRegions = ( 234 | en, 235 | Base, 236 | ); 237 | mainGroup = 95E6969920F230F2003BDCBE; 238 | productRefGroup = 95E696A320F230F2003BDCBE /* Products */; 239 | projectDirPath = ""; 240 | projectRoot = ""; 241 | targets = ( 242 | 95E696A120F230F2003BDCBE /* PhotoSolutionDemo */, 243 | ); 244 | }; 245 | /* End PBXProject section */ 246 | 247 | /* Begin PBXResourcesBuildPhase section */ 248 | 95E696A020F230F2003BDCBE /* Resources */ = { 249 | isa = PBXResourcesBuildPhase; 250 | buildActionMask = 2147483647; 251 | files = ( 252 | 95D8DCDC21F042EA00A656E2 /* flashOff.png in Resources */, 253 | 95D8DCDD21F042EA00A656E2 /* flashOn.png in Resources */, 254 | 95A6C72521E7166200009240 /* editIcon.png in Resources */, 255 | 95D7C69F21CB60090086D533 /* cancelIcon.png in Resources */, 256 | 95E696C920F23589003BDCBE /* PhotoCell.xib in Resources */, 257 | 95E696B020F230F3003BDCBE /* LaunchScreen.storyboard in Resources */, 258 | 95E696CB20F23589003BDCBE /* AlbumCell.xib in Resources */, 259 | 95E696AD20F230F3003BDCBE /* Assets.xcassets in Resources */, 260 | 959B7AF720F37B67008CBDB3 /* tickIcon.png in Resources */, 261 | 955E0C8621C1EB1E009FF57B /* CameraViewController.xib in Resources */, 262 | 95E696CD20F23589003BDCBE /* ImageViewCell.xib in Resources */, 263 | 95CA3FEC2203C73200C90652 /* settingsToClose.png in Resources */, 264 | 95CA3FEB2203C73200C90652 /* settingsToOpen.png in Resources */, 265 | 95D8DCD921F03FDF00A656E2 /* cameraIcon.png in Resources */, 266 | 95A6C72321E715EC00009240 /* backIcon.png in Resources */, 267 | 95A1DD7C21073E060092E04D /* PhotoStoryboard.storyboard in Resources */, 268 | 95E696D320F2397A003BDCBE /* PickerCell.xib in Resources */, 269 | 95D7C6A521CB627A0086D533 /* switchIcon.png in Resources */, 270 | 95D7C69721CB3AA70086D533 /* ImageEditView.xib in Resources */, 271 | 95E696AB20F230F2003BDCBE /* Main.storyboard in Resources */, 272 | 95A6C72721E7177500009240 /* saveIcon.png in Resources */, 273 | 95E696CF20F23591003BDCBE /* arrowIcon.png in Resources */, 274 | ); 275 | runOnlyForDeploymentPostprocessing = 0; 276 | }; 277 | /* End PBXResourcesBuildPhase section */ 278 | 279 | /* Begin PBXShellScriptBuildPhase section */ 280 | EE526C02B3A2227D7888EF85 /* [CP] Check Pods Manifest.lock */ = { 281 | isa = PBXShellScriptBuildPhase; 282 | buildActionMask = 2147483647; 283 | files = ( 284 | ); 285 | inputFileListPaths = ( 286 | ); 287 | inputPaths = ( 288 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 289 | "${PODS_ROOT}/Manifest.lock", 290 | ); 291 | name = "[CP] Check Pods Manifest.lock"; 292 | outputFileListPaths = ( 293 | ); 294 | outputPaths = ( 295 | "$(DERIVED_FILE_DIR)/Pods-PhotoSolutionDemo-checkManifestLockResult.txt", 296 | ); 297 | runOnlyForDeploymentPostprocessing = 0; 298 | shellPath = /bin/sh; 299 | shellScript = " 300 | "; 301 | showEnvVarsInLog = 0; 302 | }; 303 | /* End PBXShellScriptBuildPhase section */ 304 | 305 | /* Begin PBXSourcesBuildPhase section */ 306 | 95E6969E20F230F2003BDCBE /* Sources */ = { 307 | isa = PBXSourcesBuildPhase; 308 | buildActionMask = 2147483647; 309 | files = ( 310 | 95A1DD882107F7240092E04D /* ImageEditorViewController.swift in Sources */, 311 | 95E696C020F2357F003BDCBE /* Photo.swift in Sources */, 312 | 95A1DD842107F66F0092E04D /* PhotoCollectionViewController.swift in Sources */, 313 | 95E696A820F230F2003BDCBE /* PostViewController.swift in Sources */, 314 | 95E696C820F23589003BDCBE /* PhotoCell.swift in Sources */, 315 | 95A1DD822107EA530092E04D /* PhotoSolution.swift in Sources */, 316 | 95A1DD8A21080D350092E04D /* PhotoNavigationController.swift in Sources */, 317 | 95E696A620F230F2003BDCBE /* AppDelegate.swift in Sources */, 318 | 95D7C69021CB20D40086D533 /* ImageEditView.swift in Sources */, 319 | 95E696C120F2357F003BDCBE /* Album.swift in Sources */, 320 | 95E696D220F2397A003BDCBE /* PickerCell.swift in Sources */, 321 | 95D8DCE221F14E1600A656E2 /* UIImage+Extension.swift in Sources */, 322 | 955E0C8521C1EB1E009FF57B /* CameraViewController.swift in Sources */, 323 | 95E696CC20F23589003BDCBE /* ImageViewCell.swift in Sources */, 324 | 95E696CA20F23589003BDCBE /* AlbumCell.swift in Sources */, 325 | 95A1DD862107F69D0092E04D /* AlbumTableViewController.swift in Sources */, 326 | ); 327 | runOnlyForDeploymentPostprocessing = 0; 328 | }; 329 | /* End PBXSourcesBuildPhase section */ 330 | 331 | /* Begin PBXVariantGroup section */ 332 | 95E696A920F230F2003BDCBE /* Main.storyboard */ = { 333 | isa = PBXVariantGroup; 334 | children = ( 335 | 95E696AA20F230F2003BDCBE /* Base */, 336 | ); 337 | name = Main.storyboard; 338 | sourceTree = ""; 339 | }; 340 | 95E696AE20F230F3003BDCBE /* LaunchScreen.storyboard */ = { 341 | isa = PBXVariantGroup; 342 | children = ( 343 | 95E696AF20F230F3003BDCBE /* Base */, 344 | ); 345 | name = LaunchScreen.storyboard; 346 | sourceTree = ""; 347 | }; 348 | /* End PBXVariantGroup section */ 349 | 350 | /* Begin XCBuildConfiguration section */ 351 | 95E696B220F230F3003BDCBE /* Debug */ = { 352 | isa = XCBuildConfiguration; 353 | buildSettings = { 354 | ALWAYS_SEARCH_USER_PATHS = NO; 355 | CLANG_ANALYZER_NONNULL = YES; 356 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 357 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 358 | CLANG_CXX_LIBRARY = "libc++"; 359 | CLANG_ENABLE_MODULES = YES; 360 | CLANG_ENABLE_OBJC_ARC = YES; 361 | CLANG_ENABLE_OBJC_WEAK = YES; 362 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 363 | CLANG_WARN_BOOL_CONVERSION = YES; 364 | CLANG_WARN_COMMA = YES; 365 | CLANG_WARN_CONSTANT_CONVERSION = YES; 366 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 367 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 368 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 369 | CLANG_WARN_EMPTY_BODY = YES; 370 | CLANG_WARN_ENUM_CONVERSION = YES; 371 | CLANG_WARN_INFINITE_RECURSION = YES; 372 | CLANG_WARN_INT_CONVERSION = YES; 373 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 374 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 375 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 376 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 377 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 378 | CLANG_WARN_STRICT_PROTOTYPES = YES; 379 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 380 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 381 | CLANG_WARN_UNREACHABLE_CODE = YES; 382 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 383 | CODE_SIGN_IDENTITY = "iPhone Developer"; 384 | COPY_PHASE_STRIP = NO; 385 | DEBUG_INFORMATION_FORMAT = dwarf; 386 | ENABLE_STRICT_OBJC_MSGSEND = YES; 387 | ENABLE_TESTABILITY = YES; 388 | GCC_C_LANGUAGE_STANDARD = gnu11; 389 | GCC_DYNAMIC_NO_PIC = NO; 390 | GCC_NO_COMMON_BLOCKS = YES; 391 | GCC_OPTIMIZATION_LEVEL = 0; 392 | GCC_PREPROCESSOR_DEFINITIONS = ( 393 | "DEBUG=1", 394 | "$(inherited)", 395 | ); 396 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 397 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 398 | GCC_WARN_UNDECLARED_SELECTOR = YES; 399 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 400 | GCC_WARN_UNUSED_FUNCTION = YES; 401 | GCC_WARN_UNUSED_VARIABLE = YES; 402 | IPHONEOS_DEPLOYMENT_TARGET = 11.4; 403 | MTL_ENABLE_DEBUG_INFO = YES; 404 | ONLY_ACTIVE_ARCH = YES; 405 | SDKROOT = iphoneos; 406 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 407 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 408 | }; 409 | name = Debug; 410 | }; 411 | 95E696B320F230F3003BDCBE /* Release */ = { 412 | isa = XCBuildConfiguration; 413 | buildSettings = { 414 | ALWAYS_SEARCH_USER_PATHS = NO; 415 | CLANG_ANALYZER_NONNULL = YES; 416 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 417 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 418 | CLANG_CXX_LIBRARY = "libc++"; 419 | CLANG_ENABLE_MODULES = YES; 420 | CLANG_ENABLE_OBJC_ARC = YES; 421 | CLANG_ENABLE_OBJC_WEAK = YES; 422 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 423 | CLANG_WARN_BOOL_CONVERSION = YES; 424 | CLANG_WARN_COMMA = YES; 425 | CLANG_WARN_CONSTANT_CONVERSION = YES; 426 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 427 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 428 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 429 | CLANG_WARN_EMPTY_BODY = YES; 430 | CLANG_WARN_ENUM_CONVERSION = YES; 431 | CLANG_WARN_INFINITE_RECURSION = YES; 432 | CLANG_WARN_INT_CONVERSION = YES; 433 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 434 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 435 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 436 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 437 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 438 | CLANG_WARN_STRICT_PROTOTYPES = YES; 439 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 440 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 441 | CLANG_WARN_UNREACHABLE_CODE = YES; 442 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 443 | CODE_SIGN_IDENTITY = "iPhone Developer"; 444 | COPY_PHASE_STRIP = NO; 445 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 446 | ENABLE_NS_ASSERTIONS = NO; 447 | ENABLE_STRICT_OBJC_MSGSEND = YES; 448 | GCC_C_LANGUAGE_STANDARD = gnu11; 449 | GCC_NO_COMMON_BLOCKS = YES; 450 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 451 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 452 | GCC_WARN_UNDECLARED_SELECTOR = YES; 453 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 454 | GCC_WARN_UNUSED_FUNCTION = YES; 455 | GCC_WARN_UNUSED_VARIABLE = YES; 456 | IPHONEOS_DEPLOYMENT_TARGET = 11.4; 457 | MTL_ENABLE_DEBUG_INFO = NO; 458 | SDKROOT = iphoneos; 459 | SWIFT_COMPILATION_MODE = wholemodule; 460 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 461 | VALIDATE_PRODUCT = YES; 462 | }; 463 | name = Release; 464 | }; 465 | 95E696B520F230F3003BDCBE /* Debug */ = { 466 | isa = XCBuildConfiguration; 467 | buildSettings = { 468 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 469 | CODE_SIGN_IDENTITY = "iPhone Developer"; 470 | CODE_SIGN_STYLE = Automatic; 471 | DEVELOPMENT_TEAM = N37BZZTU8Z; 472 | INFOPLIST_FILE = PhotoSolutionDemo/Info.plist; 473 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 474 | LD_RUNPATH_SEARCH_PATHS = ( 475 | "$(inherited)", 476 | "@executable_path/Frameworks", 477 | ); 478 | PRODUCT_BUNDLE_IDENTIFIER = PhotoSolution.Demo; 479 | PRODUCT_NAME = "$(TARGET_NAME)"; 480 | PROVISIONING_PROFILE_SPECIFIER = ""; 481 | SWIFT_VERSION = 5.0; 482 | TARGETED_DEVICE_FAMILY = "1,2"; 483 | }; 484 | name = Debug; 485 | }; 486 | 95E696B620F230F3003BDCBE /* Release */ = { 487 | isa = XCBuildConfiguration; 488 | buildSettings = { 489 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 490 | CODE_SIGN_IDENTITY = "iPhone Developer"; 491 | CODE_SIGN_STYLE = Automatic; 492 | DEVELOPMENT_TEAM = N37BZZTU8Z; 493 | INFOPLIST_FILE = PhotoSolutionDemo/Info.plist; 494 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 495 | LD_RUNPATH_SEARCH_PATHS = ( 496 | "$(inherited)", 497 | "@executable_path/Frameworks", 498 | ); 499 | PRODUCT_BUNDLE_IDENTIFIER = PhotoSolution.Demo; 500 | PRODUCT_NAME = "$(TARGET_NAME)"; 501 | PROVISIONING_PROFILE_SPECIFIER = ""; 502 | SWIFT_VERSION = 5.0; 503 | TARGETED_DEVICE_FAMILY = "1,2"; 504 | }; 505 | name = Release; 506 | }; 507 | /* End XCBuildConfiguration section */ 508 | 509 | /* Begin XCConfigurationList section */ 510 | 95E6969D20F230F2003BDCBE /* Build configuration list for PBXProject "PhotoSolutionDemo" */ = { 511 | isa = XCConfigurationList; 512 | buildConfigurations = ( 513 | 95E696B220F230F3003BDCBE /* Debug */, 514 | 95E696B320F230F3003BDCBE /* Release */, 515 | ); 516 | defaultConfigurationIsVisible = 0; 517 | defaultConfigurationName = Release; 518 | }; 519 | 95E696B420F230F3003BDCBE /* Build configuration list for PBXNativeTarget "PhotoSolutionDemo" */ = { 520 | isa = XCConfigurationList; 521 | buildConfigurations = ( 522 | 95E696B520F230F3003BDCBE /* Debug */, 523 | 95E696B620F230F3003BDCBE /* Release */, 524 | ); 525 | defaultConfigurationIsVisible = 0; 526 | defaultConfigurationName = Release; 527 | }; 528 | /* End XCConfigurationList section */ 529 | }; 530 | rootObject = 95E6969A20F230F2003BDCBE /* Project object */; 531 | } 532 | --------------------------------------------------------------------------------