├── .gitignore ├── Banners.png ├── LICENSE ├── README.md ├── TGPhotoPicker ├── .swift-version ├── TGPhotoPicker.podspec ├── TGPhotoPicker.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata └── TGPhotoPicker │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-40@2x.png │ │ ├── Icon-40@3x.png │ │ ├── Icon-60@2x.png │ │ ├── Icon-60@3x.png │ │ ├── Icon-Small@2x-1.png │ │ └── Icon-Small@3x.png │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ ├── TGPhotoPicker │ ├── TGActionSheet.swift │ ├── TGAlbumPhotoPreviewVC.swift │ ├── TGAnimationButton.swift │ ├── TGBottomBar.swift │ ├── TGCameraVC.swift │ ├── TGCameraVCForiOS8.swift │ ├── TGFetchM.swift │ ├── TGImage.swift │ ├── TGPhotoCollectionVC.swift │ ├── TGPhotoFetchOptions.swift │ ├── TGPhotoImageManager.swift │ ├── TGPhotoListVC.swift │ ├── TGPhotoM.swift │ ├── TGPhotoPicker.bundle │ │ ├── camera@2x.png │ │ ├── flash@2x.png │ │ ├── flashauto@2x.png │ │ └── flashno@2x.png │ ├── TGPhotoPicker.swift │ ├── TGPhotoPickerConfig.swift │ ├── TGPhotoPickerManager.swift │ ├── TGPhotoPickerVC.swift │ ├── TGPhotoPreviewCell.swift │ ├── TGPhotoPreviewVC.swift │ └── TGTopBar.swift │ └── ViewController.swift ├── gif ├── b.gif ├── circle.gif ├── diagonalBelt.gif ├── h.gif ├── o.gif ├── s.gif ├── star.gif └── t.gif ├── img ├── IMG_2480.PNG ├── IMG_2481.PNG ├── IMG_2482.PNG ├── IMG_2483.PNG ├── IMG_2484.PNG ├── IMG_2485.PNG ├── IMG_2486.PNG ├── IMG_2487.PNG ├── IMG_2488.PNG ├── IMG_2489.PNG ├── IMG_2490.PNG ├── IMG_2491.PNG ├── IMG_2492.PNG ├── IMG_2493.PNG ├── IMG_2494.PNG ├── IMG_2495.PNG ├── IMG_2496.PNG ├── IMG_2497.PNG ├── IMG_2498.PNG ├── IMG_2584.PNG ├── IMG_2640.PNG ├── IMG_2641.PNG └── IMG_2642.PNG └── logo.png /.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 | .build/ 41 | 42 | # CocoaPods 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | # Pods/ 49 | 50 | # Carthage 51 | # 52 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 53 | # Carthage/Checkouts 54 | 55 | Carthage/Build 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 63 | 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output 68 | -------------------------------------------------------------------------------- /Banners.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/Banners.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 targetcloud 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # TGPhotoPicker 4 | 5 | the best photo picker plugin in swift(iOS8+) 6 | No using picture resources, based on TGImage 7 | 8 | ![Swift](https://img.shields.io/badge/Swift-3.0-orange.svg) 9 | ![Build](https://img.shields.io/badge/build-passing-green.svg) 10 | ![License MIT](https://img.shields.io/badge/license-MIT-green.svg?style=flat) 11 | ![Platform](https://img.shields.io/cocoapods/p/Pastel.svg?style=flat) 12 | ![Cocoapod](https://img.shields.io/badge/pod-v0.0.5-blue.svg) 13 | 14 | ## Demo Screenshot 15 | 16 | ###### 照片选择界面(.weibo)更多效果在下面哦:) 17 | 18 | 19 | 20 | ###### 参数调节界面(只为方便直观查看本插件的参数效果,实际使用时请直接参考TGPhotoPickerConfig.swift提供的参数) 21 | 22 | 23 | 24 | ###### 自定义拍照界面 25 | 26 | 27 | 28 | ## Recently Updated 29 | - 0.0.5 添加AlertSheet类和useCustomActionSheet配置属性 30 | - 0.0.4 新增11个属性,向下兼容iOS8,其中最主要的新增功能是2个,1是允许用户选择使用iOS8或iOS10拍照功能,推荐仍使用iOS8,默认使用iOS10;2是拍照时是否同时把拍照结果保存到系统相册中去,默认不保存 31 | - 0.0.3 丰富的参数,`DIY`你满意的一款photo picker 32 | 33 | ## Features 34 | - [x] 不使用图片资源,基于TGImage实现 35 | - [x] 支持`链式`编程配置,程序员的最爱 36 | - [x] 支持`Cocoapods` 37 | - [x] 支持2种`遮罩`模式(直接在选择的照片cell上显示遮罩、选择到最大照片数量后其余照片cell显示遮罩) 38 | - [x] 支持选择完成后,长按控件的照片cell进行位置调整(iOS `9` 及以上有效) 39 | - [x] 支持2种`删除`模式(选择完成后直接点每个照片cell上的删除按钮删除、选择完成后预览单个照片大图时点工具栏上的删除按钮删除) 40 | - [x] 支持选择指示器`选择时的顺序`数字显示(每个照片cell的状态有5种状态:未选择、选中状态、数字选中状态、删除状态、按住删除按钮时的高亮状态) 41 | - [x] 支持2种`选择`模式(直接选择、预览选择) 42 | - [x] 预置`weibo`、`wechat` 2种成组配置模式,省去多个参数配置,简化为一句代码配置 43 | - [x] 支持8种`选择样式`(类型)`单勾`、`圈`、`方块`、`带`、`斜带`、`三角`、`心`、`星` 44 | - [x] 支持4种`选择位置`(左上、左下、右上、右下) 45 | - [x] 支持`tinColor`统一设置风格 46 | - [x] 支持选择指示器`大小调节` 47 | - [x] 自由选择iOS8或iOS10拍照功能 48 | - [x] 轻量级、使用超灵活、功能超强大 49 | - [x] 用例丰富,快速上手 50 | 51 | ## Usage 52 | 总体分为2种使用方式,有界面的话,用TGPhotoPicker实例化(即多选照片选择完成后把数据呈现在控件上),不需要界面的话用TGPhotoPickerManager.shared.takePhotoModels单例方法获取多选照片数据(这个又分两种,用模型或不用模型(直接用`分开的数组`)) 53 | 54 | ###### 提示: 55 | 56 | `1、请先在info.plist中添加以下两个key,以请求相机相册的访问权限(iOS10)` 57 | 58 | `NSCameraUsageDescription`(Privacy - Camera Usage Description) 59 | `NSPhotoLibraryUsageDescription`(Privacy - Photo Library Usage Description) 60 | 61 | `2、作者的Xcode为8.3.3(8E3004b)若你的版本过低,可能会在TGPhotoPickerConfig.swift文件的case .smartAlbumScreenshots:处出现错误提示:Enum case 'smartAlbumScreenshots' not found in type 'PHAssetCollectionSubtype'   报错原因是这是iOS10.2/10.3新增两个值,   解决办法:1、请升级你的Xcode 2、注释相关代码` 62 | 63 | #### 使用默认(有界面) 64 | ```swift 65 | lazy var picker: TGPhotoPicker = TGPhotoPicker(self, frame: CGRect(x: 0, y: 50, width: UIScreen.main.bounds.width, height: 200)) 66 | 67 | override func viewDidLoad() { 68 | super.viewDidLoad() 69 |        //放到界面中去 70 |        self.view.addSubview(picker) 71 | } 72 | ``` 73 | 74 | #### 带配置(有界面) 75 | ```swift 76 | lazy var picker: TGPhotoPicker = TGPhotoPicker(self, frame: CGRect(x: 0, y: 50, width: UIScreen.main.bounds.width, height: 200)) { (config) in 77 | config.type = .weibo 78 |        //更多配置在这里添加 79 |    } 80 | ``` 81 | 82 | #### 带配置(链式) 83 | ```swift 84 | lazy var picker: TGPhotoPicker = TGPhotoPicker(self, frame: CGRect(x: 0, y: 50, width: UIScreen.main.bounds.width, height: 200)) { (config) in 85 | config.tg_type(.wechat) 86 | .tg_checkboxLineW(1) 87 | } 88 | ``` 89 | 90 | #### 带配置(单例配置对象) 91 | ```swift 92 | lazy var picker: TGPhotoPicker = TGPhotoPicker(self, frame: CGRect(x: 0, y: 50, width: UIScreen.main.bounds.width, height: 200)) { _ in 93 | TGPhotoPickerConfig.shared.tg_type(.wechat) 94 | .tg_checkboxLineW(1) 95 | .tg_toolBarH(50) 96 | .tg_useChineseAlbumName(true) 97 | } 98 | ``` 99 | 100 | #### 其他使用方式(无界面) `模型`数组 101 | ```swift 102 | TGPhotoPickerManager.shared.takePhotoModels(true, true) { (array) in 103 |         //示例代码 104 | self.picker.tgphotos.removeAll() 105 | self.picker.tgphotos.append(contentsOf: array) 106 | DispatchQueue.main.async { 107 | self.picker.reloadData() 108 | } 109 | } 110 | ``` 111 | 112 | #### 其他使用方式(无界面) 4个`分开独立`的数组(即模型里成员分出来的) 113 | ```swift 114 | TGPhotoPickerManager.shared.takePhotos(true, true, { (config) in 115 | //链式配置 116 | config.tg_type(TGPhotoPickerType.weibo) 117 | .tg_confirmTitle("我知道了") 118 | .tg_maxImageCount(12) 119 | }) { (asset, smallImg, bigImg, data) in 120 |         //示例代码 121 |            self.picker.tgphotos.removeAll() 122 | for i in 0.. 339 | 340 | #### 2 341 | 342 | 343 | #### 3 344 | 345 | 346 | #### 4 347 | 348 | 349 | #### 5 350 | 351 | 352 | #### 6 353 | 354 | 355 | #### 7 356 | 357 | 358 | #### 8 359 | 360 | 361 | #### 9 362 | 363 | 364 | #### 10 365 | 366 | 367 | #### 11 368 | 369 | 370 | #### 12 371 | 372 | 373 | #### 13 374 | 375 | 376 | #### 14 377 | 378 | 379 | #### 15 380 | 381 | 382 | #### 16 383 | 384 | 385 | #### 17 386 | 387 | 388 | #### 18 389 | 390 | 391 | #### 19 392 | 393 | 394 | 395 | ## 运行效果 396 | 397 | #### diagonalBelt 398 | ![](https://github.com/targetcloud/TGPhotoPicker/blob/master/gif/diagonalBelt.gif) 399 | 400 | #### circle 401 | ![](https://github.com/targetcloud/TGPhotoPicker/blob/master/gif/circle.gif) 402 | 403 | #### belt 404 | ![](https://github.com/targetcloud/TGPhotoPicker/blob/master/gif/b.gif) 405 | 406 | #### square 407 | ![](https://github.com/targetcloud/TGPhotoPicker/blob/master/gif/s.gif) 408 | 409 | #### onlyCheckbox 410 | ![](https://github.com/targetcloud/TGPhotoPicker/blob/master/gif/o.gif) 411 | 412 | #### triangle 413 | ![](https://github.com/targetcloud/TGPhotoPicker/blob/master/gif/t.gif) 414 | 415 | #### heart 416 | ![](https://github.com/targetcloud/TGPhotoPicker/blob/master/gif/h.gif) 417 | 418 | #### star 419 | ![](https://github.com/targetcloud/TGPhotoPicker/blob/master/gif/star.gif) 420 | 421 | 422 | ## Installation 423 | - 下载并拖动TGPhotoPicker到你的工程中 424 | 425 | - Cocoapods 426 | ``` 427 | pod 'TGPhotoPicker' 428 | ``` 429 | 430 | ## Reference 431 | - http://blog.csdn.net/callzjy 432 | - https://github.com/targetcloud/TGImage 433 | 434 | 如果你觉得赞,请`Star` 435 | 436 | 437 | -------------------------------------------------------------------------------- /TGPhotoPicker/.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "TGPhotoPicker" 3 | s.version = "0.0.5" 4 | s.summary = "the best photo picker plugin in swift" 5 | s.homepage = "https://github.com/targetcloud/TGPhotoPicker" 6 | s.license = "MIT" 7 | s.author = { "targetcloud" => "targetcloud@163.com" } 8 | s.platform = :ios, "8.0" 9 | s.source = { :git => "https://github.com/targetcloud/TGPhotoPicker.git", :tag => s.version } 10 | s.source_files = "TGPhotoPicker/TGPhotoPicker/TGPhotoPicker/**/*.{swift,h,m}" 11 | s.resources = "TGPhotoPicker/TGPhotoPicker/TGPhotoPicker/TGPhotoPicker.bundle" 12 | s.requires_arc = true 13 | end 14 | -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TGPhotoPicker 4 | // 5 | // Created by targetcloud on 2017/7/12. 6 | // Copyright © 2017年 targetcloud. 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: [UIApplicationLaunchOptionsKey: 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 | -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "size" : "29x29", 15 | "idiom" : "iphone", 16 | "filename" : "Icon-Small@2x-1.png", 17 | "scale" : "2x" 18 | }, 19 | { 20 | "size" : "29x29", 21 | "idiom" : "iphone", 22 | "filename" : "Icon-Small@3x.png", 23 | "scale" : "3x" 24 | }, 25 | { 26 | "size" : "40x40", 27 | "idiom" : "iphone", 28 | "filename" : "Icon-40@2x.png", 29 | "scale" : "2x" 30 | }, 31 | { 32 | "size" : "40x40", 33 | "idiom" : "iphone", 34 | "filename" : "Icon-40@3x.png", 35 | "scale" : "3x" 36 | }, 37 | { 38 | "size" : "60x60", 39 | "idiom" : "iphone", 40 | "filename" : "Icon-60@2x.png", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "size" : "60x60", 45 | "idiom" : "iphone", 46 | "filename" : "Icon-60@3x.png", 47 | "scale" : "3x" 48 | } 49 | ], 50 | "info" : { 51 | "version" : 1, 52 | "author" : "xcode" 53 | } 54 | } -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/TGPhotoPicker/TGPhotoPicker/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/TGPhotoPicker/TGPhotoPicker/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/TGPhotoPicker/TGPhotoPicker/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/TGPhotoPicker/TGPhotoPicker/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/TGPhotoPicker/TGPhotoPicker/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/TGPhotoPicker/TGPhotoPicker/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/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 | -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/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 | -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Picker 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSCameraUsageDescription 26 | 应用需要使用您的相机 27 | NSPhotoLibraryUsageDescription 28 | 应用需要访问您的相册 29 | UILaunchStoryboardName 30 | LaunchScreen 31 | UIMainStoryboardFile 32 | Main 33 | UIRequiredDeviceCapabilities 34 | 35 | armv7 36 | 37 | UIStatusBarHidden 38 | 39 | UIStatusBarStyle 40 | UIStatusBarStyleLightContent 41 | UISupportedInterfaceOrientations 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationLandscapeLeft 45 | UIInterfaceOrientationLandscapeRight 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/TGPhotoPicker/TGActionSheet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TGActionSheet.swift 3 | // TGPhotoPicker 4 | // 5 | // Created by targetcloud on 2017/8/13. 6 | // Copyright © 2017年 targetcloud. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | fileprivate let TGActionSheetCancelTag = 1999 12 | fileprivate let TGActionSheetBaseTag = 1000 13 | fileprivate let TGActionSheetAnimationDuration: TimeInterval = 0.25 14 | 15 | protocol TGActionSheetDelegate: NSObjectProtocol { 16 | func actionSheet(actionSheet: TGActionSheet?, didClickedAt index: Int) 17 | } 18 | 19 | class TGActionSheet: UIView { 20 | weak var delegate: TGActionSheetDelegate? 21 | 22 | var name:String? 23 | 24 | fileprivate lazy var btnArr: [UIButton] = [UIButton]() 25 | 26 | fileprivate lazy var dividerArr: [UIView] = [UIView]() 27 | 28 | fileprivate lazy var coverView: UIView = { [unowned self] in 29 | let coverView = UIView() 30 | coverView.backgroundColor = UIColor(white: 0, alpha: 0.3) 31 | coverView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(coverViewDidClick))) 32 | return coverView 33 | }() 34 | 35 | fileprivate lazy var actionSheet: UIView = { 36 | let actionSheet = UIView() 37 | actionSheet.backgroundColor = UIColor.white.withAlphaComponent(0.9) 38 | return actionSheet 39 | }() 40 | 41 | fileprivate lazy var cancelBtn: UIButton = { 42 | let cancelBtn = UIButton(type: .custom) 43 | cancelBtn.tag = TGActionSheetCancelTag 44 | cancelBtn.backgroundColor = UIColor(white: 1, alpha: 1) 45 | cancelBtn.titleLabel?.textAlignment = .center 46 | cancelBtn.titleLabel?.font = UIFont.systemFont(ofSize: TGPhotoPickerConfig.shared.fontSize) 47 | cancelBtn.setTitleColor(.darkGray, for: .normal) 48 | cancelBtn.addTarget(self, action: #selector(actionSheetClicked(_:)), for: .touchUpInside) 49 | return cancelBtn 50 | }() 51 | 52 | class func showActionSheet(with delegate: TGActionSheetDelegate?, title: String? = nil, cancelTitle: String, otherTitles: [String]) -> TGActionSheet { 53 | return TGActionSheet(delegate: delegate, title: title,cancelTitle: cancelTitle, otherTitles: otherTitles) 54 | } 55 | 56 | override init(frame: CGRect) { 57 | super.init(frame: frame) 58 | } 59 | 60 | init(delegate: TGActionSheetDelegate?, title: String? = nil,cancelTitle: String, otherTitles: [String]) { 61 | super.init(frame: CGRect.zero) 62 | btnArr.removeAll() 63 | dividerArr.removeAll() 64 | self.backgroundColor = .clear 65 | self.delegate = delegate 66 | self.addSubview(coverView) 67 | self.coverView.addSubview(actionSheet) 68 | if (title?.characters.count ?? 0) > 0{ 69 | self.createBtn(with: title!, bgColor: UIColor(white: 1, alpha: 1), titleColor: .lightGray, tagIndex: 0) 70 | } 71 | for i in 0..= TGActionSheetBaseTag{ 106 | self.delegate?.actionSheet(actionSheet: self, didClickedAt: btn.tag - TGActionSheetBaseTag) 107 | self.dismiss() 108 | } else { 109 | self.dismiss() 110 | } 111 | } 112 | 113 | func show() { 114 | if self.superview != nil { return } 115 | 116 | let keyWindow = UIApplication.shared.keyWindow 117 | self.frame = (keyWindow?.bounds)! 118 | keyWindow?.addSubview(self) 119 | 120 | coverView.frame = CGRect(x: 0, y: 0, width: TGPhotoPickerConfig.ScreenW, height: TGPhotoPickerConfig.ScreenH) 121 | 122 | let actionH = CGFloat(self.btnArr.count + 1) * TGPhotoPickerConfig.shared.toolBarH + 5.0 123 | actionSheet.frame = CGRect(x: 0, y: self.frame.height, width: TGPhotoPickerConfig.ScreenW, height: actionH) 124 | 125 | cancelBtn.frame = CGRect(x: 0, y: actionH - TGPhotoPickerConfig.shared.toolBarH, width: self.frame.width, height: TGPhotoPickerConfig.shared.toolBarH) 126 | 127 | let btnW: CGFloat = self.frame.width 128 | let btnH: CGFloat = TGPhotoPickerConfig.shared.toolBarH 129 | let btnX: CGFloat = 0 130 | var btnY: CGFloat = 0 131 | for i in 0..? 21 | var currentPage: Int = 0 22 | weak var delegate: TGPhotoCollectionDelegate? 23 | 24 | fileprivate var isAnimation = false 25 | fileprivate var topBar: TGTopBar? 26 | fileprivate var bottomBar: TGBottomBar? 27 | fileprivate var indicatorLabel: UILabel? 28 | 29 | fileprivate lazy var nav: TGPhotoPickerVC = self.navigationController as! TGPhotoPickerVC 30 | 31 | fileprivate lazy var cv: UICollectionView = { 32 | self.automaticallyAdjustsScrollViewInsets = false 33 | 34 | let layout = UICollectionViewFlowLayout() 35 | layout.scrollDirection = .horizontal 36 | layout.itemSize = CGSize(width: self.view.frame.width,height: self.view.frame.height) 37 | layout.minimumInteritemSpacing = 0 38 | layout.minimumLineSpacing = 0 39 | 40 | let collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: layout) 41 | collectionView.backgroundColor = .black 42 | collectionView.dataSource = self 43 | collectionView.delegate = self 44 | collectionView.isPagingEnabled = true 45 | collectionView.scrollsToTop = false 46 | collectionView.showsHorizontalScrollIndicator = false 47 | collectionView.contentOffset = CGPoint.zero 48 | collectionView.contentSize = CGSize(width: self.view.bounds.width * CGFloat(self.fetchResult!.count), height: self.view.bounds.height) 49 | collectionView.register(TGPhotoPreviewCell.self, forCellWithReuseIdentifier: cellID) 50 | self.view.addSubview(collectionView) 51 | 52 | return collectionView 53 | }() 54 | 55 | override func viewDidLoad() { 56 | super.viewDidLoad() 57 | self.view.backgroundColor = .white 58 | self.cv.reloadData() 59 | setupBar() 60 | 61 | if TGPhotoPickerConfig.shared.isShowIndicator && (TGPhotoPickerConfig.shared.indicatorPosition != .inBottomBar){ 62 | indicatorLabel = (bottomBar?.indicatorLabel)! 63 | switch TGPhotoPickerConfig.shared.indicatorPosition { 64 | case .top: 65 | self.view.addSubview(indicatorLabel!) 66 | indicatorLabel?.origin = CGPoint(x: (TGPhotoPickerConfig.ScreenW - (bottomBar?.indicatorLabel.w)!)/2, y: (topBar?.frame.maxY)! - (indicatorLabel?.h)! + 5) 67 | case .bottom: 68 | self.view.addSubview(indicatorLabel!) 69 | indicatorLabel?.origin = CGPoint(x: (TGPhotoPickerConfig.ScreenW - (bottomBar?.indicatorLabel.w)!)/2, y: (bottomBar?.y)! - (indicatorLabel?.h)! - 5) 70 | default: 71 | self.topBar?.addSubview(indicatorLabel!) 72 | indicatorLabel?.origin = CGPoint(x: (TGPhotoPickerConfig.ScreenW - (bottomBar?.indicatorLabel.w)!)/2, y: ((topBar?.h)! - (indicatorLabel?.h)!)/2) 73 | } 74 | indicatorLabel?.isHidden = false 75 | } 76 | } 77 | 78 | override var prefersStatusBarHidden: Bool{ 79 | return true 80 | } 81 | 82 | override func viewWillAppear(_ animated: Bool) { 83 | super.viewWillAppear(animated) 84 | self.navigationController?.isNavigationBarHidden = true 85 | 86 | if #available(iOS 9.0, *) { 87 | let isVCBased = Bundle.main.infoDictionary?["UIViewControllerBasedStatusBarAppearance"] as? Bool ?? false 88 | if !isVCBased{ 89 | UIApplication.shared.setStatusBarHidden(true, with: .none) 90 | } 91 | }else { 92 | UIApplication.shared.setStatusBarHidden(true, with: .none) 93 | } 94 | 95 | self.cv.setContentOffset(CGPoint(x: CGFloat(self.currentPage) * self.view.bounds.width, y: 0), animated: false) 96 | changeCurrentToolbar() 97 | } 98 | 99 | fileprivate func changeCurrentToolbar(){ 100 | if let order = nav.assetArr.index(of: self.fetchResult![self.currentPage]){ 101 | self.topBar!.setSelect(true,TGPhotoPickerConfig.shared.isShowNumber ? order : -1) 102 | self.bottomBar?.canEdit(true) 103 | } else { 104 | self.topBar!.setSelect(false) 105 | self.bottomBar?.canEdit(false) 106 | } 107 | } 108 | 109 | private func setupBar(){ 110 | self.topBar = TGTopBar(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: TGPhotoPickerConfig.shared.toolBarH)) 111 | topBar?.nav = self.nav 112 | topBar?.selectNum = self.nav.alreadySelectedImageNum 113 | topBar?.delegate = self 114 | topBar?.source = self 115 | 116 | let positionY = self.view.bounds.height - TGPhotoPickerConfig.shared.toolBarH 117 | self.bottomBar = TGBottomBar(frame: CGRect(x: 0,y: positionY,width: self.view.bounds.width,height: TGPhotoPickerConfig.shared.toolBarH)) 118 | self.bottomBar?.delegate = self 119 | self.bottomBar?.changeNumber(number: nav.assetArr.count, animation: false) 120 | 121 | self.view.addSubview(topBar!) 122 | self.view.addSubview(bottomBar!) 123 | bottomBar?.nav = nav 124 | bottomBar?.host = "\(type(of: self))" 125 | } 126 | } 127 | 128 | extension TGAlbumPhotoPreviewVC: TGBottomBarDelegate{ 129 | func onDoneButtonClicked() { 130 | self.nav.imageSelectFinish() 131 | } 132 | 133 | func onEditButtonClicked(_ sender:TGAnimationButton){ 134 | 135 | } 136 | } 137 | 138 | extension TGAlbumPhotoPreviewVC: TGTopBarDelegate{ 139 | func onBackClicked() { 140 | self.navigationController?.popViewController(animated: true) 141 | self.delegate?.onPreviewPageBack() 142 | } 143 | 144 | func onSelectedClicked(select: Bool) { 145 | if select { 146 | self.nav.assetArr.append(self.fetchResult![self.currentPage] ) 147 | } else { 148 | if let index = self.nav.assetArr.index(of: self.fetchResult![self.currentPage] ){ 149 | self.nav.assetArr.remove(at: index) 150 | } 151 | } 152 | self.bottomBar?.canEdit(select) 153 | self.bottomBar?.changeNumber(number: self.nav.assetArr.count, animation: TGPhotoPickerConfig.shared.checkboxAnimate) 154 | } 155 | } 156 | 157 | extension TGAlbumPhotoPreviewVC: UICollectionViewDataSource{ 158 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 159 | return self.fetchResult?.count ?? 0 160 | } 161 | 162 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 163 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath) as! TGPhotoPreviewCell 164 | cell.delegate = self 165 | cell.asset = self.fetchResult![indexPath.row] 166 | return cell 167 | } 168 | } 169 | 170 | extension TGAlbumPhotoPreviewVC: TGPhotoPreviewCellDelegate{ 171 | func onImageSingleTap() { 172 | if self.isAnimation { 173 | return 174 | } 175 | self.isAnimation = true 176 | if self.topBar!.frame.origin.y < 0 { 177 | UIView.animate(withDuration: 0.3, delay: 0, options: [UIViewAnimationOptions.curveEaseOut], animations: { 178 | self.topBar!.frame.origin = CGPoint.zero 179 | var originPoint = self.bottomBar!.frame.origin 180 | originPoint.y = originPoint.y - self.bottomBar!.frame.height 181 | self.bottomBar!.frame.origin = originPoint 182 | if TGPhotoPickerConfig.shared.indicatorPosition == .top{ 183 | self.indicatorLabel?.y = (self.topBar?.frame.maxY)! + 5 184 | } 185 | if TGPhotoPickerConfig.shared.indicatorPosition == .bottom{ 186 | self.indicatorLabel?.bottom = (self.bottomBar?.y)! - 5 187 | } 188 | }, completion: { isFinished in 189 | if isFinished { 190 | self.isAnimation = false 191 | } 192 | }) 193 | } else { 194 | UIView.animate(withDuration: 0.3, delay: 0, options: [UIViewAnimationOptions.curveEaseOut], animations: { 195 | self.topBar!.frame.origin = CGPoint(x:0, y: -self.topBar!.frame.height) 196 | var originPoint = self.bottomBar!.frame.origin 197 | originPoint.y = originPoint.y + self.bottomBar!.frame.height 198 | self.bottomBar!.frame.origin = originPoint 199 | if TGPhotoPickerConfig.shared.indicatorPosition == .top{ 200 | self.indicatorLabel?.y = (self.topBar?.frame.maxY)! + 5 201 | } 202 | if TGPhotoPickerConfig.shared.indicatorPosition == .bottom{ 203 | self.indicatorLabel?.bottom = (self.bottomBar?.y)! - 5 204 | } 205 | }, completion: { isFinished in 206 | if isFinished { 207 | self.isAnimation = false 208 | } 209 | }) 210 | } 211 | } 212 | } 213 | 214 | extension TGAlbumPhotoPreviewVC: UICollectionViewDelegate{ 215 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 216 | self.currentPage = Int(scrollView.contentOffset.x / self.view.bounds.width) 217 | } 218 | 219 | func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { 220 | changeCurrentToolbar() 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/TGPhotoPicker/TGAnimationButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TGAnimationButton.swift 3 | // TGAnimationButton 4 | // 5 | // Created by targetcloud on 2017/8/18. 6 | // Copyright © 2017年 targetcloud. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | enum TGAnimationButtonKind { 12 | case none 13 | case topToBottom 14 | case bottomToTop 15 | case leftToRight 16 | case rightToLeft 17 | case scale 18 | } 19 | 20 | class TGAnimationButton: UIButton { 21 | var animationKind: TGAnimationButtonKind = .topToBottom 22 | 23 | var borderColor: UIColor = UIColor.clear { 24 | didSet { 25 | layer.borderColor = borderColor.cgColor 26 | } 27 | } 28 | 29 | var borderWidth: CGFloat = 0 { 30 | didSet { 31 | layer.borderWidth = borderWidth 32 | } 33 | } 34 | 35 | var cornerRadius: CGFloat = 0 { 36 | didSet { 37 | layer.cornerRadius = cornerRadius 38 | } 39 | } 40 | 41 | var activityIndicatorViewStyle: UIActivityIndicatorViewStyle = .gray{ 42 | didSet{ 43 | indicatorV.activityIndicatorViewStyle = activityIndicatorViewStyle 44 | } 45 | } 46 | 47 | public override var isEnabled: Bool { 48 | didSet { 49 | guard animationKind != .none else { return } 50 | if oldValue != isEnabled { 51 | if oldValue { 52 | lastDisabledTitle = title(for: .disabled) 53 | loading(title: lastDisabledTitle) 54 | setTitle("", for: .disabled) 55 | } else { 56 | reset() 57 | setTitle(lastDisabledTitle, for: .disabled) 58 | } 59 | } 60 | } 61 | } 62 | 63 | lazy var backV = UIView() 64 | lazy var messageLbl = UILabel() 65 | lazy var indicatorV: UIActivityIndicatorView = { 66 | let indicator = UIActivityIndicatorView() 67 | indicator.hidesWhenStopped = true 68 | indicator.sizeToFit() 69 | indicator.transform = CGAffineTransform(scaleX: 0.6, y: 0.6) 70 | return indicator 71 | }() 72 | private var lastTitle: String? 73 | private var lastDisabledTitle: String? 74 | private var lastWidth: CGFloat? 75 | private var lsatHeight: CGFloat? 76 | 77 | private var transformY: CGFloat { 78 | return self.h * (animationKind == .topToBottom ? (-1) : (animationKind == .bottomToTop ? 1 : 0)) 79 | } 80 | 81 | private var transformX: CGFloat { 82 | return self.w * (animationKind == .leftToRight ? (-1) : (animationKind == .rightToLeft ? 1 : 0)) 83 | } 84 | 85 | override init(frame: CGRect) { 86 | super.init(frame: frame) 87 | setup() 88 | } 89 | 90 | required init?(coder aDecoder: NSCoder) { 91 | super.init(coder: aDecoder) 92 | setup() 93 | } 94 | 95 | private func setup() { 96 | layer.masksToBounds = true 97 | 98 | messageLbl.textColor = titleLabel?.textColor 99 | messageLbl.font = titleLabel?.font 100 | backV.addSubview(messageLbl) 101 | 102 | indicatorV.activityIndicatorViewStyle = activityIndicatorViewStyle 103 | backV.addSubview(indicatorV) 104 | 105 | backV.h = self.h 106 | backV.centerY = self.h * 0.5 107 | backV.backgroundColor = .clear 108 | backV.alpha = 0 109 | 110 | addSubview(backV) 111 | 112 | lastTitle = currentTitle 113 | lsatHeight = self.h 114 | lastWidth = self.w 115 | } 116 | 117 | private func loading(title: String?) { 118 | messageLbl.text = title 119 | messageLbl.textColor = self.titleColor(for: .disabled) 120 | messageLbl.shadowColor = self.titleShadowColor(for: .disabled) 121 | messageLbl.font = self.titleLabel?.font 122 | messageLbl.sizeToFit() 123 | 124 | indicatorV.centerY = backV.centerY 125 | indicatorV.x = (TGPhotoPickerConfig.shared.padding < 5) ? 5 : TGPhotoPickerConfig.shared.padding 126 | messageLbl.centerY = indicatorV.centerY 127 | messageLbl.left = indicatorV.right + ((TGPhotoPickerConfig.shared.padding < 5) ? 5 : TGPhotoPickerConfig.shared.padding) 128 | backV.right = messageLbl.right 129 | backV.w = messageLbl.right + ((TGPhotoPickerConfig.shared.padding < 5) ? 5 : TGPhotoPickerConfig.shared.padding) 130 | 131 | self.w = self.w < backV.w ? backV.w : self.w 132 | backV.left = (self.w - backV.w ) * 0.5 133 | 134 | indicatorV.startAnimating() 135 | backV.transform = (title == lastTitle) ? .identity : animationKind == .scale ? CGAffineTransform(scaleX: 0.5, y: 0.5) : CGAffineTransform(translationX: transformX, y: transformY) 136 | UIView.animate(withDuration: TGPhotoPickerConfig.shared.animateDuration) { 137 | self.titleLabel!.alpha = 0 138 | self.backV.alpha = 1 139 | self.backV.transform = .identity 140 | } 141 | } 142 | 143 | private func reset() { 144 | UIView.animate(withDuration: TGPhotoPickerConfig.shared.animateDuration, animations: { 145 | self.titleLabel!.alpha = 1 146 | self.backV.alpha = 0 147 | self.backV.transform = (self.currentTitle == self.lastDisabledTitle) ? .identity : self.animationKind == .scale ? CGAffineTransform(scaleX: 0.5, y: 0.5) : CGAffineTransform(translationX: 0, y: self.transformY) 148 | }) { (finished) in 149 | self.backV.transform = .identity 150 | self.indicatorV.stopAnimating() 151 | UIView.animate(withDuration: TGPhotoPickerConfig.shared.animateDuration, animations: { 152 | if self.currentTitle == self.lastDisabledTitle { 153 | self.w = self.lastWidth ?? self.w 154 | }else{ 155 | self.sizeToFit() 156 | self.w = self.w > (self.lastWidth ?? self.w) ? self.w : (self.lastWidth ?? self.w) 157 | self.h = self.lsatHeight ?? self.h 158 | } 159 | }) 160 | } 161 | } 162 | 163 | } 164 | -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/TGPhotoPicker/TGBottomBar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TGBottomBar.swift 3 | // TGPhotoPicker 4 | // 5 | // Created by targetcloud on 2017/7/22. 6 | // Copyright © 2017年 targetcloud. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @objc 12 | protocol TGBottomBarDelegate:class{ 13 | func onDoneButtonClicked() 14 | 15 | @objc optional 16 | func onOriginalButtonClicked(_ sender:TGAnimationButton) 17 | 18 | @objc optional 19 | func onPreviewButtonClicked(_ sender:TGAnimationButton) 20 | 21 | @objc optional 22 | func onReselectButtonClicked(_ sender:TGAnimationButton) 23 | 24 | @objc optional 25 | func onEditButtonClicked(_ sender:TGAnimationButton) 26 | } 27 | 28 | let padding = (TGPhotoPickerConfig.shared.padding * 2 > 10) ? 10 : (TGPhotoPickerConfig.shared.padding * 2 < 4 ? 4 : TGPhotoPickerConfig.shared.padding * 2) 29 | 30 | class TGBottomBar: UIView { 31 | var host: String?{ 32 | didSet{ 33 | if host == ("\(type(of: TGPhotoCollectionVC.self))" as NSString).components(separatedBy: ".").first!{ 34 | if TGPhotoPickerConfig.shared.isShowPreviewButton{ 35 | self.addSubview(previewButton) 36 | } 37 | 38 | if TGPhotoPickerConfig.shared.isShowReselect{ 39 | self.addSubview(reselectButton) 40 | } 41 | 42 | if TGPhotoPickerConfig.shared.isShowOriginal{ 43 | self.addSubview(originalButton) 44 | } 45 | 46 | var prevBtn:TGAnimationButton? 47 | for i in 0.. 0 154 | self.reselectButton.isEnabled = number > 0 155 | self.doneButton?.isEnabled = number > 0 156 | self.doneNumberContainer?.isHidden = true 157 | 158 | /* 159 | switch TGPhotoPickerConfig.shared.type { 160 | case .normal: 161 | self.numLabel?.text = String(number) 162 | self.doneNumberContainer?.isHidden = !(number > 0) 163 | if number > 0 && animation{ 164 | self.doneNumberAnimationLayer!.transform = CGAffineTransform(scaleX: 0.5, y: 0.5) 165 | UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 10, options: UIViewAnimationOptions.curveEaseIn, animations: { 166 | self.doneNumberAnimationLayer!.transform = CGAffineTransform.identity 167 | }, completion: nil) 168 | } 169 | case .wechat,.weibo: 170 | self.doneNumberContainer?.isHidden = true 171 | } 172 | */ 173 | 174 | if TGPhotoPickerConfig.shared.isShowIndicator{ 175 | let attributeString = NSMutableAttributedString(string:"\(number) / \(TGPhotoPickerConfig.shared.maxImageCount)") 176 | attributeString.addAttribute(NSFontAttributeName, 177 | value: UIFont.boldSystemFont(ofSize: TGPhotoPickerConfig.shared.fontSize+3), 178 | range: NSMakeRange(0,"\(number) ".characters.count)) 179 | 180 | attributeString.addAttribute(NSFontAttributeName, 181 | value: UIFont.boldSystemFont(ofSize: TGPhotoPickerConfig.shared.fontSize), 182 | range: NSMakeRange("\(number) ".characters.count,1)) 183 | 184 | attributeString.addAttribute(NSFontAttributeName, 185 | value: UIFont.systemFont(ofSize: TGPhotoPickerConfig.shared.fontSize-3), 186 | range: NSMakeRange("\(number) / ".characters.count,"\(TGPhotoPickerConfig.shared.maxImageCount)".characters.count)) 187 | indicatorLabel.attributedText = attributeString 188 | }else{ 189 | let addStr = number > 0 ? "("+String(number)+")" : "" 190 | self.doneButton!.setTitle(TGPhotoPickerConfig.shared.doneTitle + addStr, for: .normal) 191 | } 192 | } 193 | 194 | //没有加private,因为可能并不显示在工具条里 195 | lazy var indicatorLabel: UILabel = { 196 | let indicatorLbl = UILabel(frame: CGRect(x: 0, y: (self.height - TGPhotoPickerConfig.shared.toolBarH * 0.8) / 2, width: 0, height: TGPhotoPickerConfig.shared.toolBarH * 0.8)) 197 | indicatorLbl.isHidden = true 198 | indicatorLbl.text = "0 / \(TGPhotoPickerConfig.shared.maxImageCount)" 199 | indicatorLbl.font = UIFont.systemFont(ofSize: TGPhotoPickerConfig.shared.fontSize+1) 200 | indicatorLbl.layer.cornerRadius = TGPhotoPickerConfig.shared.doneButtonH * 0.15 201 | indicatorLbl.clipsToBounds = true 202 | indicatorLbl.textColor = .white 203 | indicatorLbl.textAlignment = .center 204 | indicatorLbl.backgroundColor = TGPhotoPickerConfig.shared.indicatorColor 205 | //if TGPhotoPickerConfig.shared.isShowBorder { 206 | indicatorLbl.layer.borderWidth = TGPhotoPickerConfig.shared.checkboxLineW 207 | indicatorLbl.layer.borderColor = UIColor.clear.cgColor 208 | //} 209 | indicatorLbl.sizeToFit() 210 | indicatorLbl.h = TGPhotoPickerConfig.shared.toolBarH * 0.8 211 | indicatorLbl.w = indicatorLbl.w < TGPhotoPickerConfig.shared.doneButtonW * 0.8 ? TGPhotoPickerConfig.shared.doneButtonW * 0.8 : indicatorLbl.w 212 | return indicatorLbl 213 | }() 214 | 215 | private lazy var previewButton: TGAnimationButton = { 216 | let previewBtn = TGAnimationButton(frame: CGRect(x: 0, y: (self.height - TGPhotoPickerConfig.shared.doneButtonH * 0.9) / 2, width: 0, height: TGPhotoPickerConfig.shared.doneButtonH * 0.9)) 217 | previewBtn.animationKind = .none 218 | previewBtn.setTitle(TGPhotoPickerConfig.shared.previewBottonTitle, for: .normal) 219 | previewBtn.titleLabel?.font = UIFont.systemFont(ofSize: TGPhotoPickerConfig.shared.fontSize-1) 220 | previewBtn.cornerRadius = TGPhotoPickerConfig.shared.doneButtonH * 0.1 221 | previewBtn.backgroundColor = .clear 222 | previewBtn.setTitleColor(UIColor.white, for: .normal) 223 | previewBtn.setTitleColor(TGPhotoPickerConfig.shared.disabledColor, for: .disabled) 224 | if TGPhotoPickerConfig.shared.isShowBorder { 225 | previewBtn.borderWidth = TGPhotoPickerConfig.shared.checkboxLineW 226 | previewBtn.borderColor = TGPhotoPickerConfig.shared.tinColor 227 | } 228 | previewBtn.sizeToFit() 229 | previewBtn.h = TGPhotoPickerConfig.shared.doneButtonH * 0.9 230 | previewBtn.w = previewBtn.w < TGPhotoPickerConfig.shared.doneButtonW * 0.8 ? TGPhotoPickerConfig.shared.doneButtonW * 0.8 : previewBtn.w 231 | previewBtn.addTarget(self, action: #selector(previewClicked), for: .touchUpInside) 232 | 233 | return previewBtn 234 | }() 235 | 236 | private lazy var reselectButton: TGAnimationButton = { 237 | let reselectBtn = TGAnimationButton(frame: CGRect(x: 0, y: (self.height - TGPhotoPickerConfig.shared.doneButtonH * 0.9) / 2, width: 0, height: TGPhotoPickerConfig.shared.doneButtonH * 0.9)) 238 | reselectBtn.animationKind = .none 239 | reselectBtn.setTitle(TGPhotoPickerConfig.shared.reselectTitle, for: .normal) 240 | reselectBtn.titleLabel?.font = UIFont.systemFont(ofSize: TGPhotoPickerConfig.shared.fontSize-1) 241 | reselectBtn.cornerRadius = TGPhotoPickerConfig.shared.doneButtonH * 0.1 242 | reselectBtn.backgroundColor = .clear 243 | reselectBtn.setTitleColor(UIColor.white, for: .normal) 244 | reselectBtn.setTitleColor(TGPhotoPickerConfig.shared.disabledColor, for: .disabled) 245 | if TGPhotoPickerConfig.shared.isShowBorder { 246 | reselectBtn.borderWidth = TGPhotoPickerConfig.shared.checkboxLineW 247 | reselectBtn.borderColor = TGPhotoPickerConfig.shared.tinColor 248 | } 249 | reselectBtn.sizeToFit() 250 | reselectBtn.h = TGPhotoPickerConfig.shared.doneButtonH * 0.9 251 | reselectBtn.w = reselectBtn.w < TGPhotoPickerConfig.shared.doneButtonW * 0.8 ? TGPhotoPickerConfig.shared.doneButtonW * 0.8 : reselectBtn.w 252 | reselectBtn.addTarget(self, action: #selector(reselectClicked), for: .touchUpInside) 253 | 254 | return reselectBtn 255 | }() 256 | 257 | private lazy var originalButton: TGAnimationButton = { 258 | let originalBtn = TGAnimationButton(frame: CGRect(x: 0, y: (self.height - TGPhotoPickerConfig.shared.doneButtonH * 0.9) / 2, width: 0, height: TGPhotoPickerConfig.shared.doneButtonH * 0.9)) 259 | originalBtn.setTitle(TGPhotoPickerConfig.shared.originalTitle, for: .normal) 260 | originalBtn.setTitle(TGPhotoPickerConfig.shared.originalTitle, for: .disabled) 261 | originalBtn.titleLabel?.font = UIFont.systemFont(ofSize: TGPhotoPickerConfig.shared.fontSize-1) 262 | originalBtn.cornerRadius = TGPhotoPickerConfig.shared.doneButtonH * 0.1 263 | originalBtn.backgroundColor = .clear 264 | originalBtn.setTitleColor(UIColor.white, for: .normal) 265 | originalBtn.setTitleColor(TGPhotoPickerConfig.shared.disabledColor, for: .disabled) 266 | if TGPhotoPickerConfig.shared.isShowBorder { 267 | originalBtn.borderWidth = TGPhotoPickerConfig.shared.checkboxLineW 268 | originalBtn.borderColor = TGPhotoPickerConfig.shared.tinColor 269 | } 270 | 271 | originalBtn.animationKind = .scale 272 | 273 | originalBtn.setImage(UIImage.size(CGSize(width: TGPhotoPickerConfig.shared.doneButtonH * 0.3, height: TGPhotoPickerConfig.shared.doneButtonH * 0.3)) 274 | .border(color: TGPhotoPickerConfig.shared.tinColor) 275 | .border(width: TGPhotoPickerConfig.shared.checkboxLineW) 276 | .corner(radius: TGPhotoPickerConfig.shared.doneButtonH * 0.15) 277 | .color(.clear).image, for: .normal) 278 | originalBtn.setImage(UIImage.size(CGSize(width: TGPhotoPickerConfig.shared.doneButtonH * 0.3, height: TGPhotoPickerConfig.shared.doneButtonH * 0.3)) 279 | .corner(radius: TGPhotoPickerConfig.shared.doneButtonH * 0.15) 280 | .color(TGPhotoPickerConfig.shared.tinColor).image, for: .selected) 281 | originalBtn.setImage(UIImage.size(CGSize(width: TGPhotoPickerConfig.shared.doneButtonH * 0.3, height: TGPhotoPickerConfig.shared.doneButtonH * 0.3)) 282 | .corner(radius: TGPhotoPickerConfig.shared.doneButtonH * 0.15) 283 | .color(TGPhotoPickerConfig.shared.tinColor).image, for: .highlighted) 284 | 285 | originalBtn.sizeToFit() 286 | originalBtn.h = TGPhotoPickerConfig.shared.doneButtonH * 0.9 287 | originalBtn.w = originalBtn.w < TGPhotoPickerConfig.shared.doneButtonW * 0.8 ? TGPhotoPickerConfig.shared.doneButtonW * 0.8 : originalBtn.w 288 | originalBtn.addTarget(self, action: #selector(originalClicked), for: .touchUpInside) 289 | 290 | return originalBtn 291 | }() 292 | 293 | private lazy var editButton: TGAnimationButton = { 294 | let editBtn = TGAnimationButton(frame: CGRect(x: padding, y: (self.height - TGPhotoPickerConfig.shared.doneButtonH * 0.9) / 2, width: 0, height: TGPhotoPickerConfig.shared.doneButtonH * 0.9)) 295 | editBtn.animationKind = .none 296 | editBtn.setTitle(TGPhotoPickerConfig.shared.editButtonTitle, for: .normal) 297 | editBtn.titleLabel?.font = UIFont.systemFont(ofSize: TGPhotoPickerConfig.shared.fontSize-1) 298 | editBtn.cornerRadius = TGPhotoPickerConfig.shared.doneButtonH * 0.1 299 | editBtn.backgroundColor = .clear 300 | editBtn.setTitleColor(UIColor.white, for: .normal) 301 | editBtn.setTitleColor(TGPhotoPickerConfig.shared.disabledColor, for: .disabled) 302 | if TGPhotoPickerConfig.shared.isShowBorder { 303 | editBtn.borderWidth = TGPhotoPickerConfig.shared.checkboxLineW 304 | editBtn.borderColor = TGPhotoPickerConfig.shared.tinColor 305 | } 306 | editBtn.sizeToFit() 307 | editBtn.h = TGPhotoPickerConfig.shared.doneButtonH * 0.9 308 | editBtn.w = editBtn.w < TGPhotoPickerConfig.shared.doneButtonW * 0.8 ? TGPhotoPickerConfig.shared.doneButtonW * 0.8 : editBtn.w 309 | editBtn.addTarget(self, action: #selector(editClicked), for: .touchUpInside) 310 | 311 | return editBtn 312 | }() 313 | 314 | @objc private func editClicked(_ sender:TGAnimationButton){ 315 | self.delegate?.onEditButtonClicked?(sender) 316 | } 317 | 318 | @objc private func originalClicked(_ sender:TGAnimationButton){ 319 | self.delegate?.onOriginalButtonClicked?(sender) 320 | } 321 | 322 | @objc private func previewClicked(_ sender:TGAnimationButton){ 323 | self.delegate?.onPreviewButtonClicked?(sender) 324 | } 325 | 326 | @objc private func reselectClicked(_ sender:TGAnimationButton){ 327 | self.delegate?.onReselectButtonClicked?(sender) 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/TGPhotoPicker/TGCameraVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TGCameraVC.swift 3 | // TGPhotoPicker 4 | // 5 | // Created by targetcloud on 2017/7/25. 6 | // Copyright © 2017年 targetcloud. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | import Photos 12 | 13 | @available(iOS 10.0, *) 14 | class TGCameraVC: UIViewController { 15 | 16 | var callbackPicutureData: ((Data?) -> ())? 17 | 18 | private var device: AVCaptureDevice? 19 | private var input: AVCaptureDeviceInput? 20 | private var imageOutput: AVCapturePhotoOutput? 21 | private var session: AVCaptureSession? 22 | private var previewLayer: AVCaptureVideoPreviewLayer? 23 | fileprivate var showImageContainerView: UIView? 24 | fileprivate var showImageView: UIImageView? 25 | fileprivate var picData: Data? 26 | private var flashMode: AVCaptureFlashMode = .auto 27 | private weak var flashButton: UIButton? 28 | 29 | override func viewDidLoad() { 30 | super.viewDidLoad() 31 | 32 | TGPhotoPickerManager.shared.authorizeCamera { (status) in 33 | if status == .authorized{ 34 | self.setupCamera() 35 | self.setupUI() 36 | } 37 | } 38 | 39 | if #available(iOS 9.0, *) { 40 | let isVCBased = Bundle.main.infoDictionary?["UIViewControllerBasedStatusBarAppearance"] as? Bool ?? false 41 | if !isVCBased{ 42 | UIApplication.shared.setStatusBarHidden(false, with: .none) 43 | } 44 | }else { 45 | UIApplication.shared.statusBarStyle = .lightContent 46 | UIApplication.shared.setStatusBarHidden(false, with: .none) 47 | } 48 | } 49 | 50 | override var prefersStatusBarHidden: Bool{ 51 | return false 52 | } 53 | 54 | override var preferredStatusBarStyle: UIStatusBarStyle { 55 | return .lightContent 56 | } 57 | 58 | private func setupCamera() { 59 | AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeVideo) { success in 60 | if !success { 61 | let alertVC = UIAlertController(title: TGPhotoPickerConfig.shared.cameraUsage, message: TGPhotoPickerConfig.shared.cameraUsageTip, preferredStyle: .actionSheet) 62 | alertVC.addAction(UIAlertAction(title: TGPhotoPickerConfig.shared.confirmTitle, style: .default, handler: nil)) 63 | self.present(alertVC, animated: true, completion: nil) 64 | } 65 | } 66 | device = cameraWithPosistion(.back) 67 | input = try? AVCaptureDeviceInput(device: device) 68 | guard input != nil else { 69 | return 70 | } 71 | 72 | imageOutput = AVCapturePhotoOutput() 73 | session = AVCaptureSession() 74 | session?.beginConfiguration() 75 | session?.sessionPreset = TGPhotoPickerConfig.shared.sessionPreset 76 | if session!.canAddInput(input) { 77 | session!.addInput(input) 78 | } 79 | if session!.canAddOutput(imageOutput) { 80 | session!.addOutput(imageOutput) 81 | } 82 | previewLayer = AVCaptureVideoPreviewLayer(session: session) 83 | previewLayer?.frame = view.bounds 84 | previewLayer?.videoGravity = TGPhotoPickerConfig.shared.videoGravity 85 | view.layer.addSublayer(previewLayer!) 86 | session?.commitConfiguration() 87 | session?.startRunning() 88 | } 89 | 90 | private func cameraWithPosistion(_ position: AVCaptureDevicePosition) -> AVCaptureDevice { 91 | let type = AVCaptureDeviceType(rawValue: TGPhotoPickerConfig.shared.captureDeviceType.rawValue) 92 | return AVCaptureDevice.defaultDevice(withDeviceType: type, mediaType: AVMediaTypeVideo, position: position) 93 | } 94 | 95 | private func setupUI() { 96 | let takeButton = UIButton(frame: CGRect(x: 0, y: 0, width: TGPhotoPickerConfig.shared.takeWH, height: TGPhotoPickerConfig.shared.takeWH)) 97 | takeButton.center = CGPoint(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height - TGPhotoPickerConfig.shared.buttonEdge.bottom) 98 | takeButton.setImage(UIImage.size(width: TGPhotoPickerConfig.shared.takeWH, height: TGPhotoPickerConfig.shared.takeWH).border(width: 3).border(color: .white).color(.clear).corner(radius: TGPhotoPickerConfig.shared.takeWH / 2).image + 99 | UIImage.size(width: TGPhotoPickerConfig.shared.takeWH - 10, height: TGPhotoPickerConfig.shared.takeWH - 10).color(UIColor(white: 0.95, alpha: 1) ).corner(radius: (TGPhotoPickerConfig.shared.takeWH - 10) / 2).image, for: .normal) 100 | takeButton.setImage(UIImage.size(width: TGPhotoPickerConfig.shared.takeWH, height: TGPhotoPickerConfig.shared.takeWH).border(width: 3).border(color: .white).color(.clear).corner(radius: TGPhotoPickerConfig.shared.takeWH / 2).image + 101 | UIImage.size(width: TGPhotoPickerConfig.shared.takeWH - 10, height: TGPhotoPickerConfig.shared.takeWH - 10).color(UIColor(white: 0.8, alpha: 1) ).corner(radius: (TGPhotoPickerConfig.shared.takeWH - 10) / 2).image, for: .highlighted) 102 | takeButton.addTarget(self, action: #selector(takePhotoAction), for: .touchUpInside) 103 | view.addSubview(takeButton) 104 | 105 | let cameraChangeButton = UIButton(frame: CGRect(x: 0, y: 0, width: TGPhotoPickerConfig.shared.takeWH * 0.6, height: TGPhotoPickerConfig.shared.takeWH * 0.6)) 106 | cameraChangeButton.setImage(TGPhotoPickerConfig.getImageNo2x3xSuffix("camera"), for: .normal) 107 | cameraChangeButton.center = CGPoint(x: UIScreen.main.bounds.width - TGPhotoPickerConfig.shared.buttonEdge.right, y: takeButton.center.y) 108 | cameraChangeButton.addTarget(self, action: #selector(changeCameraPositionAction), for: .touchUpInside) 109 | cameraChangeButton.contentMode = .scaleAspectFit 110 | view.addSubview(cameraChangeButton) 111 | 112 | let flashChangeButton = UIButton(frame: CGRect(x: 0, y: 0, width: TGPhotoPickerConfig.shared.takeWH * 0.5, height: TGPhotoPickerConfig.shared.takeWH * 0.5)) 113 | flashChangeButton.center = CGPoint(x: cameraChangeButton.center.x, y: TGPhotoPickerConfig.shared.buttonEdge.top) 114 | flashChangeButton.setImage(TGPhotoPickerConfig.getImageNo2x3xSuffix("flashauto"), for: .normal) 115 | flashChangeButton.addTarget(self, action: #selector(flashChangeAction), for: .touchUpInside) 116 | flashChangeButton.contentMode = .scaleAspectFit 117 | flashButton = flashChangeButton 118 | view.addSubview(flashChangeButton) 119 | 120 | let backButton = UIButton(frame: CGRect(x: 0, y: 0, width: TGPhotoPickerConfig.shared.takeWH * 0.4, height: TGPhotoPickerConfig.shared.takeWH * 0.4)) 121 | backButton.center = CGPoint(x: TGPhotoPickerConfig.shared.buttonEdge.left , y: flashChangeButton.center.y) 122 | backButton.setImage(UIImage.size(width: TGPhotoPickerConfig.shared.takeWH * 0.4, height: TGPhotoPickerConfig.shared.takeWH * 0.4) 123 | .corner(radius: TGPhotoPickerConfig.shared.takeWH * 0.2) 124 | .color(.clear) 125 | .border(color: UIColor.white.withAlphaComponent(0.7)) 126 | .border(width: TGPhotoPickerConfig.shared.isShowBorder ? TGPhotoPickerConfig.shared.checkboxLineW : 0) 127 | .image 128 | .with({ context in 129 | context.setLineCap(.round) 130 | UIColor.white.setStroke() 131 | context.setLineWidth(TGPhotoPickerConfig.shared.checkboxLineW) 132 | let WH = TGPhotoPickerConfig.shared.takeWH * 0.4 133 | context.move(to: CGPoint(x: WH * 0.6, y: WH * 0.2)) 134 | context.addLine(to: CGPoint(x: WH * 0.35, y: WH * 0.5)) 135 | context.move(to: CGPoint(x: WH * 0.35, y: WH * 0.5)) 136 | context.addLine(to: CGPoint(x: WH * 0.6, y: WH * 0.8)) 137 | context.strokePath() 138 | }), for: .normal) 139 | backButton.contentMode = .scaleAspectFit 140 | backButton.addTarget(self, action: #selector(backAction), for: .touchUpInside) 141 | view.addSubview(backButton) 142 | 143 | showImageContainerView = UIView(frame: view.bounds) 144 | showImageContainerView?.backgroundColor = TGPhotoPickerConfig.shared.previewBGColor 145 | view.addSubview(showImageContainerView!) 146 | 147 | let height = showImageContainerView!.bounds.height - TGPhotoPickerConfig.shared.takeWH - TGPhotoPickerConfig.shared.buttonEdge.bottom - TGPhotoPickerConfig.shared.previewPadding * 2 148 | showImageView = UIImageView(frame: CGRect(x: TGPhotoPickerConfig.shared.previewPadding, y: TGPhotoPickerConfig.shared.previewPadding * 2, width: showImageContainerView!.bounds.width - 2 * TGPhotoPickerConfig.shared.previewPadding, height: height)) 149 | showImageView?.contentMode = .scaleAspectFit 150 | showImageContainerView?.addSubview(showImageView!) 151 | showImageContainerView?.isHidden = true 152 | 153 | let giveupButton = createImageOperatorButton(nil, CGPoint(x: TGPhotoPickerConfig.shared.takeWH * 1.5, y: showImageContainerView!.bounds.height - TGPhotoPickerConfig.shared.takeWH * 1.5), TGPhotoPickerConfig.shared.getCheckboxImage(true, true, .circle, TGPhotoPickerConfig.shared.takeWH * 0.7).unselect) 154 | giveupButton.addTarget(self, action: #selector(giveupImageAction), for: .touchUpInside) 155 | showImageContainerView?.addSubview(giveupButton) 156 | 157 | let ensureButton = createImageOperatorButton(nil, CGPoint(x: showImageContainerView!.bounds.width - TGPhotoPickerConfig.shared.takeWH * 1.5, y: showImageContainerView!.bounds.height - TGPhotoPickerConfig.shared.takeWH * 1.5), TGPhotoPickerConfig.shared.getCheckboxImage(true, false, .circle, TGPhotoPickerConfig.shared.takeWH * 0.7).select) 158 | ensureButton.addTarget(self, action: #selector(useImageAction), for: .touchUpInside) 159 | showImageContainerView?.addSubview(ensureButton) 160 | } 161 | 162 | private func createImageOperatorButton(_ title: String?, _ center: CGPoint, _ img: UIImage?) -> UIButton { 163 | let btn = UIButton(frame: CGRect(x: 0, y: 0, width: TGPhotoPickerConfig.shared.takeWH * 0.7, height: TGPhotoPickerConfig.shared.takeWH * 0.7)) 164 | btn.center = center 165 | btn.setTitle(title, for: .normal) 166 | btn.setImage(img, for: .normal) 167 | btn.contentMode = .scaleAspectFit 168 | return btn 169 | } 170 | 171 | @objc private func flashChangeAction() { 172 | switch flashMode { 173 | case .auto: 174 | flashMode = .on 175 | flashButton?.setImage(TGPhotoPickerConfig.getImageNo2x3xSuffix("flash"), for: .normal) 176 | case .on: 177 | flashMode = .off 178 | flashButton?.setImage(TGPhotoPickerConfig.getImageNo2x3xSuffix("flashno"), for: .normal) 179 | case .off: 180 | flashMode = .auto 181 | flashButton?.setImage(TGPhotoPickerConfig.getImageNo2x3xSuffix("flashauto"), for: .normal) 182 | } 183 | } 184 | 185 | @objc private func backAction() { 186 | dismiss(animated: true, completion: nil) 187 | } 188 | 189 | @objc private func takePhotoAction() { 190 | let connection = imageOutput?.connection(withMediaType: AVMediaTypeVideo) 191 | guard connection != nil else { 192 | return 193 | } 194 | let photoSettings = AVCapturePhotoSettings() 195 | photoSettings.flashMode = flashMode 196 | imageOutput?.capturePhoto(with: photoSettings, delegate: self) 197 | } 198 | 199 | @objc private func changeCameraPositionAction() { 200 | let animation = CATransition() 201 | animation.duration = 0.5 202 | animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) 203 | animation.type = TGPhotoPickerConfig.shared.transitionType 204 | 205 | let newDevice: AVCaptureDevice! 206 | let newInput: AVCaptureDeviceInput? 207 | let position = input?.device.position 208 | if position == .front { 209 | newDevice = cameraWithPosistion(.back) 210 | animation.subtype = kCATransitionFromLeft 211 | } else { 212 | newDevice = cameraWithPosistion(.front) 213 | animation.subtype = kCATransitionFromRight 214 | } 215 | newInput = try? AVCaptureDeviceInput(device: newDevice) 216 | guard newInput != nil else{ 217 | return 218 | } 219 | 220 | previewLayer?.add(animation, forKey: nil) 221 | 222 | session?.beginConfiguration() 223 | session?.removeInput(input) 224 | if session!.canAddInput(newInput) { 225 | session?.addInput(newInput!) 226 | input = newInput 227 | } else { 228 | session?.addInput(input) 229 | } 230 | session?.commitConfiguration() 231 | } 232 | 233 | @objc private func giveupImageAction() { 234 | showImageView?.image = UIImage() 235 | showImageContainerView?.isHidden = true 236 | } 237 | 238 | @objc private func useImageAction() { 239 | callbackPicutureData?(picData) 240 | dismiss(animated: true, completion: nil) 241 | } 242 | } 243 | 244 | @available(iOS 10.0, *) 245 | extension TGCameraVC: AVCapturePhotoCaptureDelegate { 246 | func capture(_ captureOutput: AVCapturePhotoOutput, didFinishProcessingPhotoSampleBuffer photoSampleBuffer: CMSampleBuffer?, previewPhotoSampleBuffer: CMSampleBuffer?, resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Error?) { 247 | if error != nil { 248 | print("error = \(String(describing: error?.localizedDescription))") 249 | } else { 250 | if let imageData = AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: photoSampleBuffer!, previewPhotoSampleBuffer: previewPhotoSampleBuffer){ 251 | picData = imageData 252 | showImageContainerView?.isHidden = false 253 | showImageView?.image = UIImage(data: imageData) 254 | if TGPhotoPickerConfig.shared.saveImageToPhotoAlbum{ 255 | self.saveImageToPhotoAlbum(UIImage(data: imageData)!) 256 | } 257 | } 258 | } 259 | } 260 | 261 | fileprivate func saveImageToPhotoAlbum(_ savedImage:UIImage){ 262 | canUseAlbum { (canUse) in 263 | if canUse{ 264 | UIImageWriteToSavedPhotosAlbum(savedImage, self, #selector(self.imageDidFinishSavingWithErrorContextInfo), nil) 265 | } 266 | } 267 | } 268 | 269 | @objc fileprivate func imageDidFinishSavingWithErrorContextInfo(image:UIImage,error:NSError?,contextInfo:UnsafeMutableRawPointer?){ 270 | canUseAlbum { (canUse) in 271 | if canUse{ 272 | let msg = (error != nil) ? (TGPhotoPickerConfig.shared.saveImageFailTip+"("+(error?.localizedDescription)!+")") : TGPhotoPickerConfig.shared.saveImageSuccessTip 273 | if !TGPhotoPickerConfig.shared.showCameraSaveSuccess && error == nil{ 274 | return 275 | } 276 | let alert = UIAlertView(title: TGPhotoPickerConfig.shared.saveImageTip, message: msg, delegate: self, cancelButtonTitle: TGPhotoPickerConfig.shared.confirmTitle) 277 | alert.show() 278 | } 279 | } 280 | } 281 | 282 | fileprivate func canUseAlbum(returnClosure:@escaping (Bool)-> ()){ 283 | TGPhotoPickerManager.shared.authorizePhotoLibrary { (status) in 284 | returnClosure(status == .authorized) 285 | } 286 | /* 287 | if PHPhotoLibrary.authorizationStatus() != PHAuthorizationStatus.authorized { 288 | let alertView = UIAlertView(title: TGPhotoPickerConfig.shared.PhotoLibraryUsage, message: TGPhotoPickerConfig.shared.PhotoLibraryUsageTip, delegate: nil, cancelButtonTitle: TGPhotoPickerConfig.shared.confirmTitle, otherButtonTitles: TGPhotoPickerConfig.shared.cancelTitle) 289 | alertView.tag = TGPhotoPickerConfig.shared.alertViewTag 290 | alertView.show() 291 | return false 292 | }else{ 293 | return true 294 | } 295 | */ 296 | } 297 | } 298 | 299 | -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/TGPhotoPicker/TGCameraVCForiOS8.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TGCameraVCForiOS8.swift 3 | // TGPhotoPicker 4 | // 5 | // Created by targetcloud on 2017/8/11. 6 | // Copyright © 2017年 targetcloud. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | import Photos 12 | 13 | class TGCameraVCForiOS8: UIViewController { 14 | 15 | var callbackPicutureData: ((Data?) -> ())? 16 | 17 | fileprivate var device: AVCaptureDevice? 18 | fileprivate lazy var session : AVCaptureSession = AVCaptureSession() 19 | fileprivate lazy var previewLayer : AVCaptureVideoPreviewLayer = AVCaptureVideoPreviewLayer(session: self.session) 20 | fileprivate lazy var imageOutput : AVCaptureStillImageOutput = AVCaptureStillImageOutput() 21 | fileprivate var input : AVCaptureDeviceInput? 22 | fileprivate var showImageContainerView: UIView? 23 | fileprivate var showImageView: UIImageView? 24 | fileprivate var flashMode: AVCaptureFlashMode = .auto 25 | fileprivate var picData: Data? 26 | fileprivate var image: UIImage? 27 | 28 | fileprivate lazy var takeButton: UIButton = { 29 | let takeButton = UIButton(frame: CGRect(x: 0, y: 0, width: TGPhotoPickerConfig.shared.takeWH, height: TGPhotoPickerConfig.shared.takeWH)) 30 | takeButton.center = CGPoint(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height - TGPhotoPickerConfig.shared.buttonEdge.bottom) 31 | takeButton.setImage(UIImage.size(width: TGPhotoPickerConfig.shared.takeWH, height: TGPhotoPickerConfig.shared.takeWH).border(width: 3).border(color: .white).color(.clear).corner(radius: TGPhotoPickerConfig.shared.takeWH / 2).image + 32 | UIImage.size(width: TGPhotoPickerConfig.shared.takeWH - 10, height: TGPhotoPickerConfig.shared.takeWH - 10).color(UIColor(white: 0.95, alpha: 1) ).corner(radius: (TGPhotoPickerConfig.shared.takeWH - 10) / 2).image, for: .normal) 33 | takeButton.setImage(UIImage.size(width: TGPhotoPickerConfig.shared.takeWH, height: TGPhotoPickerConfig.shared.takeWH).border(width: 3).border(color: .white).color(.clear).corner(radius: TGPhotoPickerConfig.shared.takeWH / 2).image + 34 | UIImage.size(width: TGPhotoPickerConfig.shared.takeWH - 10, height: TGPhotoPickerConfig.shared.takeWH - 10).color(UIColor(white: 0.8, alpha: 1) ).corner(radius: (TGPhotoPickerConfig.shared.takeWH - 10) / 2).image, for: .highlighted) 35 | takeButton.addTarget(self, action: #selector(takePhotoAction), for: .touchUpInside) 36 | return takeButton 37 | }() 38 | 39 | fileprivate lazy var cameraChangeButton: UIButton = { 40 | let cameraChangeButton = UIButton(frame: CGRect(x: 0, y: 0, width: TGPhotoPickerConfig.shared.takeWH * 0.6, height: TGPhotoPickerConfig.shared.takeWH * 0.6)) 41 | cameraChangeButton.setImage(TGPhotoPickerConfig.getImageNo2x3xSuffix("camera"), for: .normal) 42 | cameraChangeButton.center = CGPoint(x: UIScreen.main.bounds.width - TGPhotoPickerConfig.shared.buttonEdge.right, y: self.takeButton.center.y) 43 | cameraChangeButton.addTarget(self, action: #selector(changeCameraPositionAction), for: .touchUpInside) 44 | cameraChangeButton.contentMode = .scaleAspectFit 45 | return cameraChangeButton 46 | }() 47 | 48 | fileprivate lazy var flashButton: UIButton = { 49 | let flashChangeButton = UIButton(frame: CGRect(x: 0, y: 0, width: TGPhotoPickerConfig.shared.takeWH * 0.5, height: TGPhotoPickerConfig.shared.takeWH * 0.5)) 50 | flashChangeButton.center = CGPoint(x: self.cameraChangeButton.center.x, y: TGPhotoPickerConfig.shared.buttonEdge.top) 51 | flashChangeButton.setImage(TGPhotoPickerConfig.getImageNo2x3xSuffix("flashauto"), for: .normal) 52 | flashChangeButton.addTarget(self, action: #selector(flashChangeAction), for: .touchUpInside) 53 | flashChangeButton.contentMode = .scaleAspectFit 54 | return flashChangeButton 55 | }() 56 | 57 | @objc fileprivate func flashChangeAction(){ 58 | guard let device = device else { 59 | return 60 | } 61 | do { 62 | try device.lockForConfiguration() 63 | switch flashMode { 64 | case .auto: 65 | if device.isFlashModeSupported(.on) { 66 | device.flashMode = .on 67 | flashMode = .on 68 | flashButton.setImage(TGPhotoPickerConfig.getImageNo2x3xSuffix("flash"), for: .normal) 69 | } 70 | case .on: 71 | if device.isFlashModeSupported(.off) { 72 | device.flashMode = .off 73 | flashMode = .off 74 | flashButton.setImage(TGPhotoPickerConfig.getImageNo2x3xSuffix("flashno"), for: .normal) 75 | } 76 | case .off: 77 | if device.isFlashModeSupported(.auto) { 78 | device.flashMode = .auto 79 | flashMode = .auto 80 | flashButton.setImage(TGPhotoPickerConfig.getImageNo2x3xSuffix("flashauto"), for: .normal) 81 | } 82 | } 83 | device.unlockForConfiguration() 84 | } catch { 85 | return 86 | } 87 | } 88 | 89 | fileprivate func canUseCamera(returnClosure:@escaping (Bool)->()){ 90 | TGPhotoPickerManager.shared.authorizePhotoLibrary { (status) in 91 | returnClosure(status == .authorized) 92 | } 93 | /* 94 | let authStatus = AVCaptureDevice.authorizationStatus(forMediaType: AVMediaTypeVideo) 95 | if authStatus == .denied{ 96 | let alertView = UIAlertView(title: TGPhotoPickerConfig.shared.cameraUsage, message: TGPhotoPickerConfig.shared.cameraUsageTip, delegate: self, cancelButtonTitle: TGPhotoPickerConfig.shared.confirmTitle, otherButtonTitles: TGPhotoPickerConfig.shared.cancelTitle) 97 | alertView.tag = TGPhotoPickerConfig.shared.alertViewTag 98 | alertView.show() 99 | return false 100 | }else{ 101 | return true 102 | } 103 | */ 104 | } 105 | 106 | fileprivate func canUseAlbum(returnClosure:@escaping (Bool)->()){ 107 | TGPhotoPickerManager.shared.authorizePhotoLibrary { (status) in 108 | returnClosure(status == .authorized) 109 | } 110 | /* 111 | if PHPhotoLibrary.authorizationStatus() != PHAuthorizationStatus.authorized { 112 | let alertView = UIAlertView(title: TGPhotoPickerConfig.shared.PhotoLibraryUsage, message: TGPhotoPickerConfig.shared.PhotoLibraryUsageTip, delegate: self, cancelButtonTitle: TGPhotoPickerConfig.shared.confirmTitle, otherButtonTitles: TGPhotoPickerConfig.shared.cancelTitle) 113 | alertView.tag = TGPhotoPickerConfig.shared.alertViewTag 114 | alertView.show() 115 | return false 116 | }else{ 117 | return true 118 | } 119 | */ 120 | } 121 | 122 | fileprivate lazy var focusView: UIView = { 123 | let focusView = UIView(frame: CGRect(x: 0, y: 0, width: TGPhotoPickerConfig.shared.focusViewWH, height: TGPhotoPickerConfig.shared.focusViewWH)) 124 | focusView.layer.borderWidth = 1.0 125 | focusView.layer.borderColor = TGPhotoPickerConfig.shared.tinColor.cgColor 126 | focusView.backgroundColor = .clear 127 | focusView.isHidden = true 128 | return focusView 129 | }() 130 | 131 | override func viewDidLoad() { 132 | super.viewDidLoad() 133 | canUseCamera { (canUse) in 134 | if canUse{ 135 | self.setupCamera() 136 | self.setupUI() 137 | } 138 | } 139 | 140 | if #available(iOS 9.0, *) { 141 | let isVCBased = Bundle.main.infoDictionary?["UIViewControllerBasedStatusBarAppearance"] as? Bool ?? false 142 | if !isVCBased{ 143 | UIApplication.shared.setStatusBarHidden(false, with: .none) 144 | } 145 | }else { 146 | UIApplication.shared.statusBarStyle = .lightContent 147 | UIApplication.shared.setStatusBarHidden(false, with: .none) 148 | } 149 | } 150 | 151 | override var prefersStatusBarHidden: Bool{ 152 | return false 153 | } 154 | 155 | override var preferredStatusBarStyle: UIStatusBarStyle { 156 | return .lightContent 157 | } 158 | 159 | fileprivate func setupUI(){ 160 | self.view.addSubview(self.takeButton) 161 | self.view.addSubview(self.focusView) 162 | self.view.addSubview(self.cameraChangeButton) 163 | self.view.addSubview(self.flashButton) 164 | 165 | let backButton = UIButton(frame: CGRect(x: 0, y: 0, width: TGPhotoPickerConfig.shared.takeWH * 0.4, height: TGPhotoPickerConfig.shared.takeWH * 0.4)) 166 | backButton.center = CGPoint(x: TGPhotoPickerConfig.shared.buttonEdge.left , y: self.flashButton.center.y) 167 | backButton.setImage(UIImage.size(width: TGPhotoPickerConfig.shared.takeWH * 0.4, height: TGPhotoPickerConfig.shared.takeWH * 0.4) 168 | .corner(radius: TGPhotoPickerConfig.shared.takeWH * 0.2) 169 | .color(.clear) 170 | .border(color: UIColor.white.withAlphaComponent(0.7)) 171 | .border(width: TGPhotoPickerConfig.shared.isShowBorder ? TGPhotoPickerConfig.shared.checkboxLineW : 0) 172 | .image 173 | .with({ context in 174 | context.setLineCap(.round) 175 | UIColor.white.setStroke() 176 | context.setLineWidth(TGPhotoPickerConfig.shared.checkboxLineW) 177 | let WH = TGPhotoPickerConfig.shared.takeWH * 0.4 178 | context.move(to: CGPoint(x: WH * 0.6, y: WH * 0.2)) 179 | context.addLine(to: CGPoint(x: WH * 0.35, y: WH * 0.5)) 180 | context.move(to: CGPoint(x: WH * 0.35, y: WH * 0.5)) 181 | context.addLine(to: CGPoint(x: WH * 0.6, y: WH * 0.8)) 182 | context.strokePath() 183 | }), for: .normal) 184 | backButton.contentMode = .scaleAspectFit 185 | backButton.addTarget(self, action: #selector(backAction), for: .touchUpInside) 186 | view.addSubview(backButton) 187 | 188 | showImageContainerView = UIView(frame: view.bounds) 189 | showImageContainerView?.backgroundColor = TGPhotoPickerConfig.shared.previewBGColor 190 | view.addSubview(showImageContainerView!) 191 | 192 | let height = showImageContainerView!.bounds.height - TGPhotoPickerConfig.shared.takeWH - TGPhotoPickerConfig.shared.buttonEdge.bottom - TGPhotoPickerConfig.shared.previewPadding * 2 193 | showImageView = UIImageView(frame: CGRect(x: TGPhotoPickerConfig.shared.previewPadding, y: TGPhotoPickerConfig.shared.previewPadding * 2, width: showImageContainerView!.bounds.width - 2 * TGPhotoPickerConfig.shared.previewPadding, height: height)) 194 | showImageView?.layer.masksToBounds = true 195 | showImageView?.contentMode = .scaleAspectFit 196 | showImageContainerView?.addSubview(showImageView!) 197 | showImageContainerView?.isHidden = true 198 | 199 | let giveupButton = createImageOperatorButton(nil, CGPoint(x: TGPhotoPickerConfig.shared.takeWH * 1.5, y: showImageContainerView!.bounds.height - TGPhotoPickerConfig.shared.takeWH * 1.5), TGPhotoPickerConfig.shared.getCheckboxImage(true, true, .circle, TGPhotoPickerConfig.shared.takeWH * 0.7).unselect) 200 | giveupButton.addTarget(self, action: #selector(giveupImageAction), for: .touchUpInside) 201 | showImageContainerView?.addSubview(giveupButton) 202 | 203 | let ensureButton = createImageOperatorButton(nil, CGPoint(x: showImageContainerView!.bounds.width - TGPhotoPickerConfig.shared.takeWH * 1.5, y: showImageContainerView!.bounds.height - TGPhotoPickerConfig.shared.takeWH * 1.5), TGPhotoPickerConfig.shared.getCheckboxImage(true, false, .circle, TGPhotoPickerConfig.shared.takeWH * 0.7).select) 204 | ensureButton.addTarget(self, action: #selector(useImageAction), for: .touchUpInside) 205 | showImageContainerView?.addSubview(ensureButton) 206 | 207 | let tapGesture = UITapGestureRecognizer(target: self, action: #selector(focusGesture)) 208 | self.view.addGestureRecognizer(tapGesture) 209 | } 210 | 211 | private func createImageOperatorButton(_ title: String?, _ center: CGPoint, _ img: UIImage?) -> UIButton { 212 | let btn = UIButton(frame: CGRect(x: 0, y: 0, width: TGPhotoPickerConfig.shared.takeWH * 0.7, height: TGPhotoPickerConfig.shared.takeWH * 0.7)) 213 | btn.center = center 214 | btn.setTitle(title, for: .normal) 215 | btn.setImage(img, for: .normal) 216 | btn.contentMode = .scaleAspectFit 217 | return btn 218 | } 219 | 220 | @objc private func backAction() { 221 | dismiss(animated: true, completion: nil) 222 | } 223 | 224 | @objc private func giveupImageAction() { 225 | showImageView?.image = UIImage() 226 | showImageContainerView?.isHidden = true 227 | } 228 | 229 | @objc private func useImageAction() { 230 | callbackPicutureData?(picData) 231 | dismiss(animated: true, completion: nil) 232 | } 233 | 234 | @objc fileprivate func focusGesture(gesture: UITapGestureRecognizer){ 235 | let point = gesture.location(in: gesture.view) 236 | focusAtPoint(point) 237 | } 238 | 239 | fileprivate func focusAtPoint(_ point: CGPoint){ 240 | let size = self.view.bounds.size 241 | let focusPoint = CGPoint(x: point.y/size.height, y: 1-point.x/size.width) 242 | do { 243 | try device?.lockForConfiguration() 244 | } catch { 245 | return 246 | } 247 | if device?.isFocusModeSupported(AVCaptureFocusMode.autoFocus) ?? false{ 248 | device?.focusPointOfInterest = focusPoint 249 | device?.focusMode = .autoFocus 250 | } 251 | if device?.isExposureModeSupported(AVCaptureExposureMode.autoExpose) ?? false{ 252 | device?.exposurePointOfInterest = focusPoint 253 | device?.exposureMode = .autoExpose 254 | } 255 | device?.unlockForConfiguration() 256 | focusView.center = point 257 | focusView.isHidden = false 258 | UIView.animate(withDuration: 0.3, animations: { 259 | self.focusView.transform = CGAffineTransform(scaleX: 1.25, y: 1.25) 260 | }) { (finished) in 261 | UIView.animate(withDuration: 0.5, animations: { 262 | self.focusView.transform = .identity 263 | }, completion: { (finished) in 264 | self.focusView.isHidden = true 265 | }) 266 | } 267 | } 268 | 269 | fileprivate func setupCamera(){ 270 | self.view.backgroundColor = .white 271 | setupVideo() 272 | } 273 | 274 | @objc fileprivate func changeCameraPositionAction() { 275 | let cameraCount = AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo).count 276 | guard cameraCount>0 else { return } 277 | 278 | let rotaionAnim = CATransition() 279 | rotaionAnim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) 280 | rotaionAnim.type = TGPhotoPickerConfig.shared.transitionType 281 | rotaionAnim.duration = 0.5 282 | 283 | guard let videoInput = input else { return } 284 | let position : AVCaptureDevicePosition = videoInput.device.position == .front ? .back : .front 285 | rotaionAnim.subtype = (position == .front) ? "fromRight" : "fromLeft" 286 | 287 | guard let devices = AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo) as? [AVCaptureDevice] else { return } 288 | guard let newDevice = devices.filter({$0.position == position}).first else { return } 289 | guard let newVideoInput = try? AVCaptureDeviceInput(device: newDevice) else { return } 290 | 291 | previewLayer.add(rotaionAnim, forKey: nil) 292 | 293 | session.beginConfiguration() 294 | session.removeInput(videoInput) 295 | if session.canAddInput(newVideoInput) { 296 | session.addInput(newVideoInput) 297 | self.input = newVideoInput 298 | } else { 299 | session.addInput(input) 300 | } 301 | session.commitConfiguration() 302 | } 303 | 304 | @objc fileprivate func takePhotoAction(){ 305 | guard let videoConnection = imageOutput.connection(withMediaType: AVMediaTypeVideo) else { return } 306 | imageOutput.captureStillImageAsynchronously(from: videoConnection) { (imageDataSampleBuffer, error) in 307 | if error != nil { 308 | print("error = \(String(describing: error?.localizedDescription))") 309 | } else { 310 | guard imageDataSampleBuffer != nil else {return} 311 | guard let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataSampleBuffer) else {return} 312 | 313 | self.picData = imageData 314 | self.showImageContainerView?.isHidden = false 315 | self.image = UIImage(data: imageData) 316 | self.showImageView?.image = self.image 317 | 318 | if TGPhotoPickerConfig.shared.saveImageToPhotoAlbum{ 319 | self.saveImageToPhotoAlbum(self.image!) 320 | } 321 | 322 | print("image size\(String(describing: self.image?.size))") 323 | } 324 | } 325 | } 326 | 327 | fileprivate func saveImageToPhotoAlbum(_ savedImage:UIImage){ 328 | canUseAlbum { (canUse) in 329 | if canUse{ 330 | UIImageWriteToSavedPhotosAlbum(savedImage, self, #selector(self.imageDidFinishSavingWithErrorContextInfo), nil) 331 | } 332 | } 333 | } 334 | 335 | @objc fileprivate func imageDidFinishSavingWithErrorContextInfo(image:UIImage,error:NSError?,contextInfo:UnsafeMutableRawPointer?){ 336 | canUseAlbum { (canUse) in 337 | if canUse{ 338 | let msg = (error != nil) ? (TGPhotoPickerConfig.shared.saveImageFailTip+"("+(error?.localizedDescription)!+")") : TGPhotoPickerConfig.shared.saveImageSuccessTip 339 | if !TGPhotoPickerConfig.shared.showCameraSaveSuccess && error == nil{ 340 | return 341 | } 342 | let alert = UIAlertView(title: TGPhotoPickerConfig.shared.saveImageTip, message: msg, delegate: self, cancelButtonTitle: TGPhotoPickerConfig.shared.confirmTitle) 343 | alert.show() 344 | } 345 | } 346 | } 347 | 348 | fileprivate func setupVideo() { 349 | guard let devices = AVCaptureDevice.devices(withMediaType: AVMediaTypeVideo) as? [AVCaptureDevice] else {return} 350 | guard let device = devices.filter({$0.position == .back}).first else {return} 351 | guard let videoInput = try? AVCaptureDeviceInput(device: device) else {return} 352 | self.input = videoInput 353 | self.device = device 354 | 355 | if session.canSetSessionPreset(TGPhotoPickerConfig.shared.sessionPreset) { 356 | session.sessionPreset = TGPhotoPickerConfig.shared.sessionPreset 357 | } 358 | if session.canAddInput(input) { 359 | session.addInput(input) 360 | }else{ 361 | session.addInput(videoInput) 362 | } 363 | if session.canAddOutput(imageOutput) { 364 | session.addOutput(imageOutput) 365 | }else{ 366 | session.addOutput(imageOutput) 367 | } 368 | 369 | previewLayer.frame = view.bounds 370 | self.previewLayer.videoGravity = TGPhotoPickerConfig.shared.videoGravity 371 | view.layer.insertSublayer(previewLayer, at: 0) 372 | session.startRunning() 373 | 374 | do { 375 | try device.lockForConfiguration() 376 | } catch { 377 | return 378 | } 379 | if device.isFlashModeSupported(AVCaptureFlashMode.auto){ 380 | device.flashMode = .auto 381 | } 382 | 383 | if device.isWhiteBalanceModeSupported(AVCaptureWhiteBalanceMode.autoWhiteBalance){ 384 | device.whiteBalanceMode = .autoWhiteBalance 385 | } 386 | device.unlockForConfiguration() 387 | } 388 | } 389 | /* 390 | extension TGCameraVCForiOS8: UIAlertViewDelegate{ 391 | func alertView(_ alertView: UIAlertView, clickedButtonAt buttonIndex: Int) { 392 | if buttonIndex == 0 && alertView.tag == TGPhotoPickerConfig.shared.alertViewTag { 393 | guard let url = NSURL(string: UIApplicationOpenSettingsURLString) else { 394 | return 395 | } 396 | if UIApplication.shared.canOpenURL(url as URL){ 397 | UIApplication.shared.openURL(url as URL) 398 | } 399 | } 400 | } 401 | } 402 | */ 403 | -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/TGPhotoPicker/TGFetchM.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TGFetchM.swift 3 | // TGPhotoPicker 4 | // 5 | // Created by targetcloud on 2017/7/13. 6 | // Copyright © 2017年 targetcloud. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | class TGFetchM { 13 | var fetchResult: PHFetchResult! 14 | var assetType: PHAssetCollectionSubtype! 15 | var name: String! 16 | 17 | init(result: PHFetchResult,name: String?, assetType: PHAssetCollectionSubtype){ 18 | self.fetchResult = result 19 | self.name = name 20 | self.assetType = assetType 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/TGPhotoPicker/TGImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TGImage.swift 3 | // TGImage 4 | // 5 | // Created by targetcloud on 2017/6/30. 6 | // Copyright © 2017年 targetcloud. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public enum BorderAlignment { 12 | case inside 13 | case center 14 | case outside 15 | } 16 | 17 | public extension UIImage { 18 | public typealias ContextBlock = (CGContext) -> Void 19 | 20 | public class func with(width: CGFloat, height: CGFloat, block: ContextBlock) -> UIImage { 21 | return self.with(size: CGSize(width: width, height: height), block: block) 22 | } 23 | 24 | public class func with(size: CGSize, opaque: Bool = false, scale: CGFloat = 0, block: ContextBlock) -> UIImage { 25 | UIGraphicsBeginImageContextWithOptions(size, opaque, scale) 26 | let context = UIGraphicsGetCurrentContext()! 27 | block(context) 28 | let image = UIGraphicsGetImageFromCurrentImageContext() 29 | UIGraphicsEndImageContext() 30 | return image ?? UIImage() 31 | } 32 | 33 | public func with(size: CGSize, opaque: Bool = false, scale: CGFloat = 0, block: ContextBlock) -> UIImage { 34 | return self + UIImage.with(size:size,opaque:opaque,scale:scale,block:block) 35 | } 36 | 37 | public func with(_ block: ContextBlock) -> UIImage { 38 | return UIImage.with(size: self.size, opaque: false, scale: self.scale) { context in 39 | let rect = CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height) 40 | self.draw(in: rect) 41 | block(context) 42 | } 43 | } 44 | 45 | public func with(color: UIColor) -> UIImage { 46 | return UIImage.with(size: self.size) { context in 47 | context.translateBy(x: 0, y: self.size.height) 48 | context.scaleBy(x: 1, y: -1) 49 | context.setBlendMode(.normal) 50 | let rect = CGRect(origin: .zero, size: self.size) 51 | context.clip(to: rect, mask: self.cgImage!) 52 | color.setFill() 53 | context.fill(rect) 54 | } 55 | } 56 | 57 | public class func size(width: CGFloat, height: CGFloat) -> TGImagePresenter { 58 | return self.size(CGSize(width: width, height: height)) 59 | } 60 | 61 | public class func size(_ size: CGSize) -> TGImagePresenter { 62 | let drawer = TGImagePresenter() 63 | drawer.size = .fixed(size) 64 | return drawer 65 | } 66 | 67 | public class func resizable() -> TGImagePresenter { 68 | let drawer = TGImagePresenter() 69 | drawer.size = .resizable 70 | return drawer 71 | } 72 | 73 | private struct AssociatedKeys { 74 | static var TGImagePositionKey = "TGImagePositionKey" 75 | } 76 | 77 | public var position: CGPoint{ 78 | get { 79 | return objc_getAssociatedObject(self, &AssociatedKeys.TGImagePositionKey) as? CGPoint ?? CGPoint.zero 80 | } 81 | set(newValue) { 82 | if self.position != newValue{ 83 | self.willChangeValue(forKey: "position") 84 | objc_setAssociatedObject(self, &AssociatedKeys.TGImagePositionKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 85 | self.didChangeValue(forKey: "position") 86 | } 87 | } 88 | } 89 | 90 | public func position(_ point:CGPoint) -> UIImage{ 91 | self.position = point 92 | return self 93 | } 94 | } 95 | 96 | extension UIColor{ 97 | public class func randomColor() -> UIColor{ 98 | return UIColor(red: CGFloat(arc4random_uniform(255))/255.0, green: CGFloat(arc4random_uniform(255))/255.0, blue: CGFloat(arc4random_uniform(255))/255.0, alpha: 1.0) 99 | } 100 | } 101 | 102 | public func + (leftImage: UIImage, rigthImage: UIImage) -> UIImage { 103 | return leftImage.with { context in 104 | 105 | let leftRect = CGRect(x: 0, y: 0, width: leftImage.size.width, height: leftImage.size.height) 106 | var rightRect = CGRect(x: 0, y: 0, width: rigthImage.size.width, height: rigthImage.size.height) 107 | 108 | if rigthImage.position.x != 0 || rigthImage.position.y != 0{ 109 | rightRect.origin.x = rigthImage.position.x 110 | rightRect.origin.y = rigthImage.position.y 111 | }else if leftRect.contains(rightRect) { 112 | rightRect.origin.x = (leftRect.size.width - rightRect.size.width) / 2 113 | rightRect.origin.y = (leftRect.size.height - rightRect.size.height) / 2 114 | } else { 115 | rightRect.size = leftRect.size 116 | } 117 | 118 | rigthImage.draw(in: rightRect) 119 | } 120 | } 121 | 122 | public func += (leftImage:inout UIImage, rigthImage: UIImage) { 123 | leftImage = leftImage + rigthImage 124 | } 125 | 126 | open class TGImagePresenter{ 127 | public enum Size { 128 | case fixed(CGSize) 129 | case resizable 130 | } 131 | 132 | fileprivate static let defaultGradientLocations: [CGFloat] = [0, 1] 133 | fileprivate static let defaultGradientFrom: CGPoint = .zero 134 | fileprivate static let defaultGradientTo: CGPoint = CGPoint(x: 0, y: 1) 135 | 136 | fileprivate var colors: [UIColor] = [.clear] 137 | fileprivate var colorLocations: [CGFloat] = defaultGradientLocations 138 | fileprivate var colorStartPoint: CGPoint = defaultGradientFrom 139 | fileprivate var colorEndPoint: CGPoint = defaultGradientTo 140 | fileprivate var borderColors: [UIColor] = [.black] 141 | fileprivate var borderColorLocations: [CGFloat] = defaultGradientLocations 142 | fileprivate var borderColorStartPoint: CGPoint = defaultGradientFrom 143 | fileprivate var borderColorEndPoint: CGPoint = defaultGradientTo 144 | fileprivate var borderWidth: CGFloat = 0 145 | fileprivate var borderAlignment: BorderAlignment = .inside 146 | fileprivate var cornerRadiusTopLeft: CGFloat = 0 147 | fileprivate var cornerRadiusTopRight: CGFloat = 0 148 | fileprivate var cornerRadiusBottomLeft: CGFloat = 0 149 | fileprivate var cornerRadiusBottomRight: CGFloat = 0 150 | 151 | fileprivate var size: Size = .resizable 152 | 153 | private static var cachedImages = [String: UIImage]() 154 | 155 | private var cacheKey: String { 156 | var attributes = [String: String]() 157 | attributes["colors"] = String(self.colors.description.hashValue) 158 | attributes["colorLocations"] = String(self.colorLocations.description.hashValue) 159 | attributes["colorStartPoint"] = String(String(describing: self.colorStartPoint).hashValue) 160 | attributes["colorEndPoint"] = String(String(describing: self.colorEndPoint).hashValue) 161 | attributes["borderColors"] = String(self.borderColors.description.hashValue) 162 | attributes["borderColorLocations"] = String(self.borderColorLocations.description.hashValue) 163 | attributes["borderColorStartPoint"] = String(String(describing: self.borderColorStartPoint).hashValue) 164 | attributes["borderColorEndPoint"] = String(String(describing: self.borderColorEndPoint).hashValue) 165 | attributes["borderWidth"] = String(self.borderWidth.hashValue) 166 | attributes["borderAlignment"] = String(self.borderAlignment.hashValue) 167 | attributes["cornerRadiusTopLeft"] = String(self.cornerRadiusTopLeft.hashValue) 168 | attributes["cornerRadiusTopRight"] = String(self.cornerRadiusTopRight.hashValue) 169 | attributes["cornerRadiusBottomLeft"] = String(self.cornerRadiusBottomLeft.hashValue) 170 | attributes["cornerRadiusBottomRight"] = String(self.cornerRadiusBottomRight.hashValue) 171 | 172 | switch self.size { 173 | case .fixed(let size): 174 | attributes["size"] = "Fixed(\(size.width), \(size.height))" 175 | case .resizable: 176 | attributes["size"] = "Resizable" 177 | } 178 | 179 | var serializedAttributes = [String]() 180 | for key in attributes.keys.sorted() { 181 | if let value = attributes[key] { 182 | serializedAttributes.append("\(key):\(value)") 183 | } 184 | } 185 | 186 | let cacheKey = serializedAttributes.joined(separator: "|") 187 | return cacheKey 188 | } 189 | 190 | open func color(_ color: UIColor) -> Self { 191 | self.colors = [color] 192 | return self 193 | } 194 | 195 | open func color(gradient: [UIColor],locations: [CGFloat] = defaultGradientLocations,from startPoint: CGPoint = defaultGradientFrom,to endPoint: CGPoint = defaultGradientTo) -> Self { 196 | self.colors = gradient 197 | self.colorLocations = locations 198 | self.colorStartPoint = startPoint 199 | self.colorEndPoint = endPoint 200 | return self 201 | } 202 | 203 | open func border(color: UIColor) -> Self { 204 | self.borderColors = [color] 205 | return self 206 | } 207 | 208 | open func border(gradient: [UIColor],locations: [CGFloat] = defaultGradientLocations,from startPoint: CGPoint = defaultGradientFrom,to endPoint: CGPoint = defaultGradientTo 209 | ) -> Self { 210 | self.borderColors = gradient 211 | self.borderColorLocations = locations 212 | self.borderColorStartPoint = startPoint 213 | self.borderColorEndPoint = endPoint 214 | return self 215 | } 216 | 217 | open func border(width: CGFloat) -> Self { 218 | self.borderWidth = width 219 | return self 220 | } 221 | 222 | open func border(alignment: BorderAlignment) -> Self { 223 | self.borderAlignment = alignment 224 | return self 225 | } 226 | 227 | open func corner(radius: CGFloat) -> Self { 228 | return self.corner(topLeft: radius, topRight: radius, bottomLeft: radius, bottomRight: radius) 229 | } 230 | 231 | open func corner(topLeft: CGFloat) -> Self { 232 | self.cornerRadiusTopLeft = topLeft 233 | return self 234 | } 235 | 236 | open func corner(topRight: CGFloat) -> Self { 237 | self.cornerRadiusTopRight = topRight 238 | return self 239 | } 240 | 241 | open func corner(bottomLeft: CGFloat) -> Self { 242 | self.cornerRadiusBottomLeft = bottomLeft 243 | return self 244 | } 245 | 246 | open func corner(bottomRight: CGFloat) -> Self { 247 | self.cornerRadiusBottomRight = bottomRight 248 | return self 249 | } 250 | 251 | open func corner(topLeft: CGFloat, topRight: CGFloat, bottomLeft: CGFloat, bottomRight: CGFloat) -> Self { 252 | return self.corner(topLeft: topLeft).corner(topRight: topRight).corner(bottomLeft: bottomLeft).corner(bottomRight: bottomRight) 253 | } 254 | 255 | open var image: UIImage { 256 | switch self.size { 257 | case .fixed(let size): 258 | return self.imageWithSize(size) 259 | case .resizable: 260 | self.borderAlignment = .inside 261 | let cornerRadius = max(self.cornerRadiusTopLeft, self.cornerRadiusTopRight,self.cornerRadiusBottomLeft, self.cornerRadiusBottomRight) 262 | let capSize = ceil(max(cornerRadius, self.borderWidth)) 263 | let imageSize = capSize * 2 + 1 264 | let image = self.imageWithSize(CGSize(width: imageSize, height: imageSize)) 265 | let capInsets = UIEdgeInsets(top: capSize, left: capSize, bottom: capSize, right: capSize) 266 | return image.resizableImage(withCapInsets: capInsets) 267 | } 268 | } 269 | 270 | private func imageWithSize(_ size: CGSize, useCache: Bool = true) -> UIImage { 271 | if let cachedImage = type(of: self).cachedImages[self.cacheKey], useCache { 272 | return cachedImage 273 | } 274 | 275 | var imageSize = CGSize(width: size.width, height: size.height) 276 | var rect = CGRect() 277 | rect.size = imageSize 278 | 279 | switch self.borderAlignment { 280 | case .inside: 281 | rect.origin.x += self.borderWidth / 2 282 | rect.origin.y += self.borderWidth / 2 283 | rect.size.width -= self.borderWidth 284 | rect.size.height -= self.borderWidth 285 | case .center: 286 | rect.origin.x += self.borderWidth / 2 287 | rect.origin.y += self.borderWidth / 2 288 | imageSize.width += self.borderWidth 289 | imageSize.height += self.borderWidth 290 | case .outside: 291 | rect.origin.x += self.borderWidth / 2 292 | rect.origin.y += self.borderWidth / 2 293 | rect.size.width += self.borderWidth 294 | rect.size.height += self.borderWidth 295 | imageSize.width += self.borderWidth * 2 296 | imageSize.height += self.borderWidth * 2 297 | } 298 | 299 | let cornerRadius = max(self.cornerRadiusTopLeft, self.cornerRadiusTopRight,self.cornerRadiusBottomLeft, self.cornerRadiusBottomRight) 300 | 301 | let image = UIImage.with(size: imageSize) { context in 302 | let path: UIBezierPath 303 | if self.cornerRadiusTopLeft == self.cornerRadiusTopRight && self.cornerRadiusTopLeft == self.cornerRadiusBottomLeft && self.cornerRadiusTopLeft == self.cornerRadiusBottomRight && self.cornerRadiusTopLeft > 0 { 304 | path = UIBezierPath(roundedRect: rect, cornerRadius: self.cornerRadiusTopLeft) 305 | } else if cornerRadius > 0 { 306 | let startAngle = CGFloat.pi 307 | let topLeftCenter = CGPoint(x: self.cornerRadiusTopLeft + self.borderWidth / 2,y: self.cornerRadiusTopLeft + self.borderWidth / 2) 308 | let topRightCenter = CGPoint(x: imageSize.width - self.cornerRadiusTopRight - self.borderWidth / 2,y: self.cornerRadiusTopRight + self.borderWidth / 2) 309 | let bottomRightCenter = CGPoint(x: imageSize.width - self.cornerRadiusBottomRight - self.borderWidth / 2,y: imageSize.height - self.cornerRadiusBottomRight - self.borderWidth / 2) 310 | let bottomLeftCenter = CGPoint(x: self.cornerRadiusBottomLeft + self.borderWidth / 2,y: imageSize.height - self.cornerRadiusBottomLeft - self.borderWidth / 2) 311 | let mutablePath = UIBezierPath() 312 | self.cornerRadiusTopLeft > 0 ? mutablePath.addArc(withCenter: topLeftCenter,radius: self.cornerRadiusTopLeft,startAngle: startAngle,endAngle: 1.5 * startAngle,clockwise: true) : mutablePath.move(to: topLeftCenter) 313 | self.cornerRadiusTopRight > 0 ? mutablePath.addArc(withCenter: topRightCenter,radius: self.cornerRadiusTopRight,startAngle: 1.5 * startAngle,endAngle: 2 * startAngle,clockwise: true) : mutablePath.addLine(to: topRightCenter) 314 | self.cornerRadiusBottomRight > 0 ? mutablePath.addArc(withCenter: bottomRightCenter,radius: self.cornerRadiusBottomRight,startAngle: 2 * startAngle,endAngle: 2.5 * startAngle,clockwise: true) : mutablePath.addLine(to: bottomRightCenter) 315 | self.cornerRadiusBottomLeft > 0 ? mutablePath.addArc(withCenter: bottomLeftCenter,radius: self.cornerRadiusBottomLeft,startAngle: 2.5 * startAngle,endAngle: 3 * startAngle,clockwise: true) : mutablePath.addLine(to: bottomLeftCenter) 316 | self.cornerRadiusTopLeft > 0 ? mutablePath.addLine(to: CGPoint(x: self.borderWidth / 2, y: topLeftCenter.y)) : mutablePath.addLine(to: topLeftCenter) 317 | path = mutablePath 318 | } 319 | else { 320 | path = UIBezierPath(rect: rect) 321 | } 322 | 323 | context.saveGState() 324 | if self.colors.count <= 1 { 325 | self.colors.first?.setFill() 326 | path.fill() 327 | } else { 328 | let colorSpace = CGColorSpaceCreateDeviceRGB() 329 | let colors = self.colors.map { $0.cgColor } as CFArray 330 | if let gradient = CGGradient(colorsSpace: colorSpace, colors: colors, locations: self.colorLocations) { 331 | let startPoint = CGPoint(x: self.colorStartPoint.x * imageSize.width,y: self.colorStartPoint.y * imageSize.height) 332 | let endPoint = CGPoint(x: self.colorEndPoint.x * imageSize.width,y: self.colorEndPoint.y * imageSize.height) 333 | context.addPath(path.cgPath) 334 | context.clip() 335 | context.drawLinearGradient(gradient, start: startPoint, end: endPoint, options: []) 336 | } 337 | } 338 | context.restoreGState() 339 | 340 | context.saveGState() 341 | if self.borderColors.count <= 1 { 342 | self.borderColors.first?.setStroke() 343 | path.lineWidth = self.borderWidth 344 | path.stroke() 345 | } else { 346 | let colorSpace = CGColorSpaceCreateDeviceRGB() 347 | let colors = self.borderColors.map { $0.cgColor } as CFArray 348 | if let gradient = CGGradient(colorsSpace: colorSpace, colors: colors, locations: self.borderColorLocations) { 349 | let startPoint = CGPoint(x: self.borderColorStartPoint.x * imageSize.width,y: self.borderColorStartPoint.y * imageSize.height) 350 | let endPoint = CGPoint(x: self.borderColorEndPoint.x * imageSize.width,y: self.borderColorEndPoint.y * imageSize.height) 351 | context.addPath(path.cgPath) 352 | context.setLineWidth(self.borderWidth) 353 | context.replacePathWithStrokedPath() 354 | context.clip() 355 | context.drawLinearGradient(gradient, start: startPoint, end: endPoint, options: []) 356 | } 357 | } 358 | context.restoreGState() 359 | } 360 | 361 | if useCache { 362 | type(of: self).cachedImages[self.cacheKey] = image 363 | } 364 | return image 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/TGPhotoPicker/TGPhotoCollectionVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TGPhotoCollectionVC.swift 3 | // TGPhotoPicker 4 | // 5 | // Created by targetcloud on 2017/7/22. 6 | // Copyright © 2017年 targetcloud. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | private let reuseIdentifier = "TGPhotoCell" 13 | 14 | protocol TGPhotoCollectionViewCellDelegate: class { 15 | func selectNumberChange(number: Int,isRemove: Bool,forceRefresh: Bool) 16 | } 17 | 18 | class TGPhotoCell: UICollectionViewCell { 19 | weak var delegate: TGPhotoCollectionViewCellDelegate? 20 | weak var vc: UIViewController? 21 | weak var nav: TGPhotoPickerVC? 22 | var model : PHAsset? 23 | var assetId: String? 24 | var indexPath: IndexPath? 25 | 26 | var itemWH:CGFloat{ 27 | return (TGPhotoPickerConfig.ScreenW - (TGPhotoPickerConfig.shared.colCount + (TGPhotoPickerConfig.shared.leftAndRigthNoPadding ? -1 : 1)) * TGPhotoPickerConfig.shared.padding) / TGPhotoPickerConfig.shared.colCount 28 | } 29 | 30 | var photoSelectedIndex: Int = -1{ 31 | didSet{ 32 | if photoSelectedIndex > -1{ 33 | if TGPhotoPickerConfig.shared.isShowNumber{ 34 | if let cacheImage = getCacheImage(photoSelectedIndex){ 35 | selectBtn.setImage(cacheImage, for: .selected) 36 | } 37 | }else{ 38 | selectBtn.setImage(selectedImage, for: .selected) 39 | } 40 | } 41 | selectBtn.isSelected = photoSelectedIndex > -1 42 | } 43 | } 44 | 45 | var isMaskHidden: Bool = true{ 46 | didSet{ 47 | self.maskV.isHidden = isMaskHidden 48 | } 49 | } 50 | 51 | var isSelectMaskHidden: Bool = true{ 52 | didSet{ 53 | self.selectMaskV.isHidden = isSelectMaskHidden 54 | } 55 | } 56 | 57 | var image: UIImage?{ 58 | didSet{ 59 | photoImage.image = image 60 | } 61 | } 62 | 63 | public func immediateSelect(){ 64 | selectClicked(selectBtn) 65 | } 66 | 67 | private var selectedImage: UIImage? 68 | 69 | @objc private func selectClicked(_ sender: UIButton) { 70 | sender.isSelected = !sender.isSelected 71 | if nav == nil { 72 | nav = vc?.navigationController as? TGPhotoPickerVC 73 | } 74 | if !sender.isSelected { 75 | if vc != nil { 76 | selectMaskV.isHidden = true 77 | nav?.assetArr.remove(at: (nav?.assetArr.index(of: self.model!))!) 78 | self.delegate?.selectNumberChange(number: (nav?.assetArr.count)!,isRemove: true, forceRefresh: false) 79 | } 80 | } else { 81 | if vc != nil { 82 | if (nav?.assetArr.count)! >= TGPhotoPickerConfig.shared.maxImageCount - (nav?.alreadySelectedImageNum)! { 83 | sender.isSelected = false 84 | return self.showSelectErrorDialog() 85 | } else { 86 | selectMaskV.isHidden = !TGPhotoPickerConfig.shared.useSelectMask 87 | nav?.assetArr.append(self.model!) 88 | if TGPhotoPickerConfig.shared.isShowNumber{ 89 | if let cacheImage = getCacheImage((nav?.assetArr.count)! - 1){ 90 | selectBtn.setImage(cacheImage, for: .selected) 91 | } 92 | }else{ 93 | selectBtn.setImage(selectedImage, for: .selected) 94 | } 95 | if TGPhotoPickerConfig.shared.checkboxAnimate{ 96 | selectBtn.transform = CGAffineTransform(scaleX: 0.5, y: 0.5) 97 | UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 10, options: UIViewAnimationOptions.curveEaseIn, animations: { 98 | self.selectBtn.transform = CGAffineTransform.identity 99 | }, completion: nil) 100 | } 101 | self.delegate?.selectNumberChange(number: (nav?.assetArr.count)!,isRemove: false, forceRefresh: false) 102 | } 103 | } 104 | } 105 | } 106 | 107 | private func getCacheImage(_ index: Int) -> UIImage?{ 108 | if TGPhotoPickerConfig.shared.cacheNumerImageArr.count > 0 && 109 | index >= 0 && 110 | index < TGPhotoPickerConfig.shared.cacheNumerImageArr.count{ 111 | return TGPhotoPickerConfig.shared.cacheNumerImageArr[index] 112 | } 113 | return nil 114 | } 115 | 116 | private func showSelectErrorDialog() { 117 | if self.vc != nil { 118 | let less = TGPhotoPickerConfig.shared.maxImageCount - (nav?.alreadySelectedImageNum)! 119 | let range = TGPhotoPickerConfig.shared.errorImageMaxSelect.range(of:"#") 120 | var error = TGPhotoPickerConfig.shared.errorImageMaxSelect 121 | error.replaceSubrange(range!, with: String(less)) 122 | let alert = UIAlertController(title: nil, message: ((nav?.alreadySelectedImageNum)! > 0 ? TGPhotoPickerConfig.shared.leftTitle : "") + error, preferredStyle: UIAlertControllerStyle.alert) 123 | let confirmAction = UIAlertAction(title:TGPhotoPickerConfig.shared.confirmTitle, style: .default, handler: nil) 124 | alert.addAction(confirmAction) 125 | self.vc?.present(alert, animated: true, completion: nil) 126 | } 127 | } 128 | 129 | override init(frame: CGRect) { 130 | super.init(frame: frame) 131 | setupUI() 132 | } 133 | 134 | required init?(coder aDecoder: NSCoder) { 135 | super.init(coder: aDecoder) 136 | //fatalError("init(coder:) has not been implemented") 137 | } 138 | 139 | private func setupUI() { 140 | contentView.addSubview(self.photoImage) 141 | contentView.addSubview(self.maskV) 142 | contentView.addSubview(self.selectMaskV) 143 | contentView.addSubview(self.selectBtn) 144 | } 145 | 146 | private lazy var maskV: UIView = { 147 | let mask = UIView(frame: CGRect(x: 0, y: 0, width: self.itemWH, height: self.itemWH)) 148 | mask.backgroundColor = UIColor.white.withAlphaComponent(TGPhotoPickerConfig.shared.maskAlpha) 149 | mask.isHidden = true 150 | return mask 151 | }() 152 | 153 | private lazy var selectMaskV: UIView = { 154 | let mask = UIView(frame: CGRect(x: 0, y: 0, width: self.itemWH, height: self.itemWH)) 155 | mask.backgroundColor = UIColor.black.withAlphaComponent(TGPhotoPickerConfig.shared.maskAlpha) 156 | mask.isHidden = true 157 | return mask 158 | }() 159 | 160 | private lazy var photoImage: UIImageView = { 161 | let iv = UIImageView() 162 | iv.contentMode = .scaleAspectFill 163 | iv.frame = CGRect(x: 0, y: 0, width: self.itemWH, height: self.itemWH) 164 | iv.clipsToBounds = true 165 | return iv 166 | }() 167 | 168 | private lazy var selectBtn: UIButton = { 169 | let btn = UIButton() 170 | let imageTuples = TGPhotoPickerConfig.shared.getCheckboxImage() 171 | self.selectedImage = imageTuples.select 172 | var x:CGFloat = 0 173 | var y:CGFloat = 0 174 | switch TGPhotoPickerConfig.shared.checkboxPosition { 175 | case .topLeft: 176 | break 177 | case .topRight: 178 | x = self.itemWH - imageTuples.size.width 179 | case .bottomLeft: 180 | y = self.itemWH - imageTuples.size.height 181 | case .bottomRight: 182 | x = self.itemWH - imageTuples.size.width 183 | y = self.itemWH - imageTuples.size.height 184 | } 185 | btn.frame = CGRect(x: x, y: y, width: imageTuples.size.width, height: imageTuples.size.height) 186 | btn.addTarget(self, action: #selector(selectClicked), for: .touchUpInside) 187 | btn.setImage(imageTuples.unselect, for: .normal) 188 | btn.setImage(imageTuples.select, for: .selected) 189 | return btn 190 | }() 191 | } 192 | 193 | class TGPhotoCollectionVC: UICollectionViewController { 194 | 195 | var fetchResult: PHFetchResult? 196 | 197 | fileprivate lazy var imageManager = PHCachingImageManager() 198 | 199 | fileprivate lazy var assetGridThumbnailSize: CGSize = { 200 | let cellSize = (self.collectionViewLayout as! UICollectionViewFlowLayout).itemSize 201 | let size = cellSize.width * UIScreen.main.scale 202 | return CGSize(width: size, height: size) 203 | }() 204 | 205 | fileprivate lazy var nav: TGPhotoPickerVC = self.navigationController as! TGPhotoPickerVC 206 | 207 | fileprivate lazy var bottomBar: TGBottomBar = { 208 | let subtractH: CGFloat = ((TGPhotoPickerConfig.shared.barBGColor.getAlpha()) != 1) ? 0 : 64 209 | let toolbar = TGBottomBar(frame: CGRect(x:0,y: TGPhotoPickerConfig.ScreenH - TGPhotoPickerConfig.shared.toolBarH - subtractH,width: TGPhotoPickerConfig.ScreenW,height: TGPhotoPickerConfig.shared.toolBarH)) 210 | toolbar.delegate = self 211 | if self.nav.assetArr.count > 0 { 212 | toolbar.changeNumber(number: self.nav.assetArr.count, animation: false) 213 | } 214 | return toolbar 215 | }() 216 | 217 | override func viewDidLoad() { 218 | super.viewDidLoad() 219 | self.view.backgroundColor = .white 220 | setupUI() 221 | PHPhotoLibrary.shared().register(self) 222 | 223 | if fetchResult == nil { 224 | fetchResult = PHAsset.fetchAssets(with: TGPhotoFetchOptions()) 225 | } 226 | } 227 | 228 | deinit{ 229 | PHPhotoLibrary.shared().unregisterChangeObserver(self) 230 | } 231 | 232 | override var prefersStatusBarHidden: Bool{ 233 | return false 234 | } 235 | 236 | override func viewWillAppear(_ animated: Bool) { 237 | super.viewWillAppear(animated) 238 | self.navigationController?.isNavigationBarHidden = false 239 | 240 | if #available(iOS 9.0, *) { 241 | let isVCBased = Bundle.main.infoDictionary?["UIViewControllerBasedStatusBarAppearance"] as? Bool ?? false 242 | if !isVCBased{ 243 | UIApplication.shared.setStatusBarHidden(false, with: .none) 244 | } 245 | }else { 246 | UIApplication.shared.setStatusBarHidden(false, with: .none) 247 | } 248 | 249 | self.collectionView?.reloadData() 250 | selectNumberChange(number: self.nav.assetArr.count) 251 | } 252 | 253 | private func setupUI(){ 254 | setupNavigationBar() 255 | let originFrame = self.collectionView!.frame 256 | self.collectionView!.frame = CGRect(x:originFrame.origin.x, y:originFrame.origin.y, width:originFrame.size.width, height: originFrame.height - ((TGPhotoPickerConfig.shared.barBGColor.getAlpha() != 1) ? 0 : TGPhotoPickerConfig.shared.toolBarH)) 257 | resetCacheAssets() 258 | self.collectionView?.contentInset = UIEdgeInsetsMake( 259 | TGPhotoPickerConfig.shared.padding, 260 | TGPhotoPickerConfig.shared.leftAndRigthNoPadding ? 0 : TGPhotoPickerConfig.shared.padding, 261 | TGPhotoPickerConfig.shared.padding + ((TGPhotoPickerConfig.shared.barBGColor.getAlpha() != 1) ? TGPhotoPickerConfig.shared.toolBarH : 0), 262 | TGPhotoPickerConfig.shared.leftAndRigthNoPadding ? 0 : TGPhotoPickerConfig.shared.padding 263 | ) 264 | self.collectionView?.backgroundColor = .white 265 | self.collectionView?.register(TGPhotoCell.self, forCellWithReuseIdentifier: reuseIdentifier) 266 | self.view.addSubview(self.bottomBar) 267 | bottomBar.host = "\(type(of: self))" 268 | } 269 | 270 | fileprivate func resetCacheAssets() { 271 | self.imageManager.stopCachingImagesForAllAssets() 272 | } 273 | 274 | private func setupNavigationBar(){ 275 | self.navigationController?.navigationBar.barStyle = .black 276 | self.navigationController?.navigationBar.tintColor = .white 277 | 278 | let WH = TGPhotoPickerConfig.shared.checkboxBarWH * 0.8 279 | 280 | self.navigationItem.leftBarButtonItem = UIBarButtonItem(image: UIImage.size(width: WH, height: WH) 281 | .corner(radius: WH * 0.5) 282 | .color(.clear) 283 | .border(color: UIColor.white.withAlphaComponent(0.7)) 284 | .border(width: TGPhotoPickerConfig.shared.isShowBorder ? TGPhotoPickerConfig.shared.checkboxLineW : 0) 285 | .image 286 | .with({ context in 287 | context.setLineCap(.round) 288 | UIColor.white.setStroke() 289 | context.setLineWidth(TGPhotoPickerConfig.shared.checkboxLineW) 290 | context.move(to: CGPoint(x: WH * (TGPhotoPickerConfig.shared.isShowBorder ? 0.55 : 0.6), y: WH * (TGPhotoPickerConfig.shared.isShowBorder ? 0.25: 0.2))) 291 | context.addLine(to: CGPoint(x: WH * (TGPhotoPickerConfig.shared.isShowBorder ? 0.4 : 0.35), y: WH * 0.5)) 292 | context.move(to: CGPoint(x: WH * (TGPhotoPickerConfig.shared.isShowBorder ? 0.4 : 0.35), y: WH * 0.5)) 293 | context.addLine(to: CGPoint(x: WH * (TGPhotoPickerConfig.shared.isShowBorder ? 0.55 : 0.6), y: WH * (TGPhotoPickerConfig.shared.isShowBorder ? 0.75 : 0.8))) 294 | // context.move(to: CGPoint(x: 12, y: 4)) 295 | // context.addLine(to: CGPoint(x: 7, y: 10)) 296 | // context.move(to: CGPoint(x: 7, y: 10)) 297 | // context.addLine(to: CGPoint(x: 12, y: 16)) 298 | context.strokePath() 299 | }), style: .plain, target: self, action: #selector(back)) 300 | 301 | self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: TGPhotoPickerConfig.shared.cancelTitle, style: UIBarButtonItemStyle.plain, target: self, action: #selector(cancel)) 302 | } 303 | 304 | @objc private func back(){ 305 | self.navigationController?.popViewController(animated: true) 306 | } 307 | 308 | @objc private func cancel(){ 309 | self.nav.assetArr.removeAll() 310 | self.navigationController?.dismiss(animated: true, completion: nil) 311 | } 312 | 313 | class func configCustomCollectionLayout() -> UICollectionViewFlowLayout { 314 | let layout = UICollectionViewFlowLayout() 315 | layout.minimumInteritemSpacing = TGPhotoPickerConfig.shared.padding 316 | layout.minimumLineSpacing = TGPhotoPickerConfig.shared.padding 317 | let itemWH:CGFloat = (TGPhotoPickerConfig.ScreenW - (TGPhotoPickerConfig.shared.colCount + (TGPhotoPickerConfig.shared.leftAndRigthNoPadding ? -1 : 1)) * TGPhotoPickerConfig.shared.padding) / TGPhotoPickerConfig.shared.colCount 318 | layout.itemSize = CGSize(width:itemWH, height: itemWH) 319 | return layout 320 | } 321 | 322 | override func numberOfSections(in collectionView: UICollectionView) -> Int { 323 | return 1 324 | } 325 | 326 | override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 327 | return self.fetchResult?.count ?? 0 328 | } 329 | 330 | fileprivate func setupCell(_ cell: TGPhotoCell,_ asset: PHAsset){ 331 | cell.photoSelectedIndex = self.nav.assetArr.index(of: asset) ?? -1 332 | cell.isMaskHidden = (cell.photoSelectedIndex > -1) ? true : (TGPhotoPickerConfig.shared.useSelectMask ? true : !(nav.assetArr.count >= TGPhotoPickerConfig.shared.maxImageCount)) 333 | cell.isSelectMaskHidden = TGPhotoPickerConfig.shared.useSelectMask ? !(cell.photoSelectedIndex > -1) : true 334 | cell.model = asset 335 | cell.delegate = self 336 | cell.vc = self 337 | cell.nav = self.nav 338 | cell.assetId = asset.localIdentifier 339 | 340 | self.imageManager.requestImage(for: asset, targetSize: self.assetGridThumbnailSize, contentMode: .aspectFill, options: nil) { image, info in 341 | if cell.assetId == asset.localIdentifier { 342 | DispatchQueue.main.async { 343 | cell.image = image 344 | } 345 | } 346 | } 347 | } 348 | 349 | override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 350 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! TGPhotoCell 351 | cell.indexPath = indexPath 352 | setupCell(cell,self.fetchResult![indexPath.row]) 353 | return cell 354 | } 355 | 356 | override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 357 | if TGPhotoPickerConfig.shared.immediateTapSelect{ 358 | if let cell = collectionView.cellForItem(at: indexPath) as? TGPhotoCell{ 359 | cell.immediateSelect() 360 | } 361 | return 362 | } 363 | let previewvc = TGAlbumPhotoPreviewVC() 364 | previewvc.fetchResult = self.fetchResult 365 | previewvc.currentPage = indexPath.row 366 | previewvc.delegate = self 367 | self.navigationController?.show(previewvc, sender: nil) 368 | } 369 | 370 | } 371 | 372 | extension TGPhotoCollectionVC: TGBottomBarDelegate{ 373 | func onDoneButtonClicked(){ 374 | self.nav.imageSelectFinish() 375 | } 376 | 377 | func onOriginalButtonClicked(_ sender:TGAnimationButton){ 378 | sender.isSelected = !sender.isSelected 379 | } 380 | 381 | func onPreviewButtonClicked(_ sender:TGAnimationButton){ 382 | let previewvc = TGAlbumPhotoPreviewVC() 383 | previewvc.fetchResult = self.fetchResult 384 | previewvc.currentPage = (self.fetchResult?.index(of: nav.assetArr[0]))! 385 | previewvc.delegate = self 386 | self.navigationController?.show(previewvc, sender: nil) 387 | } 388 | 389 | func onReselectButtonClicked(_ sender:TGAnimationButton){ 390 | nav.assetArr.removeAll() 391 | selectNumberChange(number: (nav.assetArr.count),forceRefresh: true) 392 | } 393 | } 394 | 395 | extension TGPhotoCollectionVC: TGPhotoCollectionDelegate{ 396 | func onPreviewPageBack() { 397 | self.collectionView?.reloadData() 398 | self.selectNumberChange(number: self.nav.assetArr.count) 399 | } 400 | } 401 | 402 | extension TGPhotoCollectionVC: PHPhotoLibraryChangeObserver{ 403 | func photoLibraryDidChange(_ changeInstance: PHChange) { 404 | if let collectionChanges = changeInstance.changeDetails(for: fetchResult!) { 405 | DispatchQueue.main.async { 406 | self.fetchResult = collectionChanges.fetchResultAfterChanges 407 | if (collectionChanges.hasIncrementalChanges || collectionChanges.hasMoves) { 408 | self.collectionView?.reloadData() 409 | } 410 | self.resetCacheAssets() 411 | } 412 | } 413 | } 414 | } 415 | 416 | extension TGPhotoCollectionVC: TGPhotoCollectionViewCellDelegate{ 417 | func selectNumberChange(number: Int,isRemove: Bool = false,forceRefresh: Bool = false) { 418 | if forceRefresh || 419 | (isRemove && TGPhotoPickerConfig.shared.isShowNumber) ||//是删除并显示数字的情况 420 | (!TGPhotoPickerConfig.shared.useSelectMask &&//反向显示遮罩的情况下并且 421 | (number == TGPhotoPickerConfig.shared.maxImageCount ||//选择已经达到最多张数 422 | (isRemove && (self.nav.assetArr.count == TGPhotoPickerConfig.shared.maxImageCount - 1))//最多张减1需要去掉反向显示的所有遮罩 423 | ) 424 | ){ 425 | UIView.performWithoutAnimation { 426 | self.collectionView?.performBatchUpdates({ 427 | //self.collectionView?.reloadData()//performBatchUpdates不会重新调用reloadData,所以替换成手工调用 428 | for i in 0 ..< (self.collectionView?.visibleCells.count ?? 0){ 429 | let cell = self.collectionView?.visibleCells[i] as! TGPhotoCell 430 | self.setupCell(cell, self.fetchResult![(cell.indexPath?.row)!]) 431 | } 432 | }, completion: nil) 433 | } 434 | } 435 | self.bottomBar.changeNumber(number: number, animation: TGPhotoPickerConfig.shared.checkboxAnimate) 436 | } 437 | } 438 | -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/TGPhotoPicker/TGPhotoFetchOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TGPhotoFetchOptions.swift 3 | // TGPhotoPicker 4 | // 5 | // Created by targetcloud on 2017/7/21. 6 | // Copyright © 2017年 targetcloud. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | class TGPhotoFetchOptions: PHFetchOptions { 13 | 14 | override init() { 15 | super.init() 16 | if TGPhotoPickerConfig.shared.selectKind == .onlyVideo{ 17 | self.predicate = NSPredicate(format: "mediaType == %d", PHAssetMediaType.video.rawValue) 18 | }else if (TGPhotoPickerConfig.shared.selectKind == .onlyPhoto){ 19 | self.predicate = NSPredicate(format: "mediaType == %d", PHAssetMediaType.image.rawValue) 20 | }else if TGPhotoPickerConfig.shared.selectKind == .onlyLive{ 21 | if #available(iOS 9.1, *) { 22 | self.predicate = NSPredicate(format: "mediaSubtype == %d", PHAssetMediaSubtype.photoLive.rawValue) 23 | } 24 | } 25 | 26 | self.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: TGPhotoPickerConfig.shared.ascending)] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/TGPhotoPicker/TGPhotoImageManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TGPhotoImageManager.swift 3 | // TGPhotoPicker 4 | // 5 | // Created by targetcloud on 2017/7/20. 6 | // Copyright © 2017年 targetcloud. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | class TGPhotoImageManager: PHCachingImageManager { 13 | func getPhotoByMaxSize(asset: PHObject, size: CGFloat, completion: @escaping (UIImage?, Data?, [NSObject : Any]?)->()){ 14 | let maxSize = size > TGPhotoPickerConfig.shared.previewImageFetchMaxW ? TGPhotoPickerConfig.shared.previewImageFetchMaxW : size 15 | if let asset = asset as? PHAsset { 16 | 17 | let factor = CGFloat(asset.pixelHeight)/CGFloat(asset.pixelWidth) 18 | let pixcelWidth = maxSize * UIScreen.main.scale 19 | let pixcelHeight = CGFloat(pixcelWidth) * factor 20 | 21 | self.requestImage(for: asset, targetSize: CGSize(width:pixcelWidth, height: pixcelHeight), contentMode: .aspectFit, options: nil, resultHandler: { image, info in 22 | if let info = info as? [String:Any] { 23 | let canceled = info[PHImageCancelledKey] as? Bool 24 | let error = info[PHImageErrorKey] as? NSError 25 | if canceled == nil && error == nil && image != nil { 26 | var data = UIImageJPEGRepresentation(image!, TGPhotoPickerConfig.shared.compressionQuality) 27 | if data == nil{ 28 | data = UIImagePNGRepresentation(image!) 29 | } 30 | completion(image, data, info as [NSObject : Any]?) 31 | } 32 | 33 | let isCloud = info[PHImageResultIsInCloudKey] as? Bool 34 | if isCloud != nil && image == nil { 35 | let options = PHImageRequestOptions() 36 | options.isNetworkAccessAllowed = true 37 | self.requestImageData(for: asset, options: options, resultHandler: { data, _, orientation, info in 38 | if let data = data { 39 | let resultImage = UIImage(data: data, scale: TGPhotoPickerConfig.shared.cloudImageScale) 40 | completion(resultImage, data, info as [NSObject : Any]?) 41 | } 42 | }) 43 | } 44 | } 45 | }) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/TGPhotoPicker/TGPhotoListVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TGPhotoListVC.swift 3 | // TGPhotoPicker 4 | // 5 | // Created by targetcloud on 2017/7/21. 6 | // Copyright © 2017年 targetcloud. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | private let cellIdentifier = "TGPhotoListCell" 13 | 14 | class TGPhotoListCell: UITableViewCell { 15 | 16 | class func cellWithTableView(_ tableView: UITableView) -> TGPhotoListCell{ 17 | var cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as? TGPhotoListCell 18 | if cell == nil { 19 | cell = TGPhotoListCell(style: UITableViewCellStyle.default, reuseIdentifier: cellIdentifier) 20 | } 21 | return cell! 22 | } 23 | 24 | override init(style: UITableViewCellStyle, reuseIdentifier: String?) { 25 | super.init(style: style, reuseIdentifier: reuseIdentifier) 26 | setupUI() 27 | } 28 | 29 | required init?(coder aDecoder: NSCoder) { 30 | super.init(coder: aDecoder) 31 | } 32 | 33 | private func setupUI(){ 34 | self.layoutMargins = UIEdgeInsets.zero 35 | let bgView = UIView() 36 | bgView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.1) 37 | self.selectedBackgroundView = bgView 38 | 39 | self.selectionStyle = .none 40 | self.accessoryType = .disclosureIndicator 41 | 42 | self.contentView.addSubview(coverImage) 43 | self.contentView.addSubview(photoTitle) 44 | self.contentView.addSubview(photoNum) 45 | 46 | addConstraint(NSLayoutConstraint(item: photoTitle,attribute: .centerY,relatedBy: .equal,toItem: coverImage,attribute: .centerY,multiplier: 1.0,constant: 0)) 47 | addConstraint(NSLayoutConstraint(item: photoTitle,attribute: .leading,relatedBy: .equal,toItem: coverImage,attribute: .trailing,multiplier: 1.0,constant: TGPhotoPickerConfig.shared.padding)) 48 | 49 | addConstraint(NSLayoutConstraint(item: photoNum,attribute: .centerY,relatedBy: .equal,toItem: coverImage,attribute: .centerY,multiplier: 1.0,constant: 0)) 50 | addConstraint(NSLayoutConstraint(item: photoNum,attribute: .leading,relatedBy: .equal,toItem: photoTitle,attribute: .trailing,multiplier: 1.0,constant: TGPhotoPickerConfig.shared.padding)) 51 | } 52 | 53 | func renderData(_ result:PHFetchResult, label: String?){ 54 | self.photoTitle.text = label 55 | self.photoNum.text = "(" + String(result.count) + ")" 56 | if result.count > 0 { 57 | if let firstImageAsset = result[0] as? PHAsset { 58 | let realSize = self.coverImage.frame.width * UIScreen.main.scale 59 | let size = CGSize(width:realSize, height: realSize) 60 | let imageOptions = PHImageRequestOptions() 61 | imageOptions.resizeMode = .exact 62 | PHImageManager.default().requestImage(for: firstImageAsset, targetSize: size, contentMode: .aspectFill, options: imageOptions, resultHandler: { image, info in 63 | self.coverImage.image = image 64 | }) 65 | } 66 | } 67 | } 68 | 69 | var asset: PHAsset? { 70 | willSet { 71 | if newValue == nil { 72 | //coverImage.image = UIImage.size(self.coverImage.frame.size).color(gradient: [.lightGray, .white], locations: [0, 1], from: CGPoint(x: 0, y: 0), to: CGPoint(x: 0, y: 1)).image 73 | 74 | let containerSize = self.coverImage.frame.size 75 | let size = CGSize(width: containerSize.width * 0.8, height: containerSize.height * 0.6) 76 | 77 | coverImage.image = UIImage.size(containerSize).color(self.backgroundColor!).image + 78 | UIImage.size(size).color(UIColor.lightGray.withAlphaComponent(0.2)).corner(radius: 4).image 79 | .position(CGPoint(x: self.coverImage.frame.size.width * 0.15, y: self.coverImage.frame.size.height * 0.25)) + 80 | UIImage.size(size).border(color: UIColor.lightGray.withAlphaComponent(0.5)).border(width: 1).corner(radius: 4) 81 | .color(self.backgroundColor!) 82 | .image 83 | .position(CGPoint(x: self.coverImage.frame.size.width * 0.07, y: self.coverImage.frame.size.height * 0.17)) 84 | 85 | return 86 | } 87 | let realSize = self.coverImage.frame.width * UIScreen.main.scale 88 | let size = CGSize(width:realSize, height: realSize) 89 | PHCachingImageManager.default().requestImage(for: newValue!, targetSize: size, contentMode: .aspectFill, options: nil, resultHandler: { (img, _) in 90 | self.coverImage.image = img 91 | }) 92 | } 93 | } 94 | 95 | var albumTitleAndCount: (String?, Int)? { 96 | willSet { 97 | if newValue == nil { 98 | return 99 | } 100 | self.photoTitle.text = (newValue!.0 ?? "") 101 | self.photoNum.text = "(\(String(describing: newValue!.1)))" 102 | } 103 | } 104 | 105 | private lazy var coverImage: UIImageView = { 106 | let iv = UIImageView() 107 | iv.frame = CGRect(x:0,y:0,width:TGPhotoPickerConfig.shared.albumCellH,height:TGPhotoPickerConfig.shared.albumCellH) 108 | return iv 109 | }() 110 | 111 | private lazy var photoTitle: UILabel = { 112 | let label = UILabel() 113 | label.translatesAutoresizingMaskIntoConstraints = false 114 | label.backgroundColor = .clear 115 | label.textColor = .darkGray 116 | label.font = UIFont.systemFont(ofSize: TGPhotoPickerConfig.shared.fontSize) 117 | label.numberOfLines = 0 118 | return label 119 | }() 120 | 121 | private lazy var photoNum: UILabel = { 122 | let label = UILabel() 123 | label.translatesAutoresizingMaskIntoConstraints = false 124 | label.textColor = .lightGray 125 | label.font = UIFont.systemFont(ofSize: TGPhotoPickerConfig.shared.fontSize - 1.0) 126 | label.numberOfLines = 0 127 | return label 128 | }() 129 | } 130 | 131 | class TGPhotoListVC: UITableViewController { 132 | 133 | var albums = [TGFetchM]() 134 | 135 | override func viewDidLoad() { 136 | super.viewDidLoad() 137 | 138 | if !TGPhotoPickerConfig.shared.customSmartCollections.contains(.smartAlbumUserLibrary){//如果没有包含这项,则需要默认包含这项 139 | TGPhotoPickerConfig.shared.customSmartCollections.append(.smartAlbumUserLibrary) 140 | } 141 | 142 | // if #available(iOS 9.0, *) { 143 | // if !TGPhotoPickerConfig.shared.customSmartCollections.contains(.smartAlbumScreenshots){ 144 | // TGPhotoPickerConfig.shared.customSmartCollections.append(.smartAlbumScreenshots) 145 | // } 146 | // } 147 | 148 | PHPhotoLibrary.shared().register(self) 149 | setupTableView() 150 | setupNavigationBar() 151 | loadAlbums(true) 152 | } 153 | 154 | private func setupNavigationBar(){ 155 | self.navigationController?.navigationBar.barStyle = .black 156 | self.navigationController?.navigationBar.tintColor = .white 157 | self.navigationItem.title = TGPhotoPickerConfig.shared.albumTitle 158 | self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: TGPhotoPickerConfig.shared.cancelTitle, style: .plain, target: self, action: #selector(navDismiss)) 159 | } 160 | 161 | @objc private func navDismiss(){ 162 | let nav = self.navigationController as! TGPhotoPickerVC 163 | nav.assetArr.removeAll() 164 | self.navigationController?.dismiss(animated: true, completion: nil) 165 | } 166 | 167 | private func setupTableView(){ 168 | self.tableView.rowHeight = TGPhotoPickerConfig.shared.albumCellH 169 | self.tableView.separatorColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.15) 170 | self.tableView.separatorInset = UIEdgeInsets.zero 171 | self.tableView.tableFooterView = UIView(frame: CGRect.zero) 172 | self.tableView.register(TGPhotoListCell.self, forCellReuseIdentifier: cellIdentifier) 173 | } 174 | 175 | deinit{ 176 | PHPhotoLibrary.shared().unregisterChangeObserver(self) 177 | } 178 | 179 | fileprivate func loadAlbums(_ replace: Bool){ 180 | if replace { 181 | self.albums.removeAll() 182 | } 183 | 184 | let smartAlbums = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .albumRegular, options: nil) 185 | print("smartAlbums.count:\(smartAlbums.count)") 186 | for i in 0 ..< smartAlbums.count {//14 (contains `Recently Deleted`) 187 | if TGPhotoPickerConfig.shared.useCustomSmartCollectionsMask{ 188 | if TGPhotoPickerConfig.shared.customSmartCollections.contains(smartAlbums[i].assetCollectionSubtype){ 189 | self.filterFetchResult(collection: smartAlbums[i]) 190 | } 191 | }else{ 192 | self.filterFetchResult(collection: smartAlbums[i]) 193 | } 194 | } 195 | 196 | let topUserLibraryList = PHCollectionList.fetchTopLevelUserCollections(with: nil) 197 | for i in 0 ..< topUserLibraryList.count { 198 | if let topUserAlbumItem = topUserLibraryList[i] as? PHAssetCollection { 199 | self.filterFetchResult(collection: topUserAlbumItem) 200 | } 201 | } 202 | DispatchQueue.main.async { 203 | self.tableView.reloadData() 204 | } 205 | } 206 | 207 | private func filterFetchResult(collection: PHAssetCollection){ 208 | let fetchResult = PHAsset.fetchAssets(in: collection, options: TGPhotoFetchOptions()) 209 | print("\(String(describing: collection.localizedTitle)):\(fetchResult.count)") 210 | if TGPhotoPickerConfig.shared.isShowEmptyAlbum{ 211 | self.albums.append(TGFetchM(result: fetchResult as! PHFetchResult , name: collection.localizedTitle, assetType: collection.assetCollectionSubtype)) 212 | }else if fetchResult.count > 0 { 213 | self.albums.append(TGFetchM(result: fetchResult as! PHFetchResult , name: collection.localizedTitle, assetType: collection.assetCollectionSubtype)) 214 | } 215 | } 216 | 217 | override func numberOfSections(in tableView: UITableView) -> Int { 218 | return 1 219 | } 220 | 221 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 222 | return self.albums.count 223 | } 224 | 225 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 226 | //let cell = TGPhotoListCell.cellWithTableView(tableView) 227 | //cell.renderData(self.albums[indexPath.row].fetchResult as! PHFetchResult, label: self.albums[indexPath.row].name) 228 | let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! TGPhotoListCell 229 | cell.asset = self.albums[indexPath.row].fetchResult.firstObject as? PHAsset 230 | cell.albumTitleAndCount = (TGPhotoPickerConfig.shared.useChineseAlbumName ? 231 | TGPhotoPickerConfig.getChineseAlbumName(self.albums[indexPath.row].assetType,self.albums[indexPath.row].name) : 232 | self.albums[indexPath.row].name, 233 | self.albums[indexPath.row].fetchResult.count) 234 | return cell 235 | } 236 | 237 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 238 | tableView.deselectRow(at: indexPath, animated: true) 239 | let layout = TGPhotoCollectionVC.configCustomCollectionLayout() 240 | let vc = TGPhotoCollectionVC(collectionViewLayout: layout) 241 | vc.navigationItem.title = TGPhotoPickerConfig.shared.useChineseAlbumName ? TGPhotoPickerConfig.getChineseAlbumName(self.albums[indexPath.row].assetType,self.albums[indexPath.row].name) : self.albums[indexPath.row].name 242 | vc.fetchResult = albums[indexPath.row].fetchResult as? PHFetchResult 243 | self.navigationController?.pushViewController(vc, animated: true) 244 | } 245 | } 246 | 247 | extension TGPhotoListVC : PHPhotoLibraryChangeObserver{ 248 | func photoLibraryDidChange(_ changeInstance: PHChange) { 249 | loadAlbums(true) 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/TGPhotoPicker/TGPhotoM.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TGPhotoM.swift 3 | // TGPhotoPicker 4 | // 5 | // Created by targetcloud on 2017/7/12. 6 | // Copyright © 2017年 targetcloud. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | class TGPhotoM: NSObject { 13 | var smallImage: UIImage? 14 | var bigImage: UIImage? 15 | var imageData: Data? 16 | var asset: PHAsset?{ 17 | didSet{ 18 | if self.asset?.mediaType == .video{ 19 | if self.asset?.duration == nil{ 20 | self.videoLength = "" 21 | }else{ 22 | self.videoLength = TGPhotoM.getNewTimeFromDuration(duration: (self.asset?.duration)!) 23 | } 24 | } 25 | } 26 | } 27 | var order: Int = 0 28 | 29 | var videoLength: String? 30 | 31 | convenience init(asset: PHAsset) { 32 | self.init() 33 | 34 | let imageManeger = PHImageManager() 35 | 36 | let smallOptions = PHImageRequestOptions() 37 | smallOptions.deliveryMode = .opportunistic 38 | smallOptions.resizeMode = .fast 39 | 40 | let bigOptions = PHImageRequestOptions() 41 | bigOptions.deliveryMode = .opportunistic 42 | bigOptions.resizeMode = .exact 43 | 44 | self.asset = asset 45 | 46 | let bigSize = CGSize(width: asset.pixelWidth, height: asset.pixelHeight) 47 | imageManeger.requestImage(for: asset, targetSize: bigSize, contentMode: PHImageContentMode(rawValue: 0)!, options: bigOptions, resultHandler: { (image, info) in 48 | if image != nil{ 49 | self.bigImage = image! 50 | } 51 | }) 52 | 53 | let smallSize = CGSize(width: TGPhotoPickerConfig.shared.selectWH * UIScreen.main.scale, height: TGPhotoPickerConfig.shared.selectWH * UIScreen.main.scale) 54 | imageManeger.requestImage(for: asset, targetSize: smallSize, contentMode: PHImageContentMode(rawValue: 0)!, options: smallOptions, resultHandler: { (image, info) in 55 | if image != nil{ 56 | self.smallImage = image! 57 | } 58 | }) 59 | 60 | imageManeger.requestImageData(for: asset, options: bigOptions, resultHandler: { (data, str, imageOrientation, info) in 61 | if data != nil{ 62 | self.imageData = data! 63 | } 64 | }) 65 | } 66 | 67 | class func getImagesAndDatas(photos:[PHAsset], imageData:@escaping(_ photoArr: [TGPhotoM])->()){ 68 | let smallOptions = PHImageRequestOptions() 69 | smallOptions.deliveryMode = .highQualityFormat 70 | smallOptions.resizeMode = .fast 71 | 72 | let bigOptions = PHImageRequestOptions() 73 | bigOptions.deliveryMode = .highQualityFormat 74 | bigOptions.resizeMode = .exact 75 | 76 | let imageManeger = PHImageManager() 77 | let smallSize = CGSize(width: TGPhotoPickerConfig.shared.mainCellWH * UIScreen.main.scale, height: TGPhotoPickerConfig.shared.mainCellWH * UIScreen.main.scale) 78 | 79 | var modelArr = [TGPhotoM]() 80 | for i in 0.. String{ 113 | var newTimer = "" 114 | if duration < 10 { 115 | newTimer = String(format: "0:0%d", arguments: [Int(duration)]) 116 | return newTimer 117 | } else if duration < 60 && duration >= 10 { 118 | newTimer = String(format: "0:%.0f", arguments: [duration]) 119 | return newTimer 120 | } else { 121 | let min = Int(duration/60) 122 | let sec = Int(duration - (Double(min)*60)) 123 | if sec < 10 { 124 | newTimer = String(format: "%d:0%d", arguments: [min ,sec]) 125 | return newTimer 126 | } else { 127 | newTimer = String(format: "%d:%d", arguments: [min ,sec]) 128 | return newTimer 129 | } 130 | } 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/TGPhotoPicker/TGPhotoPicker.bundle/camera@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/TGPhotoPicker/TGPhotoPicker/TGPhotoPicker/TGPhotoPicker.bundle/camera@2x.png -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/TGPhotoPicker/TGPhotoPicker.bundle/flash@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/TGPhotoPicker/TGPhotoPicker/TGPhotoPicker/TGPhotoPicker.bundle/flash@2x.png -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/TGPhotoPicker/TGPhotoPicker.bundle/flashauto@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/TGPhotoPicker/TGPhotoPicker/TGPhotoPicker/TGPhotoPicker.bundle/flashauto@2x.png -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/TGPhotoPicker/TGPhotoPicker.bundle/flashno@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/TGPhotoPicker/TGPhotoPicker/TGPhotoPicker/TGPhotoPicker.bundle/flashno@2x.png -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/TGPhotoPicker/TGPhotoPickerManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TGPhotoPickerManager.swift 3 | // TGPhotoPicker 4 | // 5 | // Created by targetcloud on 2017/7/25. 6 | // Copyright © 2017年 targetcloud. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | class TGPhotoPickerManager: NSObject { 13 | static let shared = TGPhotoPickerManager() 14 | var handlePhotosBlock: HandlePhotosBlock? 15 | var handlePhotoModelsBlock: HandlePhotoModelsBlock? 16 | 17 | private override init() { 18 | super.init() 19 | } 20 | 21 | fileprivate lazy var config: TGPhotoPickerConfig = TGPhotoPickerConfig.shared 22 | 23 | func takePhotos(_ showCamera: Bool, _ showAlbum: Bool, _ configBlock:((_ config:TGPhotoPickerConfig)->())? = nil, _ completeHandler: @escaping HandlePhotosBlock){ 24 | configBlock?(self.config) 25 | self.handlePhotosBlock = completeHandler 26 | 27 | if config.useCustomActionSheet{ 28 | let sheet = TGActionSheet(delegate: self, cancelTitle: config.cancelTitle, otherTitles: [config.cameraTitle, config.selectTitle]) 29 | sheet.show() 30 | return 31 | } 32 | 33 | let ac = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) 34 | 35 | let action1 = UIAlertAction(title: config.cameraTitle, style: .default) { (action) in 36 | self.actionSheet(actionSheet: nil, didClickedAt: 0) 37 | } 38 | 39 | let action2 = UIAlertAction(title: config.selectTitle, style: .default) { (action) in 40 | self.actionSheet(actionSheet: nil, didClickedAt: 1) 41 | } 42 | showCamera ? ac.addAction(action1) : () 43 | showAlbum ? ac.addAction(action2) : () 44 | ac.addAction(UIAlertAction(title: config.cancelTitle, style: .cancel, handler: nil)) 45 | UIApplication.shared.keyWindow?.currentVC()?.present(ac, animated: true, completion: nil) 46 | } 47 | 48 | func takePhotoModels(_ showCamera: Bool, _ showAlbum: Bool, _ configBlock:((_ config:TGPhotoPickerConfig)->())? = nil, _ completeHandler: @escaping HandlePhotoModelsBlock){ 49 | configBlock?(self.config) 50 | self.handlePhotoModelsBlock = completeHandler 51 | 52 | if config.useCustomActionSheet{ 53 | let sheet = TGActionSheet(delegate: self, cancelTitle: config.cancelTitle, otherTitles: [config.cameraTitle, config.selectTitle]) 54 | sheet.show() 55 | return 56 | } 57 | 58 | let ac = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) 59 | 60 | let action1 = UIAlertAction(title: config.cameraTitle, style: .default) { (action) in 61 | self.actionSheet(actionSheet: nil, didClickedAt: 0) 62 | } 63 | 64 | let action2 = UIAlertAction(title: config.selectTitle, style: .default) { (action) in 65 | self.actionSheet(actionSheet: nil, didClickedAt: 1) 66 | } 67 | showCamera ? ac.addAction(action1) : () 68 | showAlbum ? ac.addAction(action2) : () 69 | ac.addAction(UIAlertAction(title: config.cancelTitle, style: .cancel, handler: nil)) 70 | UIApplication.shared.keyWindow?.currentVC()?.present(ac, animated: true, completion: nil) 71 | } 72 | 73 | func authorizePhotoLibrary(authorizeClosure:@escaping (PHAuthorizationStatus)->()){ 74 | let status = PHPhotoLibrary.authorizationStatus() 75 | 76 | if status == .authorized{ 77 | authorizeClosure(status) 78 | } else if status == .notDetermined { 79 | PHPhotoLibrary.requestAuthorization({ (state) in 80 | DispatchQueue.main.async(execute: { 81 | authorizeClosure(state) 82 | }) 83 | }) 84 | } else { 85 | let sheet = TGActionSheet(delegate: self, title: config.photoLibraryUsage + "("+config.photoLibraryUsageTip+")",cancelTitle: config.cancelTitle, otherTitles: [config.confirmTitle]) 86 | sheet.name = "photoLibraryAuthorize" 87 | sheet.show() 88 | authorizeClosure(status) 89 | } 90 | } 91 | 92 | func authorizeCamera(authorizeClosure:@escaping (AVAuthorizationStatus)->()){ 93 | let status = AVCaptureDevice.authorizationStatus(forMediaType: AVMediaTypeVideo) 94 | 95 | if status == .authorized{ 96 | authorizeClosure(status) 97 | } else if status == .notDetermined { 98 | AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeVideo, completionHandler: { (granted) in 99 | if granted { 100 | authorizeClosure(.authorized) 101 | } 102 | }) 103 | } else { 104 | let sheet = TGActionSheet(delegate: self, title: config.cameraUsage + "("+config.cameraUsageTip+")",cancelTitle: config.cancelTitle, otherTitles: [config.confirmTitle]) 105 | sheet.name = "cameraAuthorize" 106 | sheet.show() 107 | authorizeClosure(status) 108 | } 109 | } 110 | 111 | public static func convertAssetArrToImageArr(assetArr:Array,scale:CGFloat = TGPhotoPickerConfig.shared.compressionQuality) -> [UIImage] { 112 | var imageArr = [UIImage]() 113 | for item in assetArr { 114 | if item.mediaType == .image { 115 | getAssetOrigin(asset: item, dealImageSuccess: { (img, info) in 116 | guard img != nil else{ return } 117 | if let zipImageData = UIImageJPEGRepresentation(img!,scale){ 118 | let image = UIImage(data: zipImageData) 119 | imageArr.append(image!) 120 | } 121 | }) 122 | } 123 | } 124 | return imageArr 125 | } 126 | 127 | public static func convertAssetArrToAVPlayerItemArr(assetArr:Array) -> [AVPlayerItem] { 128 | var videoArr = [AVPlayerItem]() 129 | for item in assetArr { 130 | if item.mediaType == .video { 131 | let videoRequestOptions = PHVideoRequestOptions() 132 | videoRequestOptions.deliveryMode = .automatic 133 | videoRequestOptions.version = .current 134 | videoRequestOptions.isNetworkAccessAllowed = true 135 | PHImageManager.default().requestPlayerItem(forVideo: item, options: videoRequestOptions) { (playItem, info) in 136 | if playItem != nil { 137 | videoArr.append(playItem!) 138 | } 139 | } 140 | } 141 | } 142 | return videoArr 143 | } 144 | 145 | static func getAssetOrigin(asset:PHAsset,dealImageSuccess:@escaping (UIImage?,[AnyHashable:Any]?) -> ()) { 146 | let option = PHImageRequestOptions() 147 | option.isSynchronous = true 148 | PHImageManager.default().requestImage(for: asset, targetSize:PHImageManagerMaximumSize, contentMode: .aspectFit, options: option) { (originImage, info) in 149 | dealImageSuccess(originImage,info) 150 | } 151 | } 152 | 153 | deinit { 154 | //print("TGPhotoPickerManager deinit") 155 | } 156 | } 157 | 158 | extension TGPhotoPickerManager: TGActionSheetDelegate { 159 | func actionSheet(actionSheet: TGActionSheet?, didClickedAt index: Int) { 160 | switch actionSheet?.name ?? "" { 161 | case "photoLibraryAuthorize","cameraAuthorize": 162 | switch index { 163 | case 0: 164 | let url = URL(string: UIApplicationOpenSettingsURLString) 165 | if let url = url, UIApplication.shared.canOpenURL(url) { 166 | if #available(iOS 10, *) { 167 | if UIApplication.shared.canOpenURL(url){ 168 | UIApplication.shared.open(url, options: [:],completionHandler: {(success) in 169 | 170 | }) 171 | } 172 | } else { 173 | if UIApplication.shared.canOpenURL(url){ 174 | UIApplication.shared.openURL(url) 175 | } 176 | } 177 | } 178 | default: 179 | break 180 | } 181 | default: 182 | switch index { 183 | case 0: 184 | if TGPhotoPickerConfig.shared.useiOS8Camera { 185 | let cameraVC = TGCameraVCForiOS8() 186 | cameraVC.callbackPicutureData = { imgData in 187 | let bigImg = UIImage(data:imgData!) 188 | let imgData = UIImageJPEGRepresentation(bigImg!,TGPhotoPickerConfig.shared.compressionQuality) 189 | let smallImg = bigImg 190 | let model = TGPhotoM() 191 | model.bigImage = bigImg 192 | model.imageData = imgData 193 | model.smallImage = smallImg 194 | self.handlePhotoModelsBlock?([model]) 195 | self.handlePhotosBlock?([nil],[smallImg],[bigImg],[imgData]) 196 | } 197 | UIApplication.shared.keyWindow?.currentVC()?.present(cameraVC, animated: true, completion: nil) 198 | } else if #available(iOS 10.0, *) { 199 | let cameraVC = TGCameraVC() 200 | cameraVC.callbackPicutureData = { imgData in 201 | let bigImg = UIImage(data:imgData!) 202 | let imgData = UIImageJPEGRepresentation(bigImg!,TGPhotoPickerConfig.shared.compressionQuality) 203 | let smallImg = bigImg 204 | let model = TGPhotoM() 205 | model.bigImage = bigImg 206 | model.imageData = imgData 207 | model.smallImage = smallImg 208 | self.handlePhotoModelsBlock?([model]) 209 | self.handlePhotosBlock?([nil],[smallImg],[bigImg],[imgData]) 210 | } 211 | UIApplication.shared.keyWindow?.currentVC()?.present(cameraVC, animated: true, completion: nil) 212 | } else { 213 | let cameraVC = TGCameraVCForiOS8() 214 | cameraVC.callbackPicutureData = { imgData in 215 | let bigImg = UIImage(data:imgData!) 216 | let imgData = UIImageJPEGRepresentation(bigImg!,TGPhotoPickerConfig.shared.compressionQuality) 217 | let smallImg = bigImg 218 | let model = TGPhotoM() 219 | model.bigImage = bigImg 220 | model.imageData = imgData 221 | model.smallImage = smallImg 222 | self.handlePhotoModelsBlock?([model]) 223 | self.handlePhotosBlock?([nil],[smallImg],[bigImg],[imgData]) 224 | } 225 | UIApplication.shared.keyWindow?.currentVC()?.present(cameraVC, animated: true, completion: nil) 226 | } 227 | case 1: 228 | authorizePhotoLibrary(authorizeClosure: { (status) in 229 | if status == .authorized{ 230 | let pickervc = TGPhotoPickerVC(type: .allAlbum) 231 | pickervc.callbackPhotos = self.handlePhotosBlock 232 | pickervc.callbackPhotoMs = self.handlePhotoModelsBlock 233 | UIApplication.shared.keyWindow?.currentVC()?.present(pickervc, animated: true, completion: nil) 234 | } 235 | }) 236 | default: 237 | break 238 | } 239 | } 240 | 241 | } 242 | } 243 | 244 | extension UIWindow { 245 | public func topMostVC()->UIViewController? { 246 | var topController = rootViewController 247 | while let presentedController = topController?.presentedViewController { 248 | topController = presentedController 249 | } 250 | return topController 251 | } 252 | 253 | public func currentVC()->UIViewController? { 254 | var currentViewController = topMostVC() 255 | while currentViewController != nil && 256 | currentViewController is UINavigationController && 257 | (currentViewController as! UINavigationController).topViewController != nil { 258 | currentViewController = (currentViewController as! UINavigationController).topViewController 259 | } 260 | return currentViewController 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/TGPhotoPicker/TGPhotoPickerVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TGPhotoPickerVC.swift 3 | // TGPhotoPicker 4 | // 5 | // Created by targetcloud on 2017/7/21. 6 | // Copyright © 2017年 targetcloud. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | enum TGPageType{ 13 | case list 14 | case recentAlbum 15 | case allAlbum 16 | } 17 | 18 | protocol TGPhotoPickerDelegate: class{ 19 | func onImageSelectFinished(images: [PHAsset]) 20 | } 21 | 22 | typealias HandlePhotosBlock = (_ asset:[PHAsset?], _ smallImage:[UIImage?],_ bigImage:[UIImage?],_ imageData:[Data?]) -> Void 23 | typealias HandlePhotoModelsBlock = (_ photoMs:[TGPhotoM]) -> Void 24 | 25 | class TGPhotoPickerVC: UINavigationController { 26 | 27 | var alreadySelectedImageNum = 0 28 | var assetArr = [PHAsset]() 29 | weak var imageSelectDelegate: TGPhotoPickerDelegate? 30 | var callbackPhotos: HandlePhotosBlock? 31 | var callbackPhotoMs: HandlePhotoModelsBlock? 32 | 33 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 34 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 35 | } 36 | 37 | override var prefersStatusBarHidden: Bool{ 38 | return false 39 | } 40 | 41 | override var preferredStatusBarStyle: UIStatusBarStyle { 42 | return .lightContent 43 | } 44 | 45 | init(type: TGPageType){ 46 | let rootVC = TGPhotoListVC(style: .plain) 47 | super.init(rootViewController: rootVC) 48 | 49 | self.navigationBar.setBackgroundImage(UIImage.size(width: 1, height: 1).color(TGPhotoPickerConfig.shared.barBGColor).image, for: UIBarMetrics.default) 50 | 51 | if #available(iOS 9.0, *) { 52 | let isVCBased = Bundle.main.infoDictionary?["UIViewControllerBasedStatusBarAppearance"] as? Bool ?? false 53 | if !isVCBased{ 54 | UIApplication.shared.setStatusBarHidden(false, with: .none) 55 | } 56 | }else { 57 | UIApplication.shared.statusBarStyle = .lightContent 58 | UIApplication.shared.setStatusBarHidden(false, with: .none) 59 | } 60 | 61 | if type == .recentAlbum || type == .allAlbum { 62 | let currentType = type == .recentAlbum ? PHAssetCollectionSubtype.smartAlbumRecentlyAdded : PHAssetCollectionSubtype.smartAlbumUserLibrary 63 | 64 | let results = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype:currentType, options: nil) 65 | if results.count > 0 { 66 | if let model = self.getModel(collection: results[0]) { 67 | if model.count > 0 { 68 | let layout = TGPhotoCollectionVC.configCustomCollectionLayout() 69 | let vc = TGPhotoCollectionVC(collectionViewLayout: layout) 70 | vc.fetchResult = model 71 | vc.title = TGPhotoPickerConfig.shared.useChineseAlbumName ? TGPhotoPickerConfig.getChineseAlbumName(currentType) : results[0].localizedTitle 72 | self.pushViewController(vc, animated: false) 73 | } 74 | } 75 | } 76 | } 77 | } 78 | 79 | required init?(coder aDecoder: NSCoder) { 80 | fatalError("init(coder:) has not been implemented") 81 | } 82 | 83 | private func getModel(collection: PHAssetCollection) -> PHFetchResult?{ 84 | let fetchResult = PHAsset.fetchAssets(in: collection, options: TGPhotoFetchOptions()) 85 | return fetchResult.count > 0 ? fetchResult : nil 86 | } 87 | 88 | func imageSelectFinish(){ 89 | self.dismiss(animated: true, completion: { 90 | self.imageSelectDelegate?.onImageSelectFinished(images: self.assetArr) 91 | TGPhotoM.getImagesAndDatas(photos: self.assetArr) { array in 92 | self.callbackPhotos?(self.assetArr,array.map{$0.smallImage},array.map{$0.bigImage},array.map{$0.imageData}) 93 | self.callbackPhotoMs?(array) 94 | } 95 | }) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/TGPhotoPicker/TGPhotoPreviewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TGPhotoPreviewCell.swift 3 | // TGPhotoPicker 4 | // 5 | // Created by targetcloud on 2017/7/19. 6 | // Copyright © 2017年 targetcloud. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | 12 | protocol TGPhotoPreviewCellDelegate: class{ 13 | func onImageSingleTap() 14 | } 15 | 16 | class TGPhotoPreviewCell: UICollectionViewCell { 17 | var asset: PHAsset?{ 18 | didSet{ 19 | let photoImageManager = TGPhotoImageManager() 20 | photoImageManager.getPhotoByMaxSize(asset: asset!, size: self.bounds.width) { (image, data, info) -> Void in 21 | self.imageView.image = image 22 | self.resizeImageView() 23 | } 24 | } 25 | } 26 | 27 | var image: UIImage?{ 28 | didSet{ 29 | self.imageView.image = image 30 | self.resizeImageView() 31 | } 32 | } 33 | 34 | weak var delegate: TGPhotoPreviewCellDelegate? 35 | 36 | fileprivate var imageContainerView = UIView() 37 | private var imageView = UIImageView() 38 | private var scrollView: UIScrollView? 39 | 40 | override init(frame: CGRect) { 41 | super.init(frame: frame) 42 | setupUI() 43 | } 44 | 45 | required init?(coder aDecoder: NSCoder) { 46 | super.init(coder: aDecoder) 47 | setupUI() 48 | } 49 | 50 | private func setupUI() { 51 | self.scrollView = UIScrollView(frame: self.bounds) 52 | //self.scrollView!.bouncesZoom = true// default is YES. if set, user can go past min/max zoom while gesturing and the zoom will animate to the min/max value at gesture end 53 | self.scrollView!.maximumZoomScale = 2.5 54 | self.scrollView!.isMultipleTouchEnabled = true 55 | self.scrollView!.delegate = self 56 | self.scrollView!.scrollsToTop = false 57 | self.scrollView!.showsHorizontalScrollIndicator = false 58 | self.scrollView!.showsVerticalScrollIndicator = false 59 | self.scrollView!.autoresizingMask = [.flexibleWidth, .flexibleHeight] 60 | self.scrollView!.delaysContentTouches = false// default is YES. if NO, we immediately call -touchesShouldBegin:withEvent:inContentView:. this has no effect on presses 61 | //self.scrollView!.canCancelContentTouches = true// default is YES. if NO, then once we start tracking, we don't try to drag if the touch moves. this has no effect on presses 62 | //self.scrollView!.alwaysBounceVertical = false// default NO. if YES and bounces is YES, even if content is smaller than bounds, allow drag vertically 63 | self.addSubview(self.scrollView!) 64 | 65 | self.imageContainerView.clipsToBounds = true 66 | self.scrollView!.addSubview(self.imageContainerView) 67 | 68 | self.imageView.backgroundColor = UIColor(white: 1.0, alpha: 0.5) 69 | self.imageView.clipsToBounds = true 70 | self.imageContainerView.addSubview(self.imageView) 71 | 72 | let singleTap = UITapGestureRecognizer(target: self, action: #selector(self.singleTap(tap:))) 73 | let doubleTap = UITapGestureRecognizer(target: self, action: #selector(self.doubleTap(tap:))) 74 | 75 | doubleTap.numberOfTapsRequired = 2 76 | singleTap.require(toFail: doubleTap) 77 | 78 | self.addGestureRecognizer(singleTap) 79 | self.addGestureRecognizer(doubleTap) 80 | } 81 | 82 | private func resizeImageView() { 83 | self.imageContainerView.frame = CGRect(x:0, y:0, width: self.frame.width, height: self.imageContainerView.bounds.height) 84 | let image = self.imageView.image! 85 | 86 | if image.size.height / image.size.width > self.bounds.height / self.bounds.width { 87 | var originFrame = self.imageContainerView.frame 88 | originFrame.size.height = floor((image.size.height / image.size.width) * self.bounds.width) 89 | self.imageContainerView.frame = originFrame 90 | } else { 91 | var height = (image.size.height / image.size.width) * self.frame.width 92 | if height < 1 || height.isNaN { 93 | height = self.frame.height 94 | } 95 | var originFrame = self.imageContainerView.frame 96 | originFrame.size.height = floor(height) 97 | self.imageContainerView.frame = originFrame 98 | self.imageContainerView.center = CGPoint(x:self.imageContainerView.center.x, y:self.bounds.height / 2) 99 | } 100 | 101 | if self.imageContainerView.frame.height > self.frame.height && self.imageContainerView.frame.height - self.frame.height <= 1 { 102 | var originFrame = self.imageContainerView.frame 103 | originFrame.size.height = self.frame.height 104 | self.imageContainerView.frame = originFrame 105 | } 106 | 107 | self.scrollView?.contentSize = CGSize(width: self.frame.width, height: max(self.imageContainerView.frame.height, self.frame.height)) 108 | self.scrollView?.scrollRectToVisible(self.bounds, animated: false) 109 | self.scrollView?.alwaysBounceVertical = self.imageContainerView.frame.height > self.frame.height 110 | self.imageView.frame = self.imageContainerView.bounds 111 | } 112 | 113 | @objc private func singleTap(tap:UITapGestureRecognizer) { 114 | delegate?.onImageSingleTap() 115 | } 116 | 117 | @objc private func doubleTap(tap:UITapGestureRecognizer) { 118 | if (self.scrollView!.zoomScale > 1.0) { 119 | self.scrollView?.setZoomScale(1.0, animated: true) 120 | } else { 121 | let touchPoint = tap.location(in: self.imageView) 122 | let zoomScale = self.scrollView?.maximumZoomScale 123 | let w = self.frame.size.width / zoomScale! 124 | let h = self.frame.size.height / zoomScale! 125 | self.scrollView?.zoom(to: CGRect(x: touchPoint.x - w/2, y: touchPoint.y - h/2, width: w, height: h), animated: true) 126 | } 127 | } 128 | } 129 | 130 | extension TGPhotoPreviewCell: UIScrollViewDelegate{ 131 | func viewForZooming(in scrollView: UIScrollView) -> UIView? { 132 | return self.imageContainerView 133 | } 134 | 135 | func scrollViewDidZoom(_ scrollView: UIScrollView) { 136 | let offsetX = (scrollView.frame.width > scrollView.contentSize.width) ? (scrollView.frame.width - scrollView.contentSize.width) * 0.5 : 0.0 137 | let offsetY = (scrollView.frame.height > scrollView.contentSize.height) ? (scrollView.frame.height - scrollView.contentSize.height) * 0.5 : 0.0 138 | self.imageContainerView.center = CGPoint(x: scrollView.contentSize.width * 0.5 + offsetX, y: scrollView.contentSize.height * 0.5 + offsetY) 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/TGPhotoPicker/TGPhotoPreviewVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TGPhotoPreviewVC.swift 3 | // TGPhotoPicker 4 | // 5 | // Created by targetcloud on 2017/7/19. 6 | // Copyright © 2017年 targetcloud. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol TGPhotoPreviewDelegate:class { 12 | func removeElement(element: TGPhotoM?) 13 | } 14 | 15 | private let cellIdentifier = "TGPhotoPreviewCell" 16 | 17 | class TGPhotoPreviewVC: UIViewController { 18 | 19 | var selectImages = [TGPhotoM]() 20 | var currentPage: Int = 0 21 | weak var delegate: TGPhotoPreviewDelegate? 22 | 23 | private var cv: UICollectionView? 24 | 25 | fileprivate var isStatusBarHidden = false{ 26 | didSet{ 27 | self.setNeedsStatusBarAppearanceUpdate() 28 | if TGPhotoPickerConfig.shared.indicatorPosition == .top { 29 | indicatorLabel.y = (isStatusBarHidden ? 0 : 64) + 5 30 | } 31 | } 32 | } 33 | 34 | override func viewDidLoad() { 35 | super.viewDidLoad() 36 | setupUI() 37 | } 38 | 39 | override func viewWillAppear(_ animated: Bool) { 40 | super.viewWillAppear(animated) 41 | updatePageTitle() 42 | self.cv?.setContentOffset(CGPoint(x: CGFloat(self.currentPage) * self.view.bounds.width, y: 0), animated: false) 43 | } 44 | 45 | private func setupUI(){ 46 | setupNavigationBar() 47 | setupCollectionView() 48 | } 49 | 50 | private func setupNavigationBar(){ 51 | UIApplication.shared.statusBarStyle = .lightContent 52 | self.navigationController?.navigationBar.barStyle = .black 53 | 54 | self.navigationController?.navigationBar.setBackgroundImage(UIImage.size(width: 1, height: 1).color(TGPhotoPickerConfig.shared.barBGColor).image, for: UIBarMetrics.default) 55 | self.navigationController?.navigationBar.tintColor = .white 56 | 57 | let WH = TGPhotoPickerConfig.shared.checkboxBarWH * 0.8 58 | self.navigationItem.leftBarButtonItem = UIBarButtonItem(image: UIImage.size(width: WH, height: WH) 59 | .corner(radius: WH * 0.5) 60 | .color(.clear) 61 | .border(color: UIColor.white.withAlphaComponent(0.7)) 62 | .border(width: TGPhotoPickerConfig.shared.isShowBorder ? TGPhotoPickerConfig.shared.checkboxLineW : 0) 63 | .image 64 | .with({ context in 65 | context.setLineCap(.round) 66 | UIColor.white.setStroke() 67 | context.setLineWidth(TGPhotoPickerConfig.shared.checkboxLineW) 68 | context.move(to: CGPoint(x: WH * (TGPhotoPickerConfig.shared.isShowBorder ? 0.55 : 0.6), y: WH * (TGPhotoPickerConfig.shared.isShowBorder ? 0.25: 0.2))) 69 | context.addLine(to: CGPoint(x: WH * (TGPhotoPickerConfig.shared.isShowBorder ? 0.4 : 0.35), y: WH * 0.5)) 70 | context.move(to: CGPoint(x: WH * (TGPhotoPickerConfig.shared.isShowBorder ? 0.4 : 0.35), y: WH * 0.5)) 71 | context.addLine(to: CGPoint(x: WH * (TGPhotoPickerConfig.shared.isShowBorder ? 0.55 : 0.6), y: WH * (TGPhotoPickerConfig.shared.isShowBorder ? 0.75 : 0.8))) 72 | // context.move(to: CGPoint(x: 12, y: 4)) 73 | // context.addLine(to: CGPoint(x: 7, y: 10)) 74 | // context.move(to: CGPoint(x: 7, y: 10)) 75 | // context.addLine(to: CGPoint(x: 12, y: 16)) 76 | context.strokePath() 77 | }), style: .plain, target: self, action: #selector(dissmiss)) 78 | 79 | self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage.size(width: 20, height: 20) 80 | .color(.clear) 81 | .image 82 | .with({ context in 83 | context.setLineCap(.round) 84 | UIColor.white.setStroke() 85 | context.setLineWidth(TGPhotoPickerConfig.shared.checkboxLineW) 86 | context.move(to: CGPoint(x: 7, y: 3)) 87 | context.addLine(to: CGPoint(x: 13, y: 3)) 88 | context.move(to: CGPoint(x: 3, y: 4)) 89 | context.addLine(to: CGPoint(x: 17, y: 4)) 90 | context.move(to: CGPoint(x: 4, y: 18)) 91 | context.addLine(to: CGPoint(x: 16, y: 18)) 92 | 93 | context.move(to: CGPoint(x: 4, y: 5)) 94 | context.addLine(to: CGPoint(x: 4, y: 17)) 95 | 96 | context.move(to: CGPoint(x: 8, y: 7)) 97 | context.addLine(to: CGPoint(x: 8, y: 14)) 98 | 99 | context.move(to: CGPoint(x: 12, y: 7)) 100 | context.addLine(to: CGPoint(x: 12, y: 14)) 101 | 102 | context.move(to: CGPoint(x: 16, y: 5)) 103 | context.addLine(to: CGPoint(x: 16, y: 17)) 104 | context.strokePath() 105 | }), style: .plain, target: self, action: #selector(remove)) 106 | } 107 | 108 | @objc private func dissmiss(){ 109 | let animation = CATransition() 110 | animation.duration = 0.2 111 | animation.subtype = kCATransitionFromRight 112 | UIApplication.shared.keyWindow?.layer.add(animation, forKey: nil) 113 | dismiss(animated: false, completion: nil) 114 | } 115 | 116 | @objc private func remove(){ 117 | delegate?.removeElement(element: self.selectImages.remove(at: currentPage)) 118 | updatePageTitle() 119 | self.cv?.deselectItem(at: IndexPath(row: currentPage, section: 0), animated: true) 120 | 121 | if self.selectImages.count > 0{ 122 | self.currentPage = self.currentPage > self.selectImages.count - 1 ? (self.selectImages.count - 1) : self.currentPage 123 | self.cv?.reloadData() 124 | } else { 125 | _ = self.navigationController?.popViewController(animated: true) 126 | dissmiss() 127 | } 128 | } 129 | 130 | private func setupCollectionView(){ 131 | self.automaticallyAdjustsScrollViewInsets = false 132 | 133 | let layout = UICollectionViewFlowLayout() 134 | layout.scrollDirection = .horizontal 135 | layout.itemSize = CGSize(width:self.view.frame.width,height: self.view.frame.height) 136 | layout.minimumInteritemSpacing = 0 137 | layout.minimumLineSpacing = 0 138 | 139 | self.cv = UICollectionView(frame: self.view.bounds, collectionViewLayout: layout) 140 | self.cv!.dataSource = self 141 | self.cv!.delegate = self 142 | self.cv!.isPagingEnabled = true 143 | self.cv!.scrollsToTop = false 144 | self.cv!.showsHorizontalScrollIndicator = false 145 | self.cv!.contentOffset = CGPoint.zero 146 | self.cv!.contentSize = CGSize(width: self.view.bounds.width * CGFloat(self.selectImages.count), height: self.view.bounds.height) 147 | self.cv!.register(TGPhotoPreviewCell.self, forCellWithReuseIdentifier: cellIdentifier) 148 | self.view.addSubview(self.cv!) 149 | 150 | switch TGPhotoPickerConfig.shared.indicatorPosition { 151 | case .inTopBar: 152 | self.navigationItem.titleView = indicatorLabel 153 | case .top: 154 | self.view.addSubview(indicatorLabel) 155 | indicatorLabel.x = (TGPhotoPickerConfig.ScreenW - indicatorLabel.w) / 2 156 | indicatorLabel.y = 64 + 5 157 | case .bottom,.inBottomBar: 158 | self.view.addSubview(indicatorLabel) 159 | indicatorLabel.x = (TGPhotoPickerConfig.ScreenW - indicatorLabel.w) / 2 160 | indicatorLabel.y = self.view.h - indicatorLabel.h - 5 161 | } 162 | } 163 | 164 | fileprivate func updatePageTitle(){ 165 | //self.title = String(self.currentPage+1) + "/" + String(self.selectImages.count) 166 | let attributeString = NSMutableAttributedString(string:"\(self.currentPage+1) / \(self.selectImages.count)") 167 | attributeString.addAttribute(NSFontAttributeName, 168 | value: UIFont.boldSystemFont(ofSize: TGPhotoPickerConfig.shared.fontSize+3), 169 | range: NSMakeRange(0,"\(self.currentPage+1) ".characters.count)) 170 | 171 | attributeString.addAttribute(NSFontAttributeName, 172 | value: UIFont.boldSystemFont(ofSize: TGPhotoPickerConfig.shared.fontSize), 173 | range: NSMakeRange("\(self.currentPage+1) ".characters.count,1)) 174 | 175 | attributeString.addAttribute(NSFontAttributeName, 176 | value: UIFont.systemFont(ofSize: TGPhotoPickerConfig.shared.fontSize-3), 177 | range: NSMakeRange("\(self.currentPage+1) / ".characters.count,"\(self.selectImages.count)".characters.count)) 178 | indicatorLabel.attributedText = attributeString 179 | } 180 | 181 | fileprivate lazy var indicatorLabel: UILabel = { 182 | let indicatorLbl = UILabel(frame: CGRect(x: 0, y: ((self.navigationController?.navigationBar.height)! - TGPhotoPickerConfig.shared.toolBarH * 0.8) / 2, width: 0, height: TGPhotoPickerConfig.shared.toolBarH * 0.8)) 183 | indicatorLbl.isHidden = false 184 | indicatorLbl.text = "0 / \(TGPhotoPickerConfig.shared.maxImageCount)" 185 | indicatorLbl.font = UIFont.systemFont(ofSize: TGPhotoPickerConfig.shared.fontSize+1) 186 | indicatorLbl.layer.cornerRadius = TGPhotoPickerConfig.shared.doneButtonH * 0.15 187 | indicatorLbl.clipsToBounds = true 188 | indicatorLbl.textColor = .white 189 | indicatorLbl.textAlignment = .center 190 | indicatorLbl.backgroundColor = TGPhotoPickerConfig.shared.indicatorColor 191 | //if TGPhotoPickerConfig.shared.isShowBorder { 192 | indicatorLbl.layer.borderWidth = TGPhotoPickerConfig.shared.checkboxLineW 193 | indicatorLbl.layer.borderColor = UIColor.clear.cgColor 194 | //} 195 | indicatorLbl.sizeToFit() 196 | indicatorLbl.h = TGPhotoPickerConfig.shared.toolBarH * 0.8 197 | indicatorLbl.w = indicatorLbl.w < TGPhotoPickerConfig.shared.doneButtonW * 0.8 ? TGPhotoPickerConfig.shared.doneButtonW * 0.8 : indicatorLbl.w 198 | return indicatorLbl 199 | }() 200 | 201 | } 202 | 203 | extension TGPhotoPreviewVC : UICollectionViewDataSource{ 204 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 205 | return self.selectImages.count 206 | } 207 | 208 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 209 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as! TGPhotoPreviewCell 210 | cell.delegate = self 211 | if let asset = self.selectImages[indexPath.row].asset { 212 | cell.asset = asset 213 | }else{ 214 | cell.image = self.selectImages[indexPath.row].bigImage 215 | } 216 | return cell 217 | } 218 | } 219 | 220 | extension TGPhotoPreviewVC : UICollectionViewDelegate{ 221 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 222 | self.currentPage = Int(scrollView.contentOffset.x / self.view.bounds.width) 223 | updatePageTitle() 224 | } 225 | } 226 | 227 | extension TGPhotoPreviewVC : TGPhotoPreviewCellDelegate{ 228 | override var prefersStatusBarHidden: Bool{ 229 | return self.isStatusBarHidden 230 | } 231 | 232 | override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { 233 | return .slide 234 | } 235 | 236 | override var preferredStatusBarStyle: UIStatusBarStyle { 237 | return .lightContent 238 | } 239 | 240 | func onImageSingleTap() { 241 | if #available(iOS 9.0, *) { 242 | self.isStatusBarHidden = !self.isStatusBarHidden 243 | let isVCBased = Bundle.main.infoDictionary?["UIViewControllerBasedStatusBarAppearance"] as? Bool ?? false 244 | if !isVCBased{ 245 | UIApplication.shared.setStatusBarHidden(self.isStatusBarHidden, with: .slide) 246 | } 247 | self.navigationController?.setNavigationBarHidden(self.isStatusBarHidden, animated: true) 248 | }else { 249 | let status = !UIApplication.shared.isStatusBarHidden 250 | UIApplication.shared.setStatusBarHidden(status, with: .slide) 251 | self.navigationController?.setNavigationBarHidden(status, animated: true) 252 | } 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /TGPhotoPicker/TGPhotoPicker/TGPhotoPicker/TGTopBar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TGTopBar.swift 3 | // TGPhotoPicker 4 | // 5 | // Created by targetcloud on 2017/7/22. 6 | // Copyright © 2017年 targetcloud. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol TGTopBarDelegate: class { 12 | func onBackClicked() 13 | func onSelectedClicked(select:Bool) 14 | } 15 | 16 | class TGTopBar: UIView { 17 | 18 | weak var delegate: TGTopBarDelegate? 19 | weak var source: TGAlbumPhotoPreviewVC? 20 | weak var nav: TGPhotoPickerVC? 21 | var selectNum = 0 22 | 23 | private var checkboxSelect: UIImageView? 24 | private var checkbox: UIButton? 25 | 26 | override init(frame: CGRect) { 27 | super.init(frame: frame) 28 | setupUI() 29 | } 30 | 31 | required init?(coder aDecoder: NSCoder) { 32 | super.init(coder: aDecoder) 33 | setupUI() 34 | } 35 | 36 | func setSelect(_ select:Bool,_ showOrder: Int = -1){ 37 | if showOrder >= 0{ 38 | checkboxSelect?.image = TGPhotoPickerConfig.shared.cacheNumerImageForBarArr[showOrder] 39 | } 40 | self.checkboxSelect!.isHidden = !select 41 | self.checkbox!.isSelected = select 42 | } 43 | 44 | private func setupUI(){ 45 | self.backgroundColor = TGPhotoPickerConfig.shared.barBGColor 46 | 47 | let WH = TGPhotoPickerConfig.shared.checkboxBarWH * 0.8 48 | 49 | let backBtn = UIButton(frame: CGRect(x: TGPhotoPickerConfig.shared.padding + 3, y: (self.bounds.height - WH) / 2, width: WH, height: WH)) 50 | 51 | backBtn.setImage(UIImage.size(width: WH, height: WH) 52 | .corner(radius: WH * 0.5) 53 | .color(.clear) 54 | .border(color: UIColor.white.withAlphaComponent(0.7)) 55 | .border(width: TGPhotoPickerConfig.shared.isShowBorder ? TGPhotoPickerConfig.shared.checkboxLineW : 0) 56 | .image 57 | .with({ context in 58 | context.setLineCap(.round) 59 | UIColor.white.setStroke() 60 | context.setLineWidth(TGPhotoPickerConfig.shared.checkboxLineW) 61 | context.move(to: CGPoint(x: WH * (TGPhotoPickerConfig.shared.isShowBorder ? 0.55 : 0.6), y: WH * (TGPhotoPickerConfig.shared.isShowBorder ? 0.25: 0.2))) 62 | context.addLine(to: CGPoint(x: WH * (TGPhotoPickerConfig.shared.isShowBorder ? 0.4 : 0.35), y: WH * 0.5)) 63 | context.move(to: CGPoint(x: WH * (TGPhotoPickerConfig.shared.isShowBorder ? 0.4 : 0.35), y: WH * 0.5)) 64 | context.addLine(to: CGPoint(x: WH * (TGPhotoPickerConfig.shared.isShowBorder ? 0.55 : 0.6), y: WH * (TGPhotoPickerConfig.shared.isShowBorder ? 0.75 : 0.8))) 65 | 66 | // context.move(to: CGPoint(x: 17, y: 7)) 67 | // context.addLine(to: CGPoint(x: 10, y: 15)) 68 | // context.move(to: CGPoint(x: 10, y: 15)) 69 | // context.addLine(to: CGPoint(x: 17, y: 23)) 70 | context.strokePath() 71 | }), for: UIControlState.normal)//config 72 | backBtn.addTarget(self, action: #selector(back), for: .touchUpInside) 73 | self.addSubview(backBtn) 74 | 75 | let checkboxX = self.bounds.width - TGPhotoPickerConfig.shared.checkboxBarWH - TGPhotoPickerConfig.shared.padding * 2 76 | let checkboxY = (self.bounds.height - TGPhotoPickerConfig.shared.checkboxBarWH) / 2 77 | self.checkbox = UIButton(type: .custom) 78 | checkbox!.frame = CGRect(x:checkboxX,y: checkboxY,width: TGPhotoPickerConfig.shared.checkboxBarWH,height: TGPhotoPickerConfig.shared.checkboxBarWH) 79 | checkbox!.addTarget(self, action: #selector(check(sender:)), for: .touchUpInside) 80 | 81 | let imageTuples = TGPhotoPickerConfig.shared.getCheckboxImage(false,false,.circle) 82 | let checkboxUnselect = UIImageView(image: imageTuples.unselect) 83 | checkboxUnselect.contentMode = .scaleAspectFit 84 | checkboxUnselect.frame = checkbox!.bounds 85 | checkbox!.addSubview(checkboxUnselect) 86 | 87 | self.checkboxSelect = UIImageView(image: imageTuples.select) 88 | checkboxSelect!.contentMode = .scaleAspectFit 89 | checkboxSelect!.frame = checkbox!.bounds 90 | checkboxSelect!.isHidden = true 91 | self.checkbox!.addSubview(checkboxSelect!) 92 | 93 | self.addSubview(checkbox!) 94 | } 95 | 96 | @objc private func back(){ 97 | delegate?.onBackClicked() 98 | } 99 | 100 | @objc private func check(sender: UIButton){ 101 | if sender.isSelected { 102 | sender.isSelected = false 103 | self.checkboxSelect!.isHidden = true 104 | self.delegate?.onSelectedClicked(select: false) 105 | } else { 106 | if let _ = self.source {//预览模式下 107 | if (nav?.assetArr.count)! >= TGPhotoPickerConfig.shared.maxImageCount - (nav?.alreadySelectedImageNum)! { 108 | return self.showSelectErrorDialog() 109 | } 110 | } 111 | sender.isSelected = true 112 | self.checkboxSelect!.isHidden = false 113 | if TGPhotoPickerConfig.shared.checkboxAnimate { 114 | self.checkboxSelect!.transform = CGAffineTransform(scaleX: 0.9, y: 0.9) 115 | UIView.animate(withDuration: 0.25, delay: 0, usingSpringWithDamping: 0.4, initialSpringVelocity: 8, options: [UIViewAnimationOptions.curveEaseIn], animations: { 116 | self.checkboxSelect!.transform = CGAffineTransform(scaleX: 1.1, y: 1.1) 117 | }, completion: { _ in 118 | UIView.animate(withDuration: 0.25, animations: { 119 | self.checkboxSelect!.transform = CGAffineTransform.identity 120 | }) 121 | }) 122 | } 123 | self.delegate?.onSelectedClicked(select: true) 124 | if TGPhotoPickerConfig.shared.isShowNumber{ 125 | checkboxSelect?.image = TGPhotoPickerConfig.shared.cacheNumerImageForBarArr[(nav?.assetArr.count)! - 1] 126 | } 127 | } 128 | } 129 | 130 | private func showSelectErrorDialog() { 131 | if self.source != nil { 132 | let less = TGPhotoPickerConfig.shared.maxImageCount - selectNum 133 | let range = TGPhotoPickerConfig.shared.errorImageMaxSelect.range(of:"#") 134 | var error = TGPhotoPickerConfig.shared.errorImageMaxSelect 135 | error.replaceSubrange(range!, with: String(less)) 136 | let alert = UIAlertController(title: nil, message: (selectNum > 0 ? TGPhotoPickerConfig.shared.leftTitle : "") + error, preferredStyle: UIAlertControllerStyle.alert) 137 | let confirmAction = UIAlertAction(title: TGPhotoPickerConfig.shared.confirmTitle, style: .default, handler: nil) 138 | alert.addAction(confirmAction) 139 | self.source?.present(alert, animated: true, completion: nil) 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /gif/b.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/gif/b.gif -------------------------------------------------------------------------------- /gif/circle.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/gif/circle.gif -------------------------------------------------------------------------------- /gif/diagonalBelt.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/gif/diagonalBelt.gif -------------------------------------------------------------------------------- /gif/h.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/gif/h.gif -------------------------------------------------------------------------------- /gif/o.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/gif/o.gif -------------------------------------------------------------------------------- /gif/s.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/gif/s.gif -------------------------------------------------------------------------------- /gif/star.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/gif/star.gif -------------------------------------------------------------------------------- /gif/t.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/gif/t.gif -------------------------------------------------------------------------------- /img/IMG_2480.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/img/IMG_2480.PNG -------------------------------------------------------------------------------- /img/IMG_2481.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/img/IMG_2481.PNG -------------------------------------------------------------------------------- /img/IMG_2482.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/img/IMG_2482.PNG -------------------------------------------------------------------------------- /img/IMG_2483.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/img/IMG_2483.PNG -------------------------------------------------------------------------------- /img/IMG_2484.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/img/IMG_2484.PNG -------------------------------------------------------------------------------- /img/IMG_2485.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/img/IMG_2485.PNG -------------------------------------------------------------------------------- /img/IMG_2486.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/img/IMG_2486.PNG -------------------------------------------------------------------------------- /img/IMG_2487.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/img/IMG_2487.PNG -------------------------------------------------------------------------------- /img/IMG_2488.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/img/IMG_2488.PNG -------------------------------------------------------------------------------- /img/IMG_2489.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/img/IMG_2489.PNG -------------------------------------------------------------------------------- /img/IMG_2490.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/img/IMG_2490.PNG -------------------------------------------------------------------------------- /img/IMG_2491.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/img/IMG_2491.PNG -------------------------------------------------------------------------------- /img/IMG_2492.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/img/IMG_2492.PNG -------------------------------------------------------------------------------- /img/IMG_2493.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/img/IMG_2493.PNG -------------------------------------------------------------------------------- /img/IMG_2494.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/img/IMG_2494.PNG -------------------------------------------------------------------------------- /img/IMG_2495.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/img/IMG_2495.PNG -------------------------------------------------------------------------------- /img/IMG_2496.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/img/IMG_2496.PNG -------------------------------------------------------------------------------- /img/IMG_2497.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/img/IMG_2497.PNG -------------------------------------------------------------------------------- /img/IMG_2498.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/img/IMG_2498.PNG -------------------------------------------------------------------------------- /img/IMG_2584.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/img/IMG_2584.PNG -------------------------------------------------------------------------------- /img/IMG_2640.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/img/IMG_2640.PNG -------------------------------------------------------------------------------- /img/IMG_2641.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/img/IMG_2641.PNG -------------------------------------------------------------------------------- /img/IMG_2642.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/img/IMG_2642.PNG -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/targetcloud/TGPhotoPicker/0aaf158b7153738578f3846b8c824dc5c5c4df95/logo.png --------------------------------------------------------------------------------