├── .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 | 
9 | 
10 | 
11 | 
12 | 
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 | 
399 |
400 | #### circle
401 | 
402 |
403 | #### belt
404 | 
405 |
406 | #### square
407 | 
408 |
409 | #### onlyCheckbox
410 | 
411 |
412 | #### triangle
413 | 
414 |
415 | #### heart
416 | 
417 |
418 | #### star
419 | 
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
--------------------------------------------------------------------------------