├── 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 |
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 |
--------------------------------------------------------------------------------
/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 |
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 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------