├── .github └── ISSUE_TEMPLATE │ ├── -------.md │ ├── ----.md │ └── --bug.md ├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── LICENSE ├── Package.swift ├── README.md ├── TZImagePickerController.podspec ├── TZImagePickerController.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── TZImagePickerControllerFramework.xcscheme ├── TZImagePickerController ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── .DS_Store │ ├── AlbumAddBtn.imageset │ │ ├── AlbumAddBtn@2x.png │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── back.imageset │ │ ├── Contents.json │ │ └── back@2x.png │ ├── leftVideoEdit.imageset │ │ ├── Contents.json │ │ └── hx_videoedit_left@2x.png │ ├── photo_delete.imageset │ │ ├── Contents.json │ │ └── photo_delete@2x.png │ └── rightVideoEdit.imageset │ │ ├── Contents.json │ │ └── hx_videoedit_right@2x.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── FLAnimatedImage │ ├── FLAnimatedImage.h │ ├── FLAnimatedImage.m │ ├── FLAnimatedImageView.h │ └── FLAnimatedImageView.m ├── Info.plist ├── Location │ ├── TZLocationManager.h │ └── TZLocationManager.m ├── LxGridViewFlowLayout.h ├── LxGridViewFlowLayout.m ├── ScreenShots │ ├── DemoPage.png │ ├── photoPickerVc.PNG │ ├── photoPreviewVc.PNG │ └── videoPlayerVc.PNG ├── TZImagePickerController │ ├── NSBundle+TZImagePicker.h │ ├── NSBundle+TZImagePicker.m │ ├── TZAssetCell.h │ ├── TZAssetCell.m │ ├── TZAssetModel.h │ ├── TZAssetModel.m │ ├── TZAuthLimitedFooterTipView.h │ ├── TZAuthLimitedFooterTipView.m │ ├── TZGifPhotoPreviewController.h │ ├── TZGifPhotoPreviewController.m │ ├── TZImageCropManager.h │ ├── TZImageCropManager.m │ ├── TZImageManager.h │ ├── TZImageManager.m │ ├── TZImagePickerController.bundle │ │ ├── MMVideoPreviewPlay@2x.png │ │ ├── MMVideoPreviewPlayHL@2x.png │ │ ├── VideoSendIcon@2x.png │ │ ├── addMore@2x.png │ │ ├── ar.lproj │ │ │ └── Localizable.strings │ │ ├── de.lproj │ │ │ └── Localizable.strings │ │ ├── en.lproj │ │ │ └── Localizable.strings │ │ ├── es.lproj │ │ │ └── Localizable.strings │ │ ├── fr.lproj │ │ │ └── Localizable.strings │ │ ├── iCloudError@2x.png │ │ ├── ja.lproj │ │ │ └── Localizable.strings │ │ ├── ko-KP.lproj │ │ │ └── Localizable.strings │ │ ├── navi_back@2x.png │ │ ├── photo_def_photoPickerVc@2x.png │ │ ├── photo_def_previewVc@2x.png │ │ ├── photo_number_icon@2x.png │ │ ├── photo_original_def@2x.png │ │ ├── photo_original_sel@2x.png │ │ ├── photo_sel_photoPickerVc@2x.png │ │ ├── photo_sel_previewVc@2x.png │ │ ├── preview_number_icon@2x.png │ │ ├── preview_original_def@2x.png │ │ ├── pt.lproj │ │ │ └── Localizable.strings │ │ ├── right_arrow@2x.png │ │ ├── ru.lproj │ │ │ └── Localizable.strings │ │ ├── takePicture80@2x.png │ │ ├── takePicture@2x.png │ │ ├── tip@2x.png │ │ ├── vi.lproj │ │ │ └── Localizable.strings │ │ ├── zh-Hans.lproj │ │ │ └── Localizable.strings │ │ └── zh-Hant.lproj │ │ │ └── Localizable.strings │ ├── TZImagePickerController.h │ ├── TZImagePickerController.m │ ├── TZImageRequestOperation.h │ ├── TZImageRequestOperation.m │ ├── TZPhotoPickerController.h │ ├── TZPhotoPickerController.m │ ├── TZPhotoPreviewCell.h │ ├── TZPhotoPreviewCell.m │ ├── TZPhotoPreviewController.h │ ├── TZPhotoPreviewController.m │ ├── TZProgressView.h │ ├── TZProgressView.m │ ├── TZVideoCropController.h │ ├── TZVideoCropController.m │ ├── TZVideoEditedPreviewController.h │ ├── TZVideoEditedPreviewController.m │ ├── TZVideoPlayerController.h │ ├── TZVideoPlayerController.m │ ├── UIView+TZLayout.h │ └── UIView+TZLayout.m ├── TZImageUploadOperation.h ├── TZImageUploadOperation.m ├── TZTestCell.h ├── TZTestCell.m ├── ViewController.h ├── ViewController.m ├── ar-001.lproj │ ├── LaunchScreen.strings │ ├── Localizable.strings │ └── Main.strings ├── main.m ├── tz-ru.lproj │ └── Localizable.strings └── zh-Hans.lproj │ ├── LaunchScreen.strings │ └── Main.strings ├── TZImagePickerControllerFramework ├── Info.plist └── PrivacyInfo.xcprivacy ├── TZImagePickerControllerTests ├── Info.plist └── TZImagePickerControllerTests.m └── TZImagePickerControllerUITests ├── Info.plist └── TZImagePickerControllerUITests.m /.github/ISSUE_TEMPLATE/-------.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 希望添加新功能 3 | about: 建议添加XX功能 4 | 5 | --- 6 | 7 | **问:你希望加什么新功能?** 8 | 答: 9 | 10 | **问:微信、QQ等大APP的选择器,是否有这个功能?** 11 | 答: 12 | 13 | **问:这个功能是否适合所有人?如不是,是否要加个属性开关?** 14 | 答: 15 | 16 | **其它你希望补充的内容** 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/----.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 其它问题 3 | about: 使用问题咨询等等 4 | 5 | --- 6 | 7 | **咨询前必看** 8 | 请先回答下列三个问题,否则不允处理,谢谢配合。 9 | 1、我最新的Demo是否有这个bug?【**如果Demo没问题,请升级新版**】 10 | 答: 11 | 12 | 2、你用的是什么版本?升级到最新版后是否正常? 13 | 答: 14 | 15 | 3、是否有改动过我库内部的代码?【**如有,请说明改动点**】 16 | 答: 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/--bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 提交Bug 3 | about: 发现一个bug或疑似bug 4 | 5 | --- 6 | 7 | **提bug前必看** 8 | 请先回答下列三个问题,否则不允处理,谢谢配合。 9 | 1、我最新的Demo是否有这个bug?【**如果Demo没问题,请升级新版**】 10 | 答: 11 | 12 | 2、你用的是什么版本?升级到最新版后是否正常? 13 | 答: 14 | 15 | 3、是否有改动过我库内部的代码?【**如有,请说明改动点**】 16 | 答: 17 | 18 | **bug内容描述** 19 | 20 | 21 | **我如何复现这个bug?** 22 | 23 | 24 | **截图** 25 | 26 | 27 | **其它说明** 28 | 有没有其它要补充的?比如你的初始化TZImagePickerController的代码 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | #Pods/ 27 | 28 | .DS_Store 29 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Zhen Tan 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 | 23 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "TZImagePickerController", 8 | platforms: [.iOS(.v8)], 9 | products: [ 10 | // Products define the executables and libraries a package produces, and make them visible to other packages. 11 | .library( 12 | name: "TZImagePickerController", 13 | targets: ["TZImagePickerController"]), 14 | ], 15 | dependencies: [ 16 | // Dependencies declare other packages that this package depends on. 17 | // .package(url: /* package url */, from: "1.0.0"), 18 | ], 19 | targets: [ 20 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 21 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 22 | .target( 23 | name: "TZImagePickerController", 24 | path: "TZImagePickerController/TZImagePickerController", 25 | resources: [.process("TZImagePickerController.bundle")], 26 | publicHeadersPath: "." 27 | ) 28 | ] 29 | ) 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TZImagePickerController 2 | [![CocoaPods](https://img.shields.io/cocoapods/v/TZImagePickerController.svg?style=flat)](https://github.com/banchichen/TZImagePickerController) 3 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 4 | 5 | 6 | A clone of UIImagePickerController, support picking multiple photos、original photo、video, also allow preview photo and video, support iOS10+. 7 | 一个支持多选、选原图和视频的图片选择器,同时有预览功能,支持iOS10+。 8 | 9 | ## 重要提示1:提issue前,请先对照Demo、常见问题自查!Demo正常说明你可以升级下新版试试。 10 | 11 | ## 重要提示2:3.8.8版本修复了iOS18下无照片和openURL失效的问题 12 | 关于iOS14模拟器的问题 13 | PHAuthorizationStatusLimited授权模式下,iOS14模拟器有bug,未授权照片无法显示,真机正常,暂可忽略:https://github.com/banchichen/TZImagePickerController/issues/1347 14 | 15 | 关于升级iOS10和Xcdoe8的提示: 16 | 在Xcode8环境下将项目运行在iOS10的设备/模拟器中,访问相册和相机需要额外配置info.plist文件。分别是Privacy - Photo Library Usage Description和Privacy - Camera Usage Description字段,详见Demo中info.plist中的设置。 17 | 18 | 项目截图 1.Demo首页 2.照片列表页 3.照片预览页 4.视频预览页 19 | 20 | 21 | 22 | ## 一. Installation 安装 23 | 24 | #### CocoaPods 25 | > pod 'TZImagePickerController' # Full version with all features 26 | > pod 'TZImagePickerController/Basic' # No location code 27 | 28 | #### Carthage 29 | > github "banchichen/TZImagePickerController" 30 | 31 | #### 手动安装 32 | > 将TZImagePickerController文件夹拽入项目中,导入头文件:#import "TZImagePickerController.h" 33 | 34 | ## 二. Example 例子 35 | 36 | TZImagePickerController *imagePickerVc = [[TZImagePickerController alloc] initWithMaxImagesCount:9 delegate:self]; 37 | 38 | // You can get the photos by block, the same as by delegate. 39 | // 你可以通过block或者代理,来得到用户选择的照片. 40 | [imagePickerVc setDidFinishPickingPhotosHandle:^(NSArray *photos, NSArray *assets, BOOL isSelectOriginalPhoto) { 41 | 42 | }]; 43 | [self presentViewController:imagePickerVc animated:YES completion:nil]; 44 | 45 | ## 三. Requirements 要求 46 | iOS 10 or later. 47 | 支持iOS10及以上系统。 48 | 49 | TZImagePickerController uses Camera、Location、Microphone、Photo Library,you need add these properties to info.plist like Demo: 50 | TZImagePickerController使用了相机、定位、麦克风、相册,请参考Demo添加下列属性到info.plist文件: 51 | `Privacy - Camera Usage Description` 52 | `Privacy - Location Usage Description` 53 | `Privacy - Location When In Use Usage Description` 54 | `Privacy - Microphone Usage Description` 55 | `Privacy - Photo Library Usage Description` 56 | `Prevent limited photos access alert` 57 | 58 | ## 四. More 更多 59 | 60 | If you find a bug, please create a issue. 61 | More information please view code. 62 | 如果你发现了bug,请提一个issue。 63 | 更多信息详见代码,也可查看我的博客: [我的博客](http://www.jianshu.com/p/1975411a31bb "半尺尘 - 简书") 64 | 65 | 关于issue: 66 | 请尽可能详细地描述**系统版本**、**手机型号**、**库的版本**、**崩溃日志**和**复现步骤**,**请先更新到最新版再测试一下**,如果新版还存在再提~如果已有开启的类似issue,请直接在该issue下评论说出你的问题 67 | 68 | ## 五. FAQ 常见问题 69 | 70 | **Q:pod search TZImagePickerController 搜索出来的不是最新版本** 71 | A:需要在终端执行cd转换文件路径命令退回到Desktop,然后执行pod setup命令更新本地spec缓存(可能需要几分钟),然后再搜索就可以了 72 | 73 | **Q:拍照后照片保存失败** 74 | A:请参考issue481:https://github.com/banchichen/TZImagePickerController/issues/481 的信息排查,若还有问题请直接在issue内评论 75 | 76 | **Q:photos数组图片不是原图,如何获取原图?** 77 | A:请参考issue457的解释:https://github.com/banchichen/TZImagePickerController/issues/457 78 | 79 | **Q:系统语言是中文/英文,界面上却有部分相册名字、返回按钮显示成了英文/中文?** 80 | A:请参考 https://github.com/banchichen/TZImagePickerController/issues/443 和 https://github.com/banchichen/TZImagePickerController/issues/929 81 | 82 | **Q:预览界面能否支持传入NSURL、UIImage对象?** 83 | A:3.0.1版本已支持,需新接一个库:[TZImagePreviewController](https://github.com/banchichen/TZImagePreviewController),请参考里面的Demo使用。 84 | 85 | **Q:设置可选视频的最大/最小时长?照片的最小/最大尺寸?不符合要求的不显示** 86 | A:可以的,参照Demo的isAssetCanBeDisplayed方法实现。我会返回asset出来,显示与否你来决定,注意这个是一个同步方法,对于需要根据asset去异步获取的信息如视频的大小、视频是否存在iCloud里来过滤的,无法做到。如果真要这样做,相册打开速度会变慢,你需要改我源码。 87 | 如果需要显示,选择时才提醒用户不可选,则实现isAssetCanBeSelected,用户选择时会调用它 88 | 89 | **Q:预览页面出现了导航栏?** 90 | A:https://github.com/banchichen/TZImagePickerController/issues/652 91 | 92 | **Q:可否增加微信编辑图片的功能?** 93 | A:考虑下,优先级低 94 | 95 | **Q:是否有QQ/微信群/钉钉群?** 96 | A:有「钉钉群:33192786」和「QQ群:859033147」,推荐加钉钉群,答疑响应更快 97 | 98 | **Q:想提交一个Pull Request?** 99 | A:请先加钉钉群(33192786)说下方案,和我确认下,避免同时改动同一处内容。**一个PR请只修复1个问题,变动内容越少越好**。 100 | 101 | **Q:demo在真机上跑不起来?** 102 | A:1、team选你自己的;2、bundleId也改成你自己的或改成一个不会和别人重复的。可参考[简书的这篇博客](https://www.jianshu.com/p/cbe59138fca6) 103 | 104 | **Q:3.6.4以上版本设置导航栏颜色无效?** 105 | A:参考Demo里的代码,加上imagePickerVc.navigationBar.standardAppearance的相关设置 106 | 107 | **Q:设置导航栏颜色无效?导航栏颜色总是白色?** 108 | A:是否有集成WRNavigationBar?如有,参考其readme调一下它的wr_setBlackList,把TZImagePickerController相关的控制器放到黑名单里,使得不受WRNavigationBar的影响。如果没有集成,可在issues列表里搜一下看看类似的issue参考下,如实在没头绪,可加群提供个能复现该问题的demo,0~2天给你解决。最近发现WRNavigationBar的黑名单会有不生效的情况,临时解决方案大家可参考:[https://github.com/wangrui460/WRNavigationBar/issues/145](https://github.com/wangrui460/WRNavigationBar/issues/145) 109 | 110 | **Q:导航栏没了?** 111 | A:是否有集成GKNavigationBarViewController?需要升级到2.0.4及以上版本,详见issue:[https://github.com/QuintGao/GKNavigationBarViewController/issues/7](https://github.com/QuintGao/GKNavigationBarViewController/issues/7)。 112 | 113 | **Q:有的视频导出失败?** 114 | A:升级到2.2.6及以上版本试试,发现是修正视频转向导致的,2.2.6开始默认不再主动修正。如需打开,可设置needFixComposition为YES,但有几率导致安卓拍的视频导出失败。此外也可参考这个issue:https://github.com/banchichen/TZImagePickerController/issues/1073 115 | 116 | **Q:视频导出慢?** 117 | A:视频导出分两步,第一步是通过PHAsset获取AVURLAsset,如是iCloud视频则涉及到网络请求,耗时容易不可控,第二步是通过AVURLAsset把视频保存到沙盒,耗时不算多。但第一步耗时不可控,你可以拷贝我源码出来拿到第一步的进度给用户一个进度提示... 118 | 119 | **Q:有的图片info里没有PHImageFileURLKey?** 120 | A:不要去拿PHImageFileURLKey,没用的,只有通过Photos框架才能访问相册照片,光拿一个路径没用。 121 | 如果需要通过路径上传照片,请先把UIImage保存到沙盒,**用沙盒路径**。 122 | 如果你上传照片需要一个名字参数,请参考Demo**直接用照片名字**。 123 | 124 | ## 六. Release Notes 最近更新 125 | 126 | **3.8.8 支持iOS18,修复openURL的失效问题** [#1686](https://github.com/banchichen/TZImagePickerController/issues/1686) 127 | **3.8.5 新增隐私清单文件** [#1675](https://github.com/banchichen/TZImagePickerController/pull/1675) 128 | **3.8.4 支持使用不带定位代码的版本** [#1606](https://github.com/banchichen/TZImagePickerController/pull/1606) 129 | 3.8.1 iOS14下可添加访问更多照片,详见PR内的评论 [#1526](https://github.com/banchichen/TZImagePickerController/pull/1526) 130 | 3.7.6 修复iOS15.2下初次授权相册权限时的长时间卡顿&白屏问题 [#1547](https://github.com/banchichen/TZImagePickerController/issues/1547) 131 | **3.6.7 修复Xcode13&iOS15下导航栏颜色异常问题** 132 | 3.6.2 新增allowEditVideo,单选视频时支持裁剪 133 | 3.6.0 修复iOS14下iCloud视频导出失败问题 134 | **3.5.2 适配iPhone12系列设备** 135 | 3.4.4 支持Dark Mode 136 | 3.4.2 适配iOS14,若干问题修复 137 | 3.3.2 适配iOS13,若干问题修复 138 | 3.2.1 新增裁剪用scaleAspectFillCrop属性,设置为YES后,照片尺寸小于裁剪框时会自动放大撑满 139 | 3.2.0 加入用NSOperationQueue控制获取原图并发数降低内存的示例 140 | 3.1.8 批量获取图片时加入队列控制,尝试优化大批量选择图片时CPU和内存占用过高的问题(仍然危险,maxImagesCount谨慎设置过大...) 141 | 3.1.5 相册内无照片时给出提示,修复快速滑动时内存一直增加的问题 142 | 3.1.3 适配阿拉伯等语言下从右往左布局的特性 143 | 3.0.8 新增gifImagePlayBlock允许使用FLAnimatedImage等替换内部的GIF播放方案 144 | 3.0.7 适配iPhoneXR、XS、XS Max 145 | 3.0.6 优化保存照片、视频的方法 146 | 3.0.1 新增对[TZImagePreviewController](https://github.com/banchichen/TZImagePreviewController)库的支持,允许预览UIImage、NSURL、PHAsset对象 147 | **3.0.0 去除iOS6和7的适配代码,更轻量,最低支持iOS8** 148 | 2.2.6 新增needFixComposition属性,默认为NO,不再主动修正视频转向,防止部分安卓拍的视频导出失败(**最后一个支持iOS6和7的版本**) 149 | 2.1.5 修复开启showSelectedIndex后照片列表页iCloud图片进度条紊乱的bug 150 | 2.1.4 新增多个页面和组件的样式自定义block,允许自定义绝大多数UI样式 151 | 2.1.2 新增showPhotoCannotSelectLayer属性,当已选照片张数达到最大可选张数时,可像微信一样让其它照片显示一个提示不可选的浮层 152 | 2.1.1 新增是否显示图片选中序号的属性,优化一些细节 153 | 2.1.0.3 新增拍摄视频功能,优化一些细节 154 | 2.0.0.6 优化自定义languageBundle的支持,加入使用示例 155 | 2.0.0.5 优化性能,提高选择器打开速度,新增越南语支持 156 | 2.0.0.2 新增繁体语言,可设置首选语言,国际化支持更强大;优化一些细节 157 | 1.9.8 支持Carthage,优化一些细节 158 | 1.9.6 优化视频预览和gif预览页toolbar在iPhoneX上的样式 159 | ... 160 | 1.8.4 加入横竖屏适配;支持视频/gif多选;支持视频和照片一起选 161 | 1.8.1 新增2个代理方法,支持由上层来决定相册/照片的显示与否 162 | ... 163 | 1.7.7 支持GIF图片的播放和选择 164 | 1.7.6 支持对共享相册和同步相册的显示 165 | 1.7.5 允许不进入预览页面直接选择照片 166 | 1.7.4 支持单选模式下裁剪照片,支持任意矩形和圆形裁剪框 167 | 1.7.3 优化iCloud照片的显示与选择 168 | ... 169 | 1.5.0 可把拍照按钮放在外面;可自定义照片排序方式;Demo页的UI大改版,新增若干开关; 170 | ... 171 | 1.4.5 性能大幅提升(性能测试截图请去博客查看);可在照片列表页拍照;Demo大幅优化; 172 | ... 173 | 174 | ## 七. Common links 常用链接 175 | 1. Json diff online: https://www.jsondiffonline.com/ 176 | 177 | -------------------------------------------------------------------------------- /TZImagePickerController.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "TZImagePickerController" 3 | s.version = "3.8.9" 4 | s.summary = "A clone of UIImagePickerController, support picking multiple photos、original photo and video" 5 | s.homepage = "https://github.com/banchichen/TZImagePickerController" 6 | s.license = "MIT" 7 | s.author = { "banchichen" => "tanzhenios@foxmail.com" } 8 | s.platform = :ios 9 | s.ios.deployment_target = "10.0" 10 | s.source = { :git => "https://github.com/banchichen/TZImagePickerController.git", :tag => "3.8.9" } 11 | s.requires_arc = true 12 | 13 | s.subspec 'Basic' do |b| 14 | b.resources = "TZImagePickerController/TZImagePickerController/*.{png,bundle}" 15 | b.source_files = "TZImagePickerController/TZImagePickerController/*.{h,m}" 16 | end 17 | 18 | s.subspec 'Location' do |l| 19 | l.source_files = 'TZImagePickerController/Location/*.{h,m}' 20 | end 21 | 22 | s.frameworks = "Photos", "PhotosUI" 23 | end 24 | -------------------------------------------------------------------------------- /TZImagePickerController.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TZImagePickerController.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TZImagePickerController.xcodeproj/xcshareddata/xcschemes/TZImagePickerControllerFramework.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /TZImagePickerController/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // TZImagePickerController 4 | // 5 | // Created by 谭真 on 15/12/24. 6 | // Copyright © 2015年 谭真. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /TZImagePickerController/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // TZImagePickerController 4 | // 5 | // Created by 谭真 on 15/12/24. 6 | // Copyright © 2015年 谭真. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "TZImagePickerController.h" 11 | 12 | @interface AppDelegate () 13 | 14 | @end 15 | 16 | @implementation AppDelegate 17 | 18 | 19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 20 | // 打开下面这句代码,使用导航栏控制器作为rootViewController 21 | // [self useNavControllerAsRoot]; 22 | return YES; 23 | } 24 | 25 | - (void)useNavControllerAsRoot { 26 | UIViewController *vc = [[UIViewController alloc] init]; 27 | vc.view.backgroundColor = [UIColor whiteColor]; 28 | 29 | UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(20, 200, 300, 44)]; 30 | [btn setTitle:@"pushTZImagePickerController" forState:UIControlStateNormal]; 31 | [btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; 32 | [btn addTarget:self action:@selector(pushTZImagePickerController) forControlEvents:UIControlEventTouchUpInside]; 33 | [vc.view addSubview:btn]; 34 | 35 | UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc]; 36 | nav.navigationBar.barStyle = UIBarStyleBlack; 37 | nav.navigationBar.translucent = YES; 38 | nav.navigationBar.barTintColor = [UIColor colorWithRed:(34/255.0) green:(34/255.0) blue:(34/255.0) alpha:1.0]; 39 | nav.navigationBar.tintColor = [UIColor blackColor]; 40 | if (@available(iOS 13.0, *)) { 41 | UINavigationBarAppearance *barAppearance = [[UINavigationBarAppearance alloc] init]; 42 | if (nav.navigationBar.isTranslucent) { 43 | UIColor *barTintColor = nav.navigationBar.barTintColor; 44 | barAppearance.backgroundColor = [barTintColor colorWithAlphaComponent:0.85]; 45 | } else { 46 | barAppearance.backgroundColor = nav.navigationBar.barTintColor; 47 | } 48 | barAppearance.titleTextAttributes = nav.navigationBar.titleTextAttributes; 49 | nav.navigationBar.standardAppearance = barAppearance; 50 | nav.navigationBar.scrollEdgeAppearance = barAppearance; 51 | } 52 | [nav setNavigationBarHidden:YES]; 53 | 54 | self.window.rootViewController = nav; 55 | [self.window makeKeyAndVisible]; 56 | } 57 | 58 | - (void)pushTZImagePickerController { 59 | TZImagePickerController *imagePickerVc = [[TZImagePickerController alloc] initWithMaxImagesCount:9 columnNumber:4 delegate:nil pushPhotoPickerVc:YES]; 60 | imagePickerVc.modalPresentationStyle = UIModalPresentationFullScreen; 61 | UINavigationController *nav = (UINavigationController *)[UIApplication sharedApplication].keyWindow.rootViewController; 62 | [nav.topViewController presentViewController:imagePickerVc animated:YES completion:nil]; 63 | } 64 | 65 | - (void)applicationWillResignActive:(UIApplication *)application { 66 | 67 | } 68 | 69 | - (void)applicationDidEnterBackground:(UIApplication *)application { 70 | 71 | } 72 | 73 | - (void)applicationWillEnterForeground:(UIApplication *)application { 74 | 75 | } 76 | 77 | - (void)applicationDidBecomeActive:(UIApplication *)application { 78 | 79 | } 80 | 81 | - (void)applicationWillTerminate:(UIApplication *)application { 82 | 83 | } 84 | 85 | @end 86 | -------------------------------------------------------------------------------- /TZImagePickerController/Assets.xcassets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/Assets.xcassets/.DS_Store -------------------------------------------------------------------------------- /TZImagePickerController/Assets.xcassets/AlbumAddBtn.imageset/AlbumAddBtn@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/Assets.xcassets/AlbumAddBtn.imageset/AlbumAddBtn@2x.png -------------------------------------------------------------------------------- /TZImagePickerController/Assets.xcassets/AlbumAddBtn.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "AlbumAddBtn@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TZImagePickerController/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 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /TZImagePickerController/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /TZImagePickerController/Assets.xcassets/back.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "back@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TZImagePickerController/Assets.xcassets/back.imageset/back@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/Assets.xcassets/back.imageset/back@2x.png -------------------------------------------------------------------------------- /TZImagePickerController/Assets.xcassets/leftVideoEdit.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "hx_videoedit_left@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /TZImagePickerController/Assets.xcassets/leftVideoEdit.imageset/hx_videoedit_left@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/Assets.xcassets/leftVideoEdit.imageset/hx_videoedit_left@2x.png -------------------------------------------------------------------------------- /TZImagePickerController/Assets.xcassets/photo_delete.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "photo_delete@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /TZImagePickerController/Assets.xcassets/photo_delete.imageset/photo_delete@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/Assets.xcassets/photo_delete.imageset/photo_delete@2x.png -------------------------------------------------------------------------------- /TZImagePickerController/Assets.xcassets/rightVideoEdit.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "hx_videoedit_right@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /TZImagePickerController/Assets.xcassets/rightVideoEdit.imageset/hx_videoedit_right@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/Assets.xcassets/rightVideoEdit.imageset/hx_videoedit_right@2x.png -------------------------------------------------------------------------------- /TZImagePickerController/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /TZImagePickerController/FLAnimatedImage/FLAnimatedImage.h: -------------------------------------------------------------------------------- 1 | // 2 | // FLAnimatedImage.h 3 | // Flipboard 4 | // 5 | // Created by Raphael Schaad on 7/8/13. 6 | // Copyright (c) 2013-2015 Flipboard. All rights reserved. 7 | // 8 | 9 | 10 | #import 11 | 12 | // Allow user classes conveniently just importing one header. 13 | #import "FLAnimatedImageView.h" 14 | 15 | 16 | #ifndef NS_DESIGNATED_INITIALIZER 17 | #if __has_attribute(objc_designated_initializer) 18 | #define NS_DESIGNATED_INITIALIZER __attribute((objc_designated_initializer)) 19 | #else 20 | #define NS_DESIGNATED_INITIALIZER 21 | #endif 22 | #endif 23 | 24 | extern const NSTimeInterval kFLAnimatedImageDelayTimeIntervalMinimum; 25 | 26 | // 27 | // An `FLAnimatedImage`'s job is to deliver frames in a highly performant way and works in conjunction with `FLAnimatedImageView`. 28 | // It subclasses `NSObject` and not `UIImage` because it's only an "image" in the sense that a sea lion is a lion. 29 | // It tries to intelligently choose the frame cache size depending on the image and memory situation with the goal to lower CPU usage for smaller ones, lower memory usage for larger ones and always deliver frames for high performant play-back. 30 | // Note: `posterImage`, `size`, `loopCount`, `delayTimes` and `frameCount` don't change after successful initialization. 31 | // 32 | @interface FLAnimatedImage : NSObject 33 | 34 | @property (nonatomic, strong, readonly) UIImage *posterImage; // Guaranteed to be loaded; usually equivalent to `-imageLazilyCachedAtIndex:0` 35 | @property (nonatomic, assign, readonly) CGSize size; // The `.posterImage`'s `.size` 36 | 37 | @property (nonatomic, assign, readonly) NSUInteger loopCount; // 0 means repeating the animation indefinitely 38 | @property (nonatomic, strong, readonly) NSDictionary *delayTimesForIndexes; // Of type `NSTimeInterval` boxed in `NSNumber`s 39 | @property (nonatomic, assign, readonly) NSUInteger frameCount; // Number of valid frames; equal to `[.delayTimes count]` 40 | 41 | @property (nonatomic, assign, readonly) NSUInteger frameCacheSizeCurrent; // Current size of intelligently chosen buffer window; can range in the interval [1..frameCount] 42 | @property (nonatomic, assign) NSUInteger frameCacheSizeMax; // Allow to cap the cache size; 0 means no specific limit (default) 43 | 44 | // Intended to be called from main thread synchronously; will return immediately. 45 | // If the result isn't cached, will return `nil`; the caller should then pause playback, not increment frame counter and keep polling. 46 | // After an initial loading time, depending on `frameCacheSize`, frames should be available immediately from the cache. 47 | - (UIImage *)imageLazilyCachedAtIndex:(NSUInteger)index; 48 | 49 | // Pass either a `UIImage` or an `FLAnimatedImage` and get back its size 50 | + (CGSize)sizeForImage:(id)image; 51 | 52 | // On success, the initializers return an `FLAnimatedImage` with all fields initialized, on failure they return `nil` and an error will be logged. 53 | - (instancetype)initWithAnimatedGIFData:(NSData *)data; 54 | // Pass 0 for optimalFrameCacheSize to get the default, predrawing is enabled by default. 55 | - (instancetype)initWithAnimatedGIFData:(NSData *)data optimalFrameCacheSize:(NSUInteger)optimalFrameCacheSize predrawingEnabled:(BOOL)isPredrawingEnabled NS_DESIGNATED_INITIALIZER; 56 | + (instancetype)animatedImageWithGIFData:(NSData *)data; 57 | 58 | @property (nonatomic, strong, readonly) NSData *data; // The data the receiver was initialized with; read-only 59 | 60 | @end 61 | 62 | typedef NS_ENUM(NSUInteger, FLLogLevel) { 63 | FLLogLevelNone = 0, 64 | FLLogLevelError, 65 | FLLogLevelWarn, 66 | FLLogLevelInfo, 67 | FLLogLevelDebug, 68 | FLLogLevelVerbose 69 | }; 70 | 71 | @interface FLAnimatedImage (Logging) 72 | 73 | + (void)setLogBlock:(void (^)(NSString *logString, FLLogLevel logLevel))logBlock logLevel:(FLLogLevel)logLevel; 74 | + (void)logStringFromBlock:(NSString *(^)(void))stringBlock withLevel:(FLLogLevel)level; 75 | 76 | @end 77 | 78 | #define FLLog(logLevel, format, ...) [FLAnimatedImage logStringFromBlock:^NSString *{ return [NSString stringWithFormat:(format), ## __VA_ARGS__]; } withLevel:(logLevel)] 79 | 80 | @interface FLWeakProxy : NSProxy 81 | 82 | + (instancetype)weakProxyForObject:(id)targetObject; 83 | 84 | @end 85 | -------------------------------------------------------------------------------- /TZImagePickerController/FLAnimatedImage/FLAnimatedImageView.h: -------------------------------------------------------------------------------- 1 | // 2 | // FLAnimatedImageView.h 3 | // Flipboard 4 | // 5 | // Created by Raphael Schaad on 7/8/13. 6 | // Copyright (c) 2013-2015 Flipboard. All rights reserved. 7 | // 8 | 9 | 10 | #import 11 | 12 | @class FLAnimatedImage; 13 | @protocol FLAnimatedImageViewDebugDelegate; 14 | 15 | 16 | // 17 | // An `FLAnimatedImageView` can take an `FLAnimatedImage` and plays it automatically when in view hierarchy and stops when removed. 18 | // The animation can also be controlled with the `UIImageView` methods `-start/stop/isAnimating`. 19 | // It is a fully compatible `UIImageView` subclass and can be used as a drop-in component to work with existing code paths expecting to display a `UIImage`. 20 | // Under the hood it uses a `CADisplayLink` for playback, which can be inspected with `currentFrame` & `currentFrameIndex`. 21 | // 22 | @interface FLAnimatedImageView : UIImageView 23 | 24 | // Setting `[UIImageView.image]` to a non-`nil` value clears out existing `animatedImage`. 25 | // And vice versa, setting `animatedImage` will initially populate the `[UIImageView.image]` to its `posterImage` and then start animating and hold `currentFrame`. 26 | @property (nonatomic, strong) FLAnimatedImage *animatedImage; 27 | @property (nonatomic, copy) void(^loopCompletionBlock)(NSUInteger loopCountRemaining); 28 | 29 | @property (nonatomic, strong, readonly) UIImage *currentFrame; 30 | @property (nonatomic, assign, readonly) NSUInteger currentFrameIndex; 31 | 32 | // The animation runloop mode. Enables playback during scrolling by allowing timer events (i.e. animation) with NSRunLoopCommonModes. 33 | // To keep scrolling smooth on single-core devices such as iPhone 3GS/4 and iPod Touch 4th gen, the default run loop mode is NSDefaultRunLoopMode. Otherwise, the default is NSDefaultRunLoopMode. 34 | @property (nonatomic, copy) NSString *runLoopMode; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /TZImagePickerController/FLAnimatedImage/FLAnimatedImageView.m: -------------------------------------------------------------------------------- 1 | // 2 | // FLAnimatedImageView.h 3 | // Flipboard 4 | // 5 | // Created by Raphael Schaad on 7/8/13. 6 | // Copyright (c) 2013-2015 Flipboard. All rights reserved. 7 | // 8 | 9 | 10 | #import "FLAnimatedImageView.h" 11 | #import "FLAnimatedImage.h" 12 | #import 13 | 14 | 15 | #if defined(DEBUG) && DEBUG 16 | @protocol FLAnimatedImageViewDebugDelegate 17 | @optional 18 | - (void)debug_animatedImageView:(FLAnimatedImageView *)animatedImageView waitingForFrame:(NSUInteger)index duration:(NSTimeInterval)duration; 19 | @end 20 | #endif 21 | 22 | 23 | @interface FLAnimatedImageView () 24 | 25 | // Override of public `readonly` properties as private `readwrite` 26 | @property (nonatomic, strong, readwrite) UIImage *currentFrame; 27 | @property (nonatomic, assign, readwrite) NSUInteger currentFrameIndex; 28 | 29 | @property (nonatomic, assign) NSUInteger loopCountdown; 30 | @property (nonatomic, assign) NSTimeInterval accumulator; 31 | @property (nonatomic, strong) CADisplayLink *displayLink; 32 | 33 | @property (nonatomic, assign) BOOL shouldAnimate; // Before checking this value, call `-updateShouldAnimate` whenever the animated image or visibility (window, superview, hidden, alpha) has changed. 34 | @property (nonatomic, assign) BOOL needsDisplayWhenImageBecomesAvailable; 35 | 36 | #if defined(DEBUG) && DEBUG 37 | @property (nonatomic, weak) id debug_delegate; 38 | #endif 39 | 40 | @end 41 | 42 | 43 | @implementation FLAnimatedImageView 44 | @synthesize runLoopMode = _runLoopMode; 45 | 46 | #pragma mark - Initializers 47 | 48 | // -initWithImage: isn't documented as a designated initializer of UIImageView, but it actually seems to be. 49 | // Using -initWithImage: doesn't call any of the other designated initializers. 50 | - (instancetype)initWithImage:(UIImage *)image 51 | { 52 | self = [super initWithImage:image]; 53 | if (self) { 54 | [self commonInit]; 55 | } 56 | return self; 57 | } 58 | 59 | // -initWithImage:highlightedImage: also isn't documented as a designated initializer of UIImageView, but it doesn't call any other designated initializers. 60 | - (instancetype)initWithImage:(UIImage *)image highlightedImage:(UIImage *)highlightedImage 61 | { 62 | self = [super initWithImage:image highlightedImage:highlightedImage]; 63 | if (self) { 64 | [self commonInit]; 65 | } 66 | return self; 67 | } 68 | 69 | - (instancetype)initWithFrame:(CGRect)frame 70 | { 71 | self = [super initWithFrame:frame]; 72 | if (self) { 73 | [self commonInit]; 74 | } 75 | return self; 76 | } 77 | 78 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 79 | { 80 | self = [super initWithCoder:aDecoder]; 81 | if (self) { 82 | [self commonInit]; 83 | } 84 | return self; 85 | } 86 | 87 | - (void)commonInit 88 | { 89 | self.runLoopMode = [[self class] defaultRunLoopMode]; 90 | 91 | if (@available(iOS 11.0, *)) { 92 | self.accessibilityIgnoresInvertColors = YES; 93 | } 94 | } 95 | 96 | 97 | #pragma mark - Accessors 98 | #pragma mark Public 99 | 100 | - (void)setAnimatedImage:(FLAnimatedImage *)animatedImage 101 | { 102 | if (![_animatedImage isEqual:animatedImage]) { 103 | if (animatedImage) { 104 | // Clear out the image. 105 | super.image = nil; 106 | // Ensure disabled highlighting; it's not supported (see `-setHighlighted:`). 107 | super.highlighted = NO; 108 | // UIImageView seems to bypass some accessors when calculating its intrinsic content size, so this ensures its intrinsic content size comes from the animated image. 109 | [self invalidateIntrinsicContentSize]; 110 | } else { 111 | // Stop animating before the animated image gets cleared out. 112 | [self stopAnimating]; 113 | } 114 | 115 | _animatedImage = animatedImage; 116 | 117 | self.currentFrame = animatedImage.posterImage; 118 | self.currentFrameIndex = 0; 119 | if (animatedImage.loopCount > 0) { 120 | self.loopCountdown = animatedImage.loopCount; 121 | } else { 122 | self.loopCountdown = NSUIntegerMax; 123 | } 124 | self.accumulator = 0.0; 125 | 126 | // Start animating after the new animated image has been set. 127 | [self updateShouldAnimate]; 128 | if (self.shouldAnimate) { 129 | [self startAnimating]; 130 | } 131 | 132 | [self.layer setNeedsDisplay]; 133 | } 134 | } 135 | 136 | 137 | #pragma mark - Life Cycle 138 | 139 | - (void)dealloc 140 | { 141 | // Removes the display link from all run loop modes. 142 | [_displayLink invalidate]; 143 | } 144 | 145 | 146 | #pragma mark - UIView Method Overrides 147 | #pragma mark Observing View-Related Changes 148 | 149 | - (void)didMoveToSuperview 150 | { 151 | [super didMoveToSuperview]; 152 | 153 | [self updateShouldAnimate]; 154 | if (self.shouldAnimate) { 155 | [self startAnimating]; 156 | } else { 157 | [self stopAnimating]; 158 | } 159 | } 160 | 161 | 162 | - (void)didMoveToWindow 163 | { 164 | [super didMoveToWindow]; 165 | 166 | [self updateShouldAnimate]; 167 | if (self.shouldAnimate) { 168 | [self startAnimating]; 169 | } else { 170 | [self stopAnimating]; 171 | } 172 | } 173 | 174 | - (void)setAlpha:(CGFloat)alpha 175 | { 176 | [super setAlpha:alpha]; 177 | 178 | [self updateShouldAnimate]; 179 | if (self.shouldAnimate) { 180 | [self startAnimating]; 181 | } else { 182 | [self stopAnimating]; 183 | } 184 | } 185 | 186 | - (void)setHidden:(BOOL)hidden 187 | { 188 | [super setHidden:hidden]; 189 | 190 | [self updateShouldAnimate]; 191 | if (self.shouldAnimate) { 192 | [self startAnimating]; 193 | } else { 194 | [self stopAnimating]; 195 | } 196 | } 197 | 198 | 199 | #pragma mark Auto Layout 200 | 201 | - (CGSize)intrinsicContentSize 202 | { 203 | // Default to let UIImageView handle the sizing of its image, and anything else it might consider. 204 | CGSize intrinsicContentSize = [super intrinsicContentSize]; 205 | 206 | // If we have have an animated image, use its image size. 207 | // UIImageView's intrinsic content size seems to be the size of its image. The obvious approach, simply calling `-invalidateIntrinsicContentSize` when setting an animated image, results in UIImageView steadfastly returning `{UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric}` for its intrinsicContentSize. 208 | // (Perhaps UIImageView bypasses its `-image` getter in its implementation of `-intrinsicContentSize`, as `-image` is not called after calling `-invalidateIntrinsicContentSize`.) 209 | if (self.animatedImage) { 210 | intrinsicContentSize = self.image.size; 211 | } 212 | 213 | return intrinsicContentSize; 214 | } 215 | 216 | #pragma mark Smart Invert Colors 217 | 218 | #pragma mark - UIImageView Method Overrides 219 | #pragma mark Image Data 220 | 221 | - (UIImage *)image 222 | { 223 | UIImage *image = nil; 224 | if (self.animatedImage) { 225 | // Initially set to the poster image. 226 | image = self.currentFrame; 227 | } else { 228 | image = super.image; 229 | } 230 | return image; 231 | } 232 | 233 | 234 | - (void)setImage:(UIImage *)image 235 | { 236 | if (image) { 237 | // Clear out the animated image and implicitly pause animation playback. 238 | self.animatedImage = nil; 239 | } 240 | 241 | super.image = image; 242 | } 243 | 244 | 245 | #pragma mark Animating Images 246 | 247 | - (NSTimeInterval)frameDelayGreatestCommonDivisor 248 | { 249 | // Presision is set to half of the `kFLAnimatedImageDelayTimeIntervalMinimum` in order to minimize frame dropping. 250 | const NSTimeInterval kGreatestCommonDivisorPrecision = 2.0 / kFLAnimatedImageDelayTimeIntervalMinimum; 251 | 252 | NSArray *delays = self.animatedImage.delayTimesForIndexes.allValues; 253 | 254 | // Scales the frame delays by `kGreatestCommonDivisorPrecision` 255 | // then converts it to an UInteger for in order to calculate the GCD. 256 | NSUInteger scaledGCD = lrint([delays.firstObject floatValue] * kGreatestCommonDivisorPrecision); 257 | for (NSNumber *value in delays) { 258 | scaledGCD = gcd(lrint([value floatValue] * kGreatestCommonDivisorPrecision), scaledGCD); 259 | } 260 | 261 | // Reverse to scale to get the value back into seconds. 262 | return scaledGCD / kGreatestCommonDivisorPrecision; 263 | } 264 | 265 | 266 | static NSUInteger gcd(NSUInteger a, NSUInteger b) 267 | { 268 | // http://en.wikipedia.org/wiki/Greatest_common_divisor 269 | if (a < b) { 270 | return gcd(b, a); 271 | } else if (a == b) { 272 | return b; 273 | } 274 | 275 | while (true) { 276 | NSUInteger remainder = a % b; 277 | if (remainder == 0) { 278 | return b; 279 | } 280 | a = b; 281 | b = remainder; 282 | } 283 | } 284 | 285 | 286 | - (void)startAnimating 287 | { 288 | if (self.animatedImage) { 289 | // Lazily create the display link. 290 | if (!self.displayLink) { 291 | // It is important to note the use of a weak proxy here to avoid a retain cycle. `-displayLinkWithTarget:selector:` 292 | // will retain its target until it is invalidated. We use a weak proxy so that the image view will get deallocated 293 | // independent of the display link's lifetime. Upon image view deallocation, we invalidate the display 294 | // link which will lead to the deallocation of both the display link and the weak proxy. 295 | FLWeakProxy *weakProxy = [FLWeakProxy weakProxyForObject:self]; 296 | self.displayLink = [CADisplayLink displayLinkWithTarget:weakProxy selector:@selector(displayDidRefresh:)]; 297 | 298 | [self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:self.runLoopMode]; 299 | } 300 | 301 | // Note: The display link's `.frameInterval` value of 1 (default) means getting callbacks at the refresh rate of the display (~60Hz). 302 | // Setting it to 2 divides the frame rate by 2 and hence calls back at every other display refresh. 303 | const NSTimeInterval kDisplayRefreshRate = 60.0; // 60Hz 304 | self.displayLink.frameInterval = MAX([self frameDelayGreatestCommonDivisor] * kDisplayRefreshRate, 1); 305 | 306 | self.displayLink.paused = NO; 307 | } else { 308 | [super startAnimating]; 309 | } 310 | } 311 | 312 | - (void)setRunLoopMode:(NSString *)runLoopMode 313 | { 314 | if (![@[NSDefaultRunLoopMode, NSRunLoopCommonModes] containsObject:runLoopMode]) { 315 | NSAssert(NO, @"Invalid run loop mode: %@", runLoopMode); 316 | _runLoopMode = [[self class] defaultRunLoopMode]; 317 | } else { 318 | _runLoopMode = runLoopMode; 319 | } 320 | } 321 | 322 | - (void)stopAnimating 323 | { 324 | if (self.animatedImage) { 325 | self.displayLink.paused = YES; 326 | } else { 327 | [super stopAnimating]; 328 | } 329 | } 330 | 331 | 332 | - (BOOL)isAnimating 333 | { 334 | BOOL isAnimating = NO; 335 | if (self.animatedImage) { 336 | isAnimating = self.displayLink && !self.displayLink.isPaused; 337 | } else { 338 | isAnimating = [super isAnimating]; 339 | } 340 | return isAnimating; 341 | } 342 | 343 | 344 | #pragma mark Highlighted Image Unsupport 345 | 346 | - (void)setHighlighted:(BOOL)highlighted 347 | { 348 | // Highlighted image is unsupported for animated images, but implementing it breaks the image view when embedded in a UICollectionViewCell. 349 | if (!self.animatedImage) { 350 | [super setHighlighted:highlighted]; 351 | } 352 | } 353 | 354 | 355 | #pragma mark - Private Methods 356 | #pragma mark Animation 357 | 358 | // Don't repeatedly check our window & superview in `-displayDidRefresh:` for performance reasons. 359 | // Just update our cached value whenever the animated image or visibility (window, superview, hidden, alpha) is changed. 360 | - (void)updateShouldAnimate 361 | { 362 | BOOL isVisible = self.window && self.superview && ![self isHidden] && self.alpha > 0.0; 363 | self.shouldAnimate = self.animatedImage && isVisible; 364 | } 365 | 366 | 367 | - (void)displayDidRefresh:(CADisplayLink *)displayLink 368 | { 369 | // If for some reason a wild call makes it through when we shouldn't be animating, bail. 370 | // Early return! 371 | if (!self.shouldAnimate) { 372 | FLLog(FLLogLevelWarn, @"Trying to animate image when we shouldn't: %@", self); 373 | return; 374 | } 375 | 376 | NSNumber *delayTimeNumber = [self.animatedImage.delayTimesForIndexes objectForKey:@(self.currentFrameIndex)]; 377 | // If we don't have a frame delay (e.g. corrupt frame), don't update the view but skip the playhead to the next frame (in else-block). 378 | if (delayTimeNumber) { 379 | NSTimeInterval delayTime = [delayTimeNumber floatValue]; 380 | // If we have a nil image (e.g. waiting for frame), don't update the view nor playhead. 381 | UIImage *image = [self.animatedImage imageLazilyCachedAtIndex:self.currentFrameIndex]; 382 | if (image) { 383 | FLLog(FLLogLevelVerbose, @"Showing frame %lu for animated image: %@", (unsigned long)self.currentFrameIndex, self.animatedImage); 384 | self.currentFrame = image; 385 | if (self.needsDisplayWhenImageBecomesAvailable) { 386 | [self.layer setNeedsDisplay]; 387 | self.needsDisplayWhenImageBecomesAvailable = NO; 388 | } 389 | 390 | self.accumulator += displayLink.duration * displayLink.frameInterval; 391 | 392 | // While-loop first inspired by & good Karma to: https://github.com/ondalabs/OLImageView/blob/master/OLImageView.m 393 | while (self.accumulator >= delayTime) { 394 | self.accumulator -= delayTime; 395 | self.currentFrameIndex++; 396 | if (self.currentFrameIndex >= self.animatedImage.frameCount) { 397 | // If we've looped the number of times that this animated image describes, stop looping. 398 | self.loopCountdown--; 399 | if (self.loopCompletionBlock) { 400 | self.loopCompletionBlock(self.loopCountdown); 401 | } 402 | 403 | if (self.loopCountdown == 0) { 404 | [self stopAnimating]; 405 | return; 406 | } 407 | self.currentFrameIndex = 0; 408 | } 409 | // Calling `-setNeedsDisplay` will just paint the current frame, not the new frame that we may have moved to. 410 | // Instead, set `needsDisplayWhenImageBecomesAvailable` to `YES` -- this will paint the new image once loaded. 411 | self.needsDisplayWhenImageBecomesAvailable = YES; 412 | } 413 | } else { 414 | FLLog(FLLogLevelDebug, @"Waiting for frame %lu for animated image: %@", (unsigned long)self.currentFrameIndex, self.animatedImage); 415 | #if defined(DEBUG) && DEBUG 416 | if ([self.debug_delegate respondsToSelector:@selector(debug_animatedImageView:waitingForFrame:duration:)]) { 417 | [self.debug_delegate debug_animatedImageView:self waitingForFrame:self.currentFrameIndex duration:(NSTimeInterval)displayLink.duration * displayLink.frameInterval]; 418 | } 419 | #endif 420 | } 421 | } else { 422 | self.currentFrameIndex++; 423 | } 424 | } 425 | 426 | + (NSString *)defaultRunLoopMode 427 | { 428 | // Key off `activeProcessorCount` (as opposed to `processorCount`) since the system could shut down cores in certain situations. 429 | return [NSProcessInfo processInfo].activeProcessorCount > 1 ? NSRunLoopCommonModes : NSDefaultRunLoopMode; 430 | } 431 | 432 | 433 | #pragma mark - CALayerDelegate (Informal) 434 | #pragma mark Providing the Layer's Content 435 | 436 | - (void)displayLayer:(CALayer *)layer 437 | { 438 | layer.contents = (__bridge id)self.image.CGImage; 439 | } 440 | 441 | 442 | @end 443 | -------------------------------------------------------------------------------- /TZImagePickerController/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en_US 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSCameraUsageDescription 26 | 访问相机以拍照 27 | NSLocationUsageDescription 28 | 允许定位以把位置保存到照片中 29 | NSLocationWhenInUseUsageDescription 30 | 允许定位以把位置保存到照片中 31 | NSMicrophoneUsageDescription 32 | 访问麦克风以录像 33 | NSPhotoLibraryUsageDescription 34 | 访问相册以选择照片 35 | PHPhotoLibraryPreventAutomaticLimitedAccessAlert 36 | 37 | UILaunchStoryboardName 38 | LaunchScreen 39 | UIMainStoryboardFile 40 | Main 41 | UIRequiredDeviceCapabilities 42 | 43 | armv7 44 | 45 | UIStatusBarHidden 46 | 47 | UISupportedInterfaceOrientations 48 | 49 | UIInterfaceOrientationPortrait 50 | UIInterfaceOrientationLandscapeLeft 51 | UIInterfaceOrientationLandscapeRight 52 | UIInterfaceOrientationPortraitUpsideDown 53 | 54 | UIViewControllerBasedStatusBarAppearance 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /TZImagePickerController/Location/TZLocationManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // TZLocationManager.h 3 | // TZImagePickerController 4 | // 5 | // Created by 谭真 on 2017/06/03. 6 | // Copyright © 2017年 谭真. All rights reserved. 7 | // 定位管理类 8 | 9 | 10 | #import 11 | #import 12 | 13 | @interface TZLocationManager : NSObject 14 | 15 | + (instancetype)manager NS_SWIFT_NAME(default()); 16 | 17 | /// 开始定位 18 | - (void)startLocation; 19 | - (void)startLocationWithSuccessBlock:(void (^)(NSArray *))successBlock failureBlock:(void (^)(NSError *error))failureBlock; 20 | - (void)startLocationWithGeocoderBlock:(void (^)(NSArray *geocoderArray))geocoderBlock; 21 | - (void)startLocationWithSuccessBlock:(void (^)(NSArray *))successBlock failureBlock:(void (^)(NSError *error))failureBlock geocoderBlock:(void (^)(NSArray *geocoderArray))geocoderBlock; 22 | 23 | /// 结束定位 24 | - (void)stopUpdatingLocation; 25 | 26 | @end 27 | 28 | -------------------------------------------------------------------------------- /TZImagePickerController/Location/TZLocationManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // TZLocationManager.m 3 | // TZImagePickerController 4 | // 5 | // Created by 谭真 on 2017/06/03. 6 | // Copyright © 2017年 谭真. All rights reserved. 7 | // 定位管理类 8 | 9 | #import "TZLocationManager.h" 10 | 11 | @interface TZLocationManager () 12 | @property (nonatomic, strong) CLLocationManager *locationManager; 13 | /// 定位成功的回调block 14 | @property (nonatomic, copy) void (^successBlock)(NSArray *); 15 | /// 编码成功的回调block 16 | @property (nonatomic, copy) void (^geocodeBlock)(NSArray *geocodeArray); 17 | /// 定位失败的回调block 18 | @property (nonatomic, copy) void (^failureBlock)(NSError *error); 19 | @end 20 | 21 | @implementation TZLocationManager 22 | 23 | + (instancetype)manager { 24 | static TZLocationManager *manager; 25 | static dispatch_once_t onceToken; 26 | dispatch_once(&onceToken, ^{ 27 | manager = [[self alloc] init]; 28 | manager.locationManager = [[CLLocationManager alloc] init]; 29 | manager.locationManager.delegate = manager; 30 | [manager.locationManager requestWhenInUseAuthorization]; 31 | }); 32 | return manager; 33 | } 34 | 35 | - (void)startLocation { 36 | [self startLocationWithSuccessBlock:nil failureBlock:nil geocoderBlock:nil]; 37 | } 38 | 39 | - (void)startLocationWithSuccessBlock:(void (^)(NSArray *))successBlock failureBlock:(void (^)(NSError *error))failureBlock { 40 | [self startLocationWithSuccessBlock:successBlock failureBlock:failureBlock geocoderBlock:nil]; 41 | } 42 | 43 | - (void)startLocationWithGeocoderBlock:(void (^)(NSArray *geocoderArray))geocoderBlock { 44 | [self startLocationWithSuccessBlock:nil failureBlock:nil geocoderBlock:geocoderBlock]; 45 | } 46 | 47 | - (void)startLocationWithSuccessBlock:(void (^)(NSArray *))successBlock failureBlock:(void (^)(NSError *error))failureBlock geocoderBlock:(void (^)(NSArray *geocoderArray))geocoderBlock { 48 | [self.locationManager startUpdatingLocation]; 49 | _successBlock = successBlock; 50 | _geocodeBlock = geocoderBlock; 51 | _failureBlock = failureBlock; 52 | } 53 | 54 | - (void)stopUpdatingLocation { 55 | [self.locationManager stopUpdatingLocation]; 56 | } 57 | 58 | #pragma mark - CLLocationManagerDelegate 59 | 60 | /// 地理位置发生改变时触发 61 | - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations { 62 | [manager stopUpdatingLocation]; 63 | 64 | if (_successBlock) { 65 | _successBlock(locations); 66 | } 67 | 68 | if (_geocodeBlock && locations.count) { 69 | CLGeocoder *geocoder = [[CLGeocoder alloc] init]; 70 | [geocoder reverseGeocodeLocation:[locations firstObject] completionHandler:^(NSArray *array, NSError *error) { 71 | self->_geocodeBlock(array); 72 | }]; 73 | } 74 | } 75 | 76 | /// 定位失败回调方法 77 | - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error { 78 | NSLog(@"定位失败, 错误: %@",error); 79 | switch([error code]) { 80 | case kCLErrorDenied: { // 用户禁止了定位权限 81 | 82 | } break; 83 | default: break; 84 | } 85 | if (_failureBlock) { 86 | _failureBlock(error); 87 | } 88 | } 89 | 90 | @end 91 | -------------------------------------------------------------------------------- /TZImagePickerController/LxGridViewFlowLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // LxGridViewFlowLayout.h 3 | // LxGridView 4 | // 5 | 6 | #import 7 | 8 | /* 9 | 此类来源于DeveloperLx的优秀开源项目:LxGridView 10 | github链接:https://github.com/DeveloperLx/LxGridView 11 | 我对这个类的代码做了一些修改; 12 | 感谢DeveloperLx的优秀代码~ 13 | */ 14 | 15 | @interface LxGridViewFlowLayout : UICollectionViewFlowLayout 16 | 17 | @property (nonatomic,assign) BOOL panGestureRecognizerEnable; 18 | 19 | @end 20 | 21 | @protocol LxGridViewDataSource 22 | 23 | @optional 24 | 25 | - (void)collectionView:(UICollectionView *)collectionView 26 | itemAtIndexPath:(NSIndexPath *)sourceIndexPath 27 | willMoveToIndexPath:(NSIndexPath *)destinationIndexPath; 28 | - (void)collectionView:(UICollectionView *)collectionView 29 | itemAtIndexPath:(NSIndexPath *)sourceIndexPath 30 | didMoveToIndexPath:(NSIndexPath *)destinationIndexPath; 31 | 32 | - (BOOL)collectionView:(UICollectionView *)collectionView 33 | canMoveItemAtIndexPath:(NSIndexPath *)indexPath; 34 | - (BOOL)collectionView:(UICollectionView *)collectionView 35 | itemAtIndexPath:(NSIndexPath *)sourceIndexPath 36 | canMoveToIndexPath:(NSIndexPath *)destinationIndexPath; 37 | 38 | @end 39 | 40 | @protocol LxGridViewDelegateFlowLayout 41 | 42 | @optional 43 | 44 | - (void)collectionView:(UICollectionView *)collectionView 45 | layout:(UICollectionViewLayout *)collectionViewLayout 46 | willBeginDraggingItemAtIndexPath:(NSIndexPath *)indexPath; 47 | - (void)collectionView:(UICollectionView *)collectionView 48 | layout:(UICollectionViewLayout *)collectionViewLayout 49 | didBeginDraggingItemAtIndexPath:(NSIndexPath *)indexPath; 50 | - (void)collectionView:(UICollectionView *)collectionView 51 | layout:(UICollectionViewLayout *)collectionViewLayout 52 | willEndDraggingItemAtIndexPath:(NSIndexPath *)indexPath; 53 | - (void)collectionView:(UICollectionView *)collectionView 54 | layout:(UICollectionViewLayout *)collectionViewLayout 55 | didEndDraggingItemAtIndexPath:(NSIndexPath *)indexPath; 56 | 57 | @end -------------------------------------------------------------------------------- /TZImagePickerController/LxGridViewFlowLayout.m: -------------------------------------------------------------------------------- 1 | // 2 | // LxGridViewFlowLayout.m 3 | // LxGridView 4 | // 5 | 6 | #import "LxGridViewFlowLayout.h" 7 | #import "TZTestCell.h" 8 | #import "UIView+TZLayout.h" 9 | 10 | #define stringify __STRING 11 | 12 | static CGFloat const PRESS_TO_MOVE_MIN_DURATION = 0.1; 13 | static CGFloat const MIN_PRESS_TO_BEGIN_EDITING_DURATION = 0.6; 14 | 15 | CG_INLINE CGPoint CGPointOffset(CGPoint point, CGFloat dx, CGFloat dy) 16 | { 17 | return CGPointMake(point.x + dx, point.y + dy); 18 | } 19 | 20 | /* 21 | 此类来源于DeveloperLx的优秀开源项目:LxGridView 22 | github链接:https://github.com/DeveloperLx/LxGridView 23 | 我对这个类的代码做了一些修改; 24 | 感谢DeveloperLx的优秀代码~ 25 | */ 26 | 27 | @interface LxGridViewFlowLayout () 28 | 29 | @property (nonatomic,readonly) id dataSource; 30 | @property (nonatomic,readonly) id delegate; 31 | 32 | @end 33 | 34 | @implementation LxGridViewFlowLayout 35 | { 36 | UILongPressGestureRecognizer * _longPressGestureRecognizer; 37 | UIPanGestureRecognizer * _panGestureRecognizer; 38 | NSIndexPath * _movingItemIndexPath; 39 | UIView * _beingMovedPromptView; 40 | CGPoint _sourceItemCollectionViewCellCenter; 41 | 42 | CADisplayLink * _displayLink; 43 | CFTimeInterval _remainSecondsToBeginEditing; 44 | } 45 | 46 | #pragma mark - setup 47 | 48 | - (void)dealloc 49 | { 50 | [_displayLink invalidate]; 51 | 52 | [self removeGestureRecognizers]; 53 | [self removeObserver:self forKeyPath:@stringify(collectionView)]; 54 | } 55 | 56 | - (instancetype)init 57 | { 58 | if (self = [super init]) { 59 | [self setup]; 60 | } 61 | return self; 62 | } 63 | 64 | - (instancetype)initWithCoder:(NSCoder *)coder 65 | { 66 | if (self = [super initWithCoder:coder]) { 67 | [self setup]; 68 | } 69 | return self; 70 | } 71 | 72 | - (void)setup 73 | { 74 | [self addObserver:self forKeyPath:@stringify(collectionView) options:NSKeyValueObservingOptionNew context:nil]; 75 | } 76 | 77 | - (void)addGestureRecognizers 78 | { 79 | self.collectionView.userInteractionEnabled = YES; 80 | 81 | _longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPressGestureRecognizerTriggerd:)]; 82 | _longPressGestureRecognizer.cancelsTouchesInView = NO; 83 | _longPressGestureRecognizer.minimumPressDuration = PRESS_TO_MOVE_MIN_DURATION; 84 | _longPressGestureRecognizer.delegate = self; 85 | 86 | for (UIGestureRecognizer * gestureRecognizer in self.collectionView.gestureRecognizers) { 87 | if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) { 88 | [gestureRecognizer requireGestureRecognizerToFail:_longPressGestureRecognizer]; 89 | } 90 | } 91 | 92 | [self.collectionView addGestureRecognizer:_longPressGestureRecognizer]; 93 | 94 | _panGestureRecognizer = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panGestureRecognizerTriggerd:)]; 95 | _panGestureRecognizer.delegate = self; 96 | [self.collectionView addGestureRecognizer:_panGestureRecognizer]; 97 | 98 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil]; 99 | } 100 | 101 | - (void)removeGestureRecognizers 102 | { 103 | if (_longPressGestureRecognizer) { 104 | if (_longPressGestureRecognizer.view) { 105 | [_longPressGestureRecognizer.view removeGestureRecognizer:_longPressGestureRecognizer]; 106 | } 107 | _longPressGestureRecognizer = nil; 108 | } 109 | 110 | if (_panGestureRecognizer) { 111 | if (_panGestureRecognizer.view) { 112 | [_panGestureRecognizer.view removeGestureRecognizer:_panGestureRecognizer]; 113 | } 114 | _panGestureRecognizer = nil; 115 | } 116 | 117 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil]; 118 | } 119 | 120 | #pragma mark - getter and setter implementation 121 | 122 | - (id)dataSource 123 | { 124 | return (id)self.collectionView.dataSource; 125 | } 126 | 127 | - (id)delegate 128 | { 129 | return (id)self.collectionView.delegate; 130 | } 131 | 132 | #pragma mark - override UICollectionViewLayout methods 133 | 134 | - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect 135 | { 136 | NSArray * layoutAttributesForElementsInRect = [super layoutAttributesForElementsInRect:rect]; 137 | 138 | for (UICollectionViewLayoutAttributes * layoutAttributes in layoutAttributesForElementsInRect) { 139 | 140 | if (layoutAttributes.representedElementCategory == UICollectionElementCategoryCell) { 141 | layoutAttributes.hidden = [layoutAttributes.indexPath isEqual:_movingItemIndexPath]; 142 | } 143 | } 144 | return layoutAttributesForElementsInRect; 145 | } 146 | 147 | - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath 148 | { 149 | UICollectionViewLayoutAttributes * layoutAttributes = [super layoutAttributesForItemAtIndexPath:indexPath]; 150 | if (layoutAttributes.representedElementCategory == UICollectionElementCategoryCell) { 151 | layoutAttributes.hidden = [layoutAttributes.indexPath isEqual:_movingItemIndexPath]; 152 | } 153 | return layoutAttributes; 154 | } 155 | 156 | #pragma mark - gesture 157 | 158 | - (void)setPanGestureRecognizerEnable:(BOOL)panGestureRecognizerEnable 159 | { 160 | _panGestureRecognizer.enabled = panGestureRecognizerEnable; 161 | } 162 | 163 | - (BOOL)panGestureRecognizerEnable 164 | { 165 | return _panGestureRecognizer.enabled; 166 | } 167 | 168 | - (void)longPressGestureRecognizerTriggerd:(UILongPressGestureRecognizer *)longPress 169 | { 170 | switch (longPress.state) { 171 | case UIGestureRecognizerStatePossible: 172 | break; 173 | case UIGestureRecognizerStateBegan: 174 | { 175 | if (_displayLink == nil) { 176 | _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkTriggered:)]; 177 | _displayLink.frameInterval = 6; 178 | [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 179 | 180 | _remainSecondsToBeginEditing = MIN_PRESS_TO_BEGIN_EDITING_DURATION; 181 | } 182 | 183 | _movingItemIndexPath = [self.collectionView indexPathForItemAtPoint:[longPress locationInView:self.collectionView]]; 184 | if ([self.dataSource respondsToSelector:@selector(collectionView:canMoveItemAtIndexPath:)] && [self.dataSource collectionView:self.collectionView canMoveItemAtIndexPath:_movingItemIndexPath] == NO) { 185 | _movingItemIndexPath = nil; 186 | return; 187 | } 188 | 189 | if ([self.delegate respondsToSelector:@selector(collectionView:layout:willBeginDraggingItemAtIndexPath:)]) { 190 | [self.delegate collectionView:self.collectionView layout:self willBeginDraggingItemAtIndexPath:_movingItemIndexPath]; 191 | } 192 | 193 | UICollectionViewCell *sourceCollectionViewCell = [self.collectionView cellForItemAtIndexPath:_movingItemIndexPath]; 194 | TZTestCell *sourceCell = (TZTestCell *)sourceCollectionViewCell; 195 | 196 | _beingMovedPromptView = [[UIView alloc]initWithFrame:CGRectOffset(sourceCollectionViewCell.frame, -10, -10)]; 197 | _beingMovedPromptView.tz_width += 20; 198 | _beingMovedPromptView.tz_height += 20; 199 | 200 | sourceCollectionViewCell.highlighted = YES; 201 | UIView * highlightedSnapshotView = [sourceCell snapshotView]; 202 | highlightedSnapshotView.frame = _beingMovedPromptView.bounds; 203 | highlightedSnapshotView.alpha = 1; 204 | 205 | sourceCollectionViewCell.highlighted = NO; 206 | UIView * snapshotView = [sourceCell snapshotView]; 207 | snapshotView.frame = _beingMovedPromptView.bounds; 208 | snapshotView.alpha = 0; 209 | 210 | [_beingMovedPromptView addSubview:snapshotView]; 211 | [_beingMovedPromptView addSubview:highlightedSnapshotView]; 212 | [self.collectionView addSubview:_beingMovedPromptView]; 213 | 214 | _sourceItemCollectionViewCellCenter = sourceCollectionViewCell.center; 215 | 216 | typeof(self) __weak weakSelf = self; 217 | [UIView animateWithDuration:0 218 | delay:0 219 | options:UIViewAnimationOptionBeginFromCurrentState 220 | animations:^{ 221 | 222 | typeof(self) __strong strongSelf = weakSelf; 223 | if (strongSelf) { 224 | highlightedSnapshotView.alpha = 0; 225 | snapshotView.alpha = 1; 226 | } 227 | } 228 | completion:^(BOOL finished) { 229 | 230 | typeof(self) __strong strongSelf = weakSelf; 231 | if (strongSelf) { 232 | [highlightedSnapshotView removeFromSuperview]; 233 | 234 | if ([strongSelf.delegate respondsToSelector:@selector(collectionView:layout:didBeginDraggingItemAtIndexPath:)]) { 235 | [strongSelf.delegate collectionView:strongSelf.collectionView layout:strongSelf didBeginDraggingItemAtIndexPath:self->_movingItemIndexPath]; 236 | } 237 | } 238 | }]; 239 | 240 | [self invalidateLayout]; 241 | } 242 | break; 243 | case UIGestureRecognizerStateChanged: 244 | break; 245 | case UIGestureRecognizerStateEnded: 246 | case UIGestureRecognizerStateCancelled: 247 | { 248 | [_displayLink invalidate]; 249 | _displayLink = nil; 250 | 251 | NSIndexPath * movingItemIndexPath = _movingItemIndexPath; 252 | 253 | if (movingItemIndexPath) { 254 | if ([self.delegate respondsToSelector:@selector(collectionView:layout:willEndDraggingItemAtIndexPath:)]) { 255 | [self.delegate collectionView:self.collectionView layout:self willEndDraggingItemAtIndexPath:movingItemIndexPath]; 256 | } 257 | 258 | _movingItemIndexPath = nil; 259 | _sourceItemCollectionViewCellCenter = CGPointZero; 260 | 261 | UICollectionViewLayoutAttributes * movingItemCollectionViewLayoutAttributes = [self layoutAttributesForItemAtIndexPath:movingItemIndexPath]; 262 | 263 | _longPressGestureRecognizer.enabled = NO; 264 | 265 | typeof(self) __weak weakSelf = self; 266 | [UIView animateWithDuration:0.2 267 | delay:0 268 | options:UIViewAnimationOptionBeginFromCurrentState 269 | animations:^{ 270 | typeof(self) __strong strongSelf = weakSelf; 271 | if (strongSelf) { 272 | self->_beingMovedPromptView.center = movingItemCollectionViewLayoutAttributes.center; 273 | } 274 | } 275 | completion:^(BOOL finished) { 276 | 277 | self->_longPressGestureRecognizer.enabled = YES; 278 | 279 | typeof(self) __strong strongSelf = weakSelf; 280 | if (strongSelf) { 281 | [self->_beingMovedPromptView removeFromSuperview]; 282 | self->_beingMovedPromptView = nil; 283 | [strongSelf invalidateLayout]; 284 | 285 | if ([strongSelf.delegate respondsToSelector:@selector(collectionView:layout:didEndDraggingItemAtIndexPath:)]) { 286 | [strongSelf.delegate collectionView:strongSelf.collectionView layout:strongSelf didEndDraggingItemAtIndexPath:movingItemIndexPath]; 287 | } 288 | } 289 | }]; 290 | } 291 | } 292 | break; 293 | case UIGestureRecognizerStateFailed: 294 | break; 295 | default: 296 | break; 297 | } 298 | } 299 | 300 | - (void)panGestureRecognizerTriggerd:(UIPanGestureRecognizer *)pan 301 | { 302 | switch (pan.state) { 303 | case UIGestureRecognizerStatePossible: 304 | break; 305 | case UIGestureRecognizerStateBegan: 306 | case UIGestureRecognizerStateChanged: 307 | { 308 | CGPoint panTranslation = [pan translationInView:self.collectionView]; 309 | _beingMovedPromptView.center = CGPointOffset(_sourceItemCollectionViewCellCenter, panTranslation.x, panTranslation.y); 310 | 311 | NSIndexPath * sourceIndexPath = _movingItemIndexPath; 312 | NSIndexPath * destinationIndexPath = [self.collectionView indexPathForItemAtPoint:_beingMovedPromptView.center]; 313 | 314 | if ((destinationIndexPath == nil) || [destinationIndexPath isEqual:sourceIndexPath]) { 315 | return; 316 | } 317 | 318 | if ([self.dataSource respondsToSelector:@selector(collectionView:itemAtIndexPath:canMoveToIndexPath:)] && [self.dataSource collectionView:self.collectionView itemAtIndexPath:sourceIndexPath canMoveToIndexPath:destinationIndexPath] == NO) { 319 | return; 320 | } 321 | 322 | if ([self.dataSource respondsToSelector:@selector(collectionView:itemAtIndexPath:willMoveToIndexPath:)]) { 323 | [self.dataSource collectionView:self.collectionView itemAtIndexPath:sourceIndexPath willMoveToIndexPath:destinationIndexPath]; 324 | } 325 | 326 | _movingItemIndexPath = destinationIndexPath; 327 | 328 | typeof(self) __weak weakSelf = self; 329 | [self.collectionView performBatchUpdates:^{ 330 | typeof(self) __strong strongSelf = weakSelf; 331 | if (strongSelf) { 332 | if (sourceIndexPath && destinationIndexPath) { 333 | [strongSelf.collectionView deleteItemsAtIndexPaths:@[sourceIndexPath]]; 334 | [strongSelf.collectionView insertItemsAtIndexPaths:@[destinationIndexPath]]; 335 | } 336 | } 337 | } completion:^(BOOL finished) { 338 | typeof(self) __strong strongSelf = weakSelf; 339 | if ([strongSelf.dataSource respondsToSelector:@selector(collectionView:itemAtIndexPath:didMoveToIndexPath:)]) { 340 | [strongSelf.dataSource collectionView:strongSelf.collectionView itemAtIndexPath:sourceIndexPath didMoveToIndexPath:destinationIndexPath]; 341 | } 342 | }]; 343 | } 344 | break; 345 | case UIGestureRecognizerStateEnded: 346 | break; 347 | case UIGestureRecognizerStateCancelled: 348 | break; 349 | case UIGestureRecognizerStateFailed: 350 | break; 351 | default: 352 | break; 353 | } 354 | } 355 | 356 | - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer 357 | { 358 | if ([_panGestureRecognizer isEqual:gestureRecognizer]) { 359 | return _movingItemIndexPath != nil; 360 | } 361 | return YES; 362 | } 363 | 364 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer 365 | { 366 | // only _longPressGestureRecognizer and _panGestureRecognizer can recognize simultaneously 367 | if ([_longPressGestureRecognizer isEqual:gestureRecognizer]) { 368 | return [_panGestureRecognizer isEqual:otherGestureRecognizer]; 369 | } 370 | if ([_panGestureRecognizer isEqual:gestureRecognizer]) { 371 | return [_longPressGestureRecognizer isEqual:otherGestureRecognizer]; 372 | } 373 | return NO; 374 | } 375 | 376 | #pragma mark - displayLink 377 | 378 | - (void)displayLinkTriggered:(CADisplayLink *)displayLink 379 | { 380 | if (_remainSecondsToBeginEditing <= 0) { 381 | [_displayLink invalidate]; 382 | _displayLink = nil; 383 | } 384 | 385 | _remainSecondsToBeginEditing = _remainSecondsToBeginEditing - 0.1; 386 | } 387 | 388 | #pragma mark - KVO and notification 389 | 390 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 391 | { 392 | if ([keyPath isEqualToString:@stringify(collectionView)]) { 393 | if (self.collectionView) { 394 | [self addGestureRecognizers]; 395 | } 396 | else { 397 | [self removeGestureRecognizers]; 398 | } 399 | } 400 | } 401 | 402 | - (void)applicationWillResignActive:(NSNotification *)notificaiton 403 | { 404 | _panGestureRecognizer.enabled = NO; 405 | _panGestureRecognizer.enabled = YES; 406 | } 407 | 408 | @end 409 | -------------------------------------------------------------------------------- /TZImagePickerController/ScreenShots/DemoPage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/ScreenShots/DemoPage.png -------------------------------------------------------------------------------- /TZImagePickerController/ScreenShots/photoPickerVc.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/ScreenShots/photoPickerVc.PNG -------------------------------------------------------------------------------- /TZImagePickerController/ScreenShots/photoPreviewVc.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/ScreenShots/photoPreviewVc.PNG -------------------------------------------------------------------------------- /TZImagePickerController/ScreenShots/videoPlayerVc.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/ScreenShots/videoPlayerVc.PNG -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/NSBundle+TZImagePicker.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSBundle+TZImagePicker.h 3 | // TZImagePickerController 4 | // 5 | // Created by 谭真 on 16/08/18. 6 | // Copyright © 2016年 谭真. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSBundle (TZImagePicker) 12 | 13 | + (NSBundle *)tz_imagePickerBundle; 14 | 15 | + (NSString *)tz_localizedStringForKey:(NSString *)key value:(NSString *)value; 16 | + (NSString *)tz_localizedStringForKey:(NSString *)key; 17 | 18 | @end 19 | 20 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/NSBundle+TZImagePicker.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSBundle+TZImagePicker.m 3 | // TZImagePickerController 4 | // 5 | // Created by 谭真 on 16/08/18. 6 | // Copyright © 2016年 谭真. All rights reserved. 7 | // 8 | 9 | #import "NSBundle+TZImagePicker.h" 10 | #import "TZImagePickerController.h" 11 | 12 | @implementation NSBundle (TZImagePicker) 13 | 14 | + (NSBundle *)tz_imagePickerBundle { 15 | #ifdef SWIFT_PACKAGE 16 | NSBundle *bundle = SWIFTPM_MODULE_BUNDLE; 17 | #else 18 | NSBundle *bundle = [NSBundle bundleForClass:[TZImagePickerController class]]; 19 | #endif 20 | NSURL *url = [bundle URLForResource:@"TZImagePickerController" withExtension:@"bundle"]; 21 | bundle = [NSBundle bundleWithURL:url]; 22 | return bundle; 23 | } 24 | 25 | + (NSString *)tz_localizedStringForKey:(NSString *)key { 26 | return [self tz_localizedStringForKey:key value:@""]; 27 | } 28 | 29 | + (NSString *)tz_localizedStringForKey:(NSString *)key value:(NSString *)value { 30 | NSBundle *bundle = [TZImagePickerConfig sharedInstance].languageBundle; 31 | NSString *value1 = [bundle localizedStringForKey:key value:value table:nil]; 32 | return value1; 33 | } 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZAssetCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // TZAssetCell.h 3 | // TZImagePickerController 4 | // 5 | // Created by 谭真 on 15/12/24. 6 | // Copyright © 2015年 谭真. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | typedef enum : NSUInteger { 13 | TZAssetCellTypePhoto = 0, 14 | TZAssetCellTypeLivePhoto, 15 | TZAssetCellTypePhotoGif, 16 | TZAssetCellTypeVideo, 17 | TZAssetCellTypeAudio, 18 | } TZAssetCellType; 19 | 20 | @class TZAssetModel; 21 | @interface TZAssetCell : UICollectionViewCell 22 | @property (weak, nonatomic) UIButton *selectPhotoButton; 23 | @property (weak, nonatomic) UIButton *cannotSelectLayerButton; 24 | @property (nonatomic, strong) TZAssetModel *model; 25 | @property (assign, nonatomic) NSInteger index; 26 | @property (nonatomic, copy) void (^didSelectPhotoBlock)(BOOL); 27 | @property (nonatomic, assign) TZAssetCellType type; 28 | @property (nonatomic, assign) BOOL allowPickingGif; 29 | @property (nonatomic, assign) BOOL allowPickingMultipleVideo; 30 | @property (nonatomic, copy) NSString *representedAssetIdentifier; 31 | @property (nonatomic, assign) int32_t imageRequestID; 32 | 33 | @property (nonatomic, strong) UIImage *photoSelImage; 34 | @property (nonatomic, strong) UIImage *photoDefImage; 35 | 36 | @property (nonatomic, assign) BOOL showSelectBtn; 37 | @property (assign, nonatomic) BOOL allowPreview; 38 | 39 | @property (nonatomic, copy) void (^assetCellDidSetModelBlock)(TZAssetCell *cell, UIImageView *imageView, UIImageView *selectImageView, UILabel *indexLabel, UIView *bottomView, UILabel *timeLength, UIImageView *videoImgView); 40 | @property (nonatomic, copy) void (^assetCellDidLayoutSubviewsBlock)(TZAssetCell *cell, UIImageView *imageView, UIImageView *selectImageView, UILabel *indexLabel, UIView *bottomView, UILabel *timeLength, UIImageView *videoImgView); 41 | @end 42 | 43 | 44 | @class TZAlbumModel; 45 | @interface TZAlbumCell : UITableViewCell 46 | @property (nonatomic, strong) TZAlbumModel *model; 47 | @property (weak, nonatomic) UIButton *selectedCountButton; 48 | 49 | @property (nonatomic, copy) void (^albumCellDidSetModelBlock)(TZAlbumCell *cell, UIImageView *posterImageView, UILabel *titleLabel); 50 | @property (nonatomic, copy) void (^albumCellDidLayoutSubviewsBlock)(TZAlbumCell *cell, UIImageView *posterImageView, UILabel *titleLabel); 51 | @end 52 | 53 | 54 | @interface TZAssetCameraCell : UICollectionViewCell 55 | @property (nonatomic, strong) UIImageView *imageView; 56 | @end 57 | 58 | 59 | @interface TZAssetAddMoreCell : TZAssetCameraCell 60 | @property (nonatomic, strong) UILabel *tipLabel; 61 | @end 62 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZAssetModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // TZAssetModel.h 3 | // TZImagePickerController 4 | // 5 | // Created by 谭真 on 15/12/24. 6 | // Copyright © 2015年 谭真. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | typedef enum : NSUInteger { 14 | TZAssetModelMediaTypePhoto = 0, 15 | TZAssetModelMediaTypeLivePhoto, 16 | TZAssetModelMediaTypePhotoGif, 17 | TZAssetModelMediaTypeVideo, 18 | TZAssetModelMediaTypeAudio 19 | } TZAssetModelMediaType; 20 | 21 | @class PHAsset; 22 | @interface TZAssetModel : NSObject 23 | 24 | @property (nonatomic, strong) PHAsset *asset; 25 | @property (nonatomic, assign) BOOL isSelected; ///< The select status of a photo, default is No 26 | @property (nonatomic, assign) TZAssetModelMediaType type; 27 | @property (nonatomic, copy) NSString *timeLength; 28 | @property (nonatomic, assign) BOOL iCloudFailed; 29 | 30 | /// Init a photo dataModel With a PHAsset 31 | /// 用一个PHAsset实例,初始化一个照片模型 32 | + (instancetype)modelWithAsset:(PHAsset *)asset type:(TZAssetModelMediaType)type; 33 | + (instancetype)modelWithAsset:(PHAsset *)asset type:(TZAssetModelMediaType)type timeLength:(NSString *)timeLength; 34 | 35 | @end 36 | 37 | 38 | @class PHFetchResult; 39 | @interface TZAlbumModel : NSObject 40 | 41 | @property (nonatomic, strong) NSString *name; ///< The album name 42 | @property (nonatomic, assign) NSInteger count; ///< Count of photos the album contain 43 | @property (nonatomic, strong) PHFetchResult *result; 44 | @property (nonatomic, strong) PHAssetCollection *collection; 45 | @property (nonatomic, strong) PHFetchOptions *options; 46 | 47 | @property (nonatomic, strong) NSArray *models; 48 | @property (nonatomic, strong) NSArray *selectedModels; 49 | @property (nonatomic, assign) NSUInteger selectedCount; 50 | 51 | @property (nonatomic, assign) BOOL isCameraRoll; 52 | 53 | - (void)setResult:(PHFetchResult *)result needFetchAssets:(BOOL)needFetchAssets; 54 | - (void)refreshFetchResult; 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZAssetModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // TZAssetModel.m 3 | // TZImagePickerController 4 | // 5 | // Created by 谭真 on 15/12/24. 6 | // Copyright © 2015年 谭真. All rights reserved. 7 | // 8 | 9 | #import "TZAssetModel.h" 10 | #import "TZImageManager.h" 11 | 12 | @implementation TZAssetModel 13 | 14 | + (instancetype)modelWithAsset:(PHAsset *)asset type:(TZAssetModelMediaType)type{ 15 | TZAssetModel *model = [[TZAssetModel alloc] init]; 16 | model.asset = asset; 17 | model.isSelected = NO; 18 | model.type = type; 19 | return model; 20 | } 21 | 22 | + (instancetype)modelWithAsset:(PHAsset *)asset type:(TZAssetModelMediaType)type timeLength:(NSString *)timeLength { 23 | TZAssetModel *model = [self modelWithAsset:asset type:type]; 24 | model.timeLength = timeLength; 25 | return model; 26 | } 27 | 28 | @end 29 | 30 | 31 | 32 | @implementation TZAlbumModel 33 | 34 | - (void)setResult:(PHFetchResult *)result needFetchAssets:(BOOL)needFetchAssets { 35 | _result = result; 36 | if (needFetchAssets) { 37 | [[TZImageManager manager] getAssetsFromFetchResult:result completion:^(NSArray *models) { 38 | self->_models = models; 39 | if (self->_selectedModels) { 40 | [self checkSelectedModels]; 41 | } 42 | }]; 43 | } 44 | } 45 | 46 | - (void)refreshFetchResult { 47 | PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:self.collection options:self.options]; 48 | self.count = fetchResult.count; 49 | [self setResult:fetchResult]; 50 | } 51 | 52 | - (void)setSelectedModels:(NSArray *)selectedModels { 53 | _selectedModels = selectedModels; 54 | if (_models) { 55 | [self checkSelectedModels]; 56 | } 57 | } 58 | 59 | - (void)checkSelectedModels { 60 | self.selectedCount = 0; 61 | NSMutableSet *selectedAssets = [NSMutableSet setWithCapacity:_selectedModels.count]; 62 | for (TZAssetModel *model in _selectedModels) { 63 | [selectedAssets addObject:model.asset]; 64 | } 65 | for (TZAssetModel *model in _models) { 66 | if ([selectedAssets containsObject:model.asset]) { 67 | self.selectedCount ++; 68 | } 69 | } 70 | } 71 | 72 | - (NSString *)name { 73 | if (_name) { 74 | return _name; 75 | } 76 | return @""; 77 | } 78 | 79 | @end 80 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZAuthLimitedFooterTipView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TZAuthLimitedFooterTipView.h 3 | // TZImagePickerController 4 | // 5 | // Created by qiaoxy on 2021/8/24. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface TZAuthLimitedFooterTipView : UIView 13 | 14 | @end 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZAuthLimitedFooterTipView.m: -------------------------------------------------------------------------------- 1 | // 2 | // TZAuthLimitedFooterTipView.m 3 | // TZImagePickerController 4 | // 5 | // Created by qiaoxy on 2021/8/24. 6 | // 7 | 8 | #import "TZAuthLimitedFooterTipView.h" 9 | #import "TZImagePickerController.h" 10 | 11 | @interface TZAuthLimitedFooterTipView() 12 | @property (nonatomic,strong) UIImageView *tipImgView; 13 | @property (nonatomic,strong) UILabel *tipLable; 14 | @property (nonatomic,strong) UIImageView *detailImgView; 15 | @end 16 | 17 | @implementation TZAuthLimitedFooterTipView 18 | 19 | - (instancetype)init { 20 | self = [super init]; 21 | if (self) { 22 | [self initSubViews]; 23 | } 24 | return self; 25 | } 26 | 27 | - (instancetype)initWithFrame:(CGRect)frame { 28 | self = [super initWithFrame:frame]; 29 | if (self) { 30 | [self initSubViews]; 31 | } 32 | return self; 33 | } 34 | 35 | - (void)initSubViews { 36 | [self addSubview:self.tipImgView]; 37 | [self addSubview:self.tipLable]; 38 | [self addSubview:self.detailImgView]; 39 | CGFloat margin = 15; 40 | CGFloat tipImgViewWH = 20; 41 | CGFloat detailImgViewWH = 12; 42 | CGFloat screenW = [UIScreen mainScreen].bounds.size.width; 43 | 44 | self.tipImgView.frame = CGRectMake(margin, 0, tipImgViewWH, tipImgViewWH); 45 | self.detailImgView.frame = CGRectMake(screenW - margin - detailImgViewWH, 0, detailImgViewWH, detailImgViewWH); 46 | 47 | CGFloat tipLabelX = CGRectGetMaxX(self.tipImgView.frame) + 10; 48 | CGFloat tipLabelW = screenW - tipLabelX - detailImgViewWH - margin - 4; 49 | self.tipLable.frame = CGRectMake(tipLabelX, 0, tipLabelW, self.bounds.size.height); 50 | 51 | self.tipImgView.center = CGPointMake(self.tipImgView.center.x, self.tipLable.center.y); 52 | self.detailImgView.center = CGPointMake(self.detailImgView.center.x, self.tipLable.center.y); 53 | } 54 | 55 | #pragma mark - Getter 56 | 57 | - (UIImageView *)tipImgView { 58 | if (!_tipImgView) { 59 | _tipImgView = [[UIImageView alloc] init]; 60 | _tipImgView.contentMode = UIViewContentModeScaleAspectFit; 61 | _tipImgView.image = [UIImage tz_imageNamedFromMyBundle:@"tip"]; 62 | } 63 | return _tipImgView; 64 | } 65 | 66 | - (UILabel *)tipLable { 67 | if (!_tipLable) { 68 | _tipLable = [[UILabel alloc] init]; 69 | NSString *appName = [TZCommonTools tz_getAppName]; 70 | _tipLable.text = [NSString stringWithFormat:[NSBundle tz_localizedStringForKey:@"Allow %@ to access your all photos"], appName]; 71 | _tipLable.numberOfLines = 0; 72 | _tipLable.font = [UIFont systemFontOfSize:14]; 73 | _tipLable.textColor = [UIColor colorWithRed:0.40 green:0.40 blue:0.40 alpha:1.0]; 74 | } 75 | return _tipLable; 76 | } 77 | 78 | - (UIImageView *)detailImgView { 79 | if (!_detailImgView) { 80 | _detailImgView = [[UIImageView alloc] init]; 81 | _detailImgView.contentMode = UIViewContentModeScaleAspectFit; 82 | _detailImgView.image = [UIImage tz_imageNamedFromMyBundle:@"right_arrow"]; 83 | } 84 | return _detailImgView; 85 | } 86 | 87 | @end 88 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZGifPhotoPreviewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TZGifPhotoPreviewController.h 3 | // TZImagePickerController 4 | // 5 | // Created by ttouch on 2016/12/13. 6 | // Copyright © 2016年 谭真. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class TZAssetModel; 12 | @interface TZGifPhotoPreviewController : UIViewController 13 | 14 | @property (nonatomic, strong) TZAssetModel *model; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZGifPhotoPreviewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TZGifPhotoPreviewController.m 3 | // TZImagePickerController 4 | // 5 | // Created by ttouch on 2016/12/13. 6 | // Copyright © 2016年 谭真. All rights reserved. 7 | // 8 | 9 | #import "TZGifPhotoPreviewController.h" 10 | #import "TZImagePickerController.h" 11 | #import "TZAssetModel.h" 12 | #import "UIView+TZLayout.h" 13 | #import "TZPhotoPreviewCell.h" 14 | #import "TZImageManager.h" 15 | 16 | #pragma clang diagnostic push 17 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 18 | 19 | @interface TZGifPhotoPreviewController () { 20 | UIView *_toolBar; 21 | UIButton *_doneButton; 22 | UILabel *_byteLabel; 23 | UIProgressView *_progress; 24 | 25 | TZPhotoPreviewView *_previewView; 26 | 27 | UIStatusBarStyle _originStatusBarStyle; 28 | } 29 | @property (assign, nonatomic) BOOL needShowStatusBar; 30 | @end 31 | 32 | @implementation TZGifPhotoPreviewController 33 | 34 | - (void)viewDidLoad { 35 | [super viewDidLoad]; 36 | self.needShowStatusBar = ![UIApplication sharedApplication].statusBarHidden; 37 | self.view.backgroundColor = [UIColor blackColor]; 38 | TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; 39 | if (tzImagePickerVc) { 40 | self.navigationItem.title = [NSString stringWithFormat:@"GIF %@",tzImagePickerVc.previewBtnTitleStr]; 41 | } 42 | [self configPreviewView]; 43 | [self configBottomToolBar]; 44 | } 45 | 46 | - (void)viewWillAppear:(BOOL)animated { 47 | [super viewWillAppear:animated]; 48 | _originStatusBarStyle = [UIApplication sharedApplication].statusBarStyle; 49 | [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent; 50 | } 51 | 52 | - (void)viewWillDisappear:(BOOL)animated { 53 | [super viewWillDisappear:animated]; 54 | if (self.needShowStatusBar) { 55 | [UIApplication sharedApplication].statusBarHidden = NO; 56 | } 57 | [UIApplication sharedApplication].statusBarStyle = _originStatusBarStyle; 58 | } 59 | 60 | - (void)configPreviewView { 61 | _previewView = [[TZPhotoPreviewView alloc] initWithFrame:CGRectZero]; 62 | _previewView.model = self.model; 63 | __weak typeof(self) weakSelf = self; 64 | [_previewView setSingleTapGestureBlock:^{ 65 | __strong typeof(weakSelf) strongSelf = weakSelf; 66 | [strongSelf signleTapAction]; 67 | }]; 68 | [self.view addSubview:_previewView]; 69 | } 70 | 71 | - (void)configBottomToolBar { 72 | _toolBar = [[UIView alloc] initWithFrame:CGRectZero]; 73 | CGFloat rgb = 34 / 255.0; 74 | _toolBar.backgroundColor = [UIColor colorWithRed:rgb green:rgb blue:rgb alpha:0.7]; 75 | 76 | _doneButton = [UIButton buttonWithType:UIButtonTypeCustom]; 77 | _doneButton.titleLabel.font = [UIFont systemFontOfSize:16]; 78 | [_doneButton addTarget:self action:@selector(doneButtonClick) forControlEvents:UIControlEventTouchUpInside]; 79 | TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; 80 | if (tzImagePickerVc) { 81 | [_doneButton setTitle:tzImagePickerVc.doneBtnTitleStr forState:UIControlStateNormal]; 82 | [_doneButton setTitleColor:tzImagePickerVc.oKButtonTitleColorNormal forState:UIControlStateNormal]; 83 | } else { 84 | [_doneButton setTitle:[NSBundle tz_localizedStringForKey:@"Done"] forState:UIControlStateNormal]; 85 | [_doneButton setTitleColor:[UIColor colorWithRed:(83/255.0) green:(179/255.0) blue:(17/255.0) alpha:1.0] forState:UIControlStateNormal]; 86 | } 87 | [_toolBar addSubview:_doneButton]; 88 | 89 | _byteLabel = [[UILabel alloc] init]; 90 | _byteLabel.textColor = [UIColor whiteColor]; 91 | _byteLabel.font = [UIFont systemFontOfSize:13]; 92 | __weak typeof(_byteLabel) byteLabel = _byteLabel; 93 | [[TZImageManager manager] getPhotosBytesWithArray:@[_model] completion:^(NSString *totalBytes) { 94 | byteLabel.text = totalBytes; 95 | }]; 96 | [_toolBar addSubview:_byteLabel]; 97 | 98 | [self.view addSubview:_toolBar]; 99 | 100 | if (tzImagePickerVc.gifPreviewPageUIConfigBlock) { 101 | tzImagePickerVc.gifPreviewPageUIConfigBlock(_toolBar, _doneButton); 102 | } 103 | } 104 | 105 | - (UIStatusBarStyle)preferredStatusBarStyle { 106 | TZImagePickerController *tzImagePicker = (TZImagePickerController *)self.navigationController; 107 | if (tzImagePicker && [tzImagePicker isKindOfClass:[TZImagePickerController class]]) { 108 | return tzImagePicker.statusBarStyle; 109 | } 110 | return [super preferredStatusBarStyle]; 111 | } 112 | 113 | #pragma mark - Layout 114 | 115 | - (void)viewDidLayoutSubviews { 116 | [super viewDidLayoutSubviews]; 117 | 118 | _previewView.frame = self.view.bounds; 119 | _previewView.scrollView.frame = self.view.bounds; 120 | CGFloat toolBarHeight = 44 + [TZCommonTools tz_safeAreaInsets].bottom; 121 | _toolBar.frame = CGRectMake(0, self.view.tz_height - toolBarHeight, self.view.tz_width, toolBarHeight); 122 | [_doneButton sizeToFit]; 123 | 124 | if ([TZCommonTools tz_isRightToLeftLayout]) { 125 | _doneButton.frame = CGRectMake(12, 0, MAX(44, _doneButton.tz_width), 44); 126 | _byteLabel.frame = CGRectMake(self.view.tz_width - 100 - 10, 0, 100, 44); 127 | } else { 128 | _doneButton.frame = CGRectMake(self.view.tz_width - _doneButton.tz_width - 12, 0, MAX(44, _doneButton.tz_width), 44); 129 | _byteLabel.frame = CGRectMake(10, 0, 100, 44); 130 | } 131 | 132 | TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; 133 | if (tzImagePickerVc.gifPreviewPageDidLayoutSubviewsBlock) { 134 | tzImagePickerVc.gifPreviewPageDidLayoutSubviewsBlock(_toolBar, _doneButton); 135 | } 136 | } 137 | 138 | #pragma mark - Click Event 139 | 140 | - (void)signleTapAction { 141 | _toolBar.hidden = !_toolBar.isHidden; 142 | [self.navigationController setNavigationBarHidden:_toolBar.isHidden]; 143 | TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; 144 | if (_toolBar.isHidden) { 145 | [UIApplication sharedApplication].statusBarHidden = YES; 146 | } else if (tzImagePickerVc.needShowStatusBar) { 147 | [UIApplication sharedApplication].statusBarHidden = NO; 148 | } 149 | } 150 | 151 | - (void)doneButtonClick { 152 | if (self.navigationController) { 153 | TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController; 154 | if (imagePickerVc.autoDismiss) { 155 | [self.navigationController dismissViewControllerAnimated:YES completion:^{ 156 | [self callDelegateMethod]; 157 | }]; 158 | } else { 159 | [self callDelegateMethod]; 160 | } 161 | } else { 162 | [self dismissViewControllerAnimated:YES completion:^{ 163 | [self callDelegateMethod]; 164 | }]; 165 | } 166 | } 167 | 168 | - (void)callDelegateMethod { 169 | TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController; 170 | UIImage *animatedImage = _previewView.imageView.image; 171 | if ([imagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didFinishPickingGifImage:sourceAssets:)]) { 172 | [imagePickerVc.pickerDelegate imagePickerController:imagePickerVc didFinishPickingGifImage:animatedImage sourceAssets:_model.asset]; 173 | } 174 | if (imagePickerVc.didFinishPickingGifImageHandle) { 175 | imagePickerVc.didFinishPickingGifImageHandle(animatedImage,_model.asset); 176 | } 177 | } 178 | 179 | #pragma clang diagnostic pop 180 | 181 | @end 182 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImageCropManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // TZImageCropManager.h 3 | // TZImagePickerController 4 | // 5 | // Created by 谭真 on 2016/12/5. 6 | // Copyright © 2016年 谭真. All rights reserved. 7 | // 图片裁剪管理类 8 | 9 | #import 10 | #import 11 | 12 | @interface TZImageCropManager : NSObject 13 | 14 | /// 裁剪框背景的处理 15 | + (void)overlayClippingWithView:(UIView *)view cropRect:(CGRect)cropRect containerView:(UIView *)containerView needCircleCrop:(BOOL)needCircleCrop; 16 | 17 | /* 18 | 1.7.2 为了解决多位同学对于图片裁剪的需求,我这两天有空便在研究图片裁剪 19 | 幸好有tuyou的PhotoTweaks库做参考,裁剪的功能实现起来简单许多 20 | 该方法和其内部引用的方法基本来自于tuyou的PhotoTweaks库,我做了稍许删减和修改 21 | 感谢tuyou同学在github开源了优秀的裁剪库PhotoTweaks,表示感谢 22 | PhotoTweaks库的github链接:https://github.com/itouch2/PhotoTweaks 23 | */ 24 | /// 获得裁剪后的图片 25 | + (UIImage *)cropImageView:(UIImageView *)imageView toRect:(CGRect)rect zoomScale:(double)zoomScale containerView:(UIView *)containerView; 26 | 27 | /// 获取圆形图片 28 | + (UIImage *)circularClipImage:(UIImage *)image; 29 | 30 | @end 31 | 32 | 33 | /// 该分类的代码来自SDWebImage:https://github.com/rs/SDWebImage 34 | /// 为了防止冲突,我将分类名字和方法名字做了修改 35 | @interface UIImage (TZGif) 36 | 37 | + (UIImage *)sd_tz_animatedGIFWithData:(NSData *)data; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImageCropManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // TZImageCropManager.m 3 | // TZImagePickerController 4 | // 5 | // Created by 谭真 on 2016/12/5. 6 | // Copyright © 2016年 谭真. All rights reserved. 7 | // 8 | 9 | #import "TZImageCropManager.h" 10 | #import "UIView+TZLayout.h" 11 | #import 12 | #import "TZImageManager.h" 13 | #import "TZImagePickerController.h" 14 | 15 | @implementation TZImageCropManager 16 | 17 | /// 裁剪框背景的处理 18 | + (void)overlayClippingWithView:(UIView *)view cropRect:(CGRect)cropRect containerView:(UIView *)containerView needCircleCrop:(BOOL)needCircleCrop { 19 | UIBezierPath *path= [UIBezierPath bezierPathWithRect:[UIScreen mainScreen].bounds]; 20 | CAShapeLayer *layer = [CAShapeLayer layer]; 21 | if (needCircleCrop) { // 圆形裁剪框 22 | [path appendPath:[UIBezierPath bezierPathWithRoundedRect:cropRect cornerRadius:cropRect.size.width / 2]]; 23 | } else { // 矩形裁剪框 24 | [path appendPath:[UIBezierPath bezierPathWithRect:cropRect]]; 25 | } 26 | layer.path = path.CGPath; 27 | layer.fillRule = kCAFillRuleEvenOdd; 28 | layer.fillColor = [[UIColor blackColor] CGColor]; 29 | layer.opacity = 0.5; 30 | [view.layer addSublayer:layer]; 31 | } 32 | 33 | /// 获得裁剪后的图片 34 | + (UIImage *)cropImageView:(UIImageView *)imageView toRect:(CGRect)rect zoomScale:(double)zoomScale containerView:(UIView *)containerView { 35 | CGAffineTransform transform = CGAffineTransformIdentity; 36 | // 平移的处理 37 | CGRect imageViewRect = [imageView convertRect:imageView.bounds toView:containerView]; 38 | CGPoint point = CGPointMake(imageViewRect.origin.x + imageViewRect.size.width / 2, imageViewRect.origin.y + imageViewRect.size.height / 2); 39 | CGFloat xMargin = containerView.tz_width - CGRectGetMaxX(rect) - rect.origin.x; 40 | CGPoint zeroPoint = CGPointMake((CGRectGetWidth(containerView.frame) - xMargin) / 2, containerView.center.y); 41 | CGPoint translation = CGPointMake(point.x - zeroPoint.x, point.y - zeroPoint.y); 42 | transform = CGAffineTransformTranslate(transform, translation.x, translation.y); 43 | // 缩放的处理 44 | transform = CGAffineTransformScale(transform, zoomScale, zoomScale); 45 | 46 | CGImageRef imageRef = [self newTransformedImage:transform 47 | sourceImage:imageView.image.CGImage 48 | sourceSize:imageView.image.size 49 | outputWidth:rect.size.width * [UIScreen mainScreen].scale 50 | cropSize:rect.size 51 | imageViewSize:imageView.frame.size]; 52 | UIImage *cropedImage = [UIImage imageWithCGImage:imageRef]; 53 | cropedImage = [[TZImageManager manager] fixOrientation:cropedImage]; 54 | CGImageRelease(imageRef); 55 | return cropedImage; 56 | } 57 | 58 | + (CGImageRef)newTransformedImage:(CGAffineTransform)transform sourceImage:(CGImageRef)sourceImage sourceSize:(CGSize)sourceSize outputWidth:(CGFloat)outputWidth cropSize:(CGSize)cropSize imageViewSize:(CGSize)imageViewSize { 59 | CGImageRef source = [self newScaledImage:sourceImage toSize:sourceSize]; 60 | 61 | CGFloat aspect = cropSize.height/cropSize.width; 62 | CGSize outputSize = CGSizeMake(outputWidth, outputWidth*aspect); 63 | 64 | CGContextRef context = CGBitmapContextCreate(NULL, outputSize.width, outputSize.height, CGImageGetBitsPerComponent(source), 0, CGImageGetColorSpace(source), CGImageGetBitmapInfo(source)); 65 | CGContextSetFillColorWithColor(context, [[UIColor clearColor] CGColor]); 66 | CGContextFillRect(context, CGRectMake(0, 0, outputSize.width, outputSize.height)); 67 | 68 | CGAffineTransform uiCoords = CGAffineTransformMakeScale(outputSize.width / cropSize.width, outputSize.height / cropSize.height); 69 | uiCoords = CGAffineTransformTranslate(uiCoords, cropSize.width/2.0, cropSize.height / 2.0); 70 | uiCoords = CGAffineTransformScale(uiCoords, 1.0, -1.0); 71 | CGContextConcatCTM(context, uiCoords); 72 | 73 | CGContextConcatCTM(context, transform); 74 | CGContextScaleCTM(context, 1.0, -1.0); 75 | 76 | CGContextDrawImage(context, CGRectMake(-imageViewSize.width/2, -imageViewSize.height/2.0, imageViewSize.width, imageViewSize.height), source); 77 | CGImageRef resultRef = CGBitmapContextCreateImage(context); 78 | CGContextRelease(context); 79 | CGImageRelease(source); 80 | return resultRef; 81 | } 82 | 83 | + (CGImageRef)newScaledImage:(CGImageRef)source toSize:(CGSize)size { 84 | CGSize srcSize = size; 85 | CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); 86 | CGContextRef context = CGBitmapContextCreate(NULL, size.width, size.height, 8, 0, rgbColorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); 87 | CGColorSpaceRelease(rgbColorSpace); 88 | 89 | CGContextSetInterpolationQuality(context, kCGInterpolationNone); 90 | CGContextTranslateCTM(context, size.width/2, size.height/2); 91 | 92 | CGContextDrawImage(context, CGRectMake(-srcSize.width/2, -srcSize.height/2, srcSize.width, srcSize.height), source); 93 | 94 | CGImageRef resultRef = CGBitmapContextCreateImage(context); 95 | CGContextRelease(context); 96 | return resultRef; 97 | } 98 | 99 | /// 获取圆形图片 100 | + (UIImage *)circularClipImage:(UIImage *)image { 101 | UIGraphicsBeginImageContextWithOptions(image.size, NO, [UIScreen mainScreen].scale); 102 | 103 | CGContextRef ctx = UIGraphicsGetCurrentContext(); 104 | CGRect rect = CGRectMake(0, 0, image.size.width, image.size.height); 105 | CGContextAddEllipseInRect(ctx, rect); 106 | CGContextClip(ctx); 107 | 108 | [image drawInRect:rect]; 109 | UIImage *circleImage = UIGraphicsGetImageFromCurrentImageContext(); 110 | 111 | UIGraphicsEndImageContext(); 112 | return circleImage; 113 | } 114 | 115 | @end 116 | 117 | 118 | @implementation UIImage (TZGif) 119 | 120 | + (UIImage *)sd_tz_animatedGIFWithData:(NSData *)data { 121 | if (!data) { 122 | return nil; 123 | } 124 | 125 | CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL); 126 | 127 | size_t count = CGImageSourceGetCount(source); 128 | 129 | UIImage *animatedImage; 130 | 131 | if (count <= 1) { 132 | animatedImage = [[UIImage alloc] initWithData:data]; 133 | } 134 | else { 135 | // images数组过大时内存会飙升,在这里限制下最大count 136 | NSInteger maxCount = [TZImagePickerConfig sharedInstance].gifPreviewMaxImagesCount ?: 50; 137 | NSInteger interval = MAX((count + maxCount / 2) / maxCount, 1); 138 | 139 | NSMutableArray *images = [NSMutableArray array]; 140 | 141 | NSTimeInterval duration = 0.0f; 142 | 143 | for (size_t i = 0; i < count; i+=interval) { 144 | CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, NULL); 145 | if (!image) { 146 | continue; 147 | } 148 | 149 | duration += [self sd_frameDurationAtIndex:i source:source] * MIN(interval, 3); 150 | 151 | [images addObject:[UIImage imageWithCGImage:image scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp]]; 152 | 153 | CGImageRelease(image); 154 | } 155 | 156 | if (!duration) { 157 | duration = (1.0f / 10.0f) * count; 158 | } 159 | 160 | animatedImage = [UIImage animatedImageWithImages:images duration:duration]; 161 | } 162 | 163 | CFRelease(source); 164 | 165 | return animatedImage; 166 | } 167 | 168 | + (float)sd_frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source { 169 | float frameDuration = 0.1f; 170 | CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil); 171 | NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties; 172 | NSDictionary *gifProperties = frameProperties[(NSString *)kCGImagePropertyGIFDictionary]; 173 | 174 | NSNumber *delayTimeUnclampedProp = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime]; 175 | if (delayTimeUnclampedProp) { 176 | frameDuration = [delayTimeUnclampedProp floatValue]; 177 | } 178 | else { 179 | 180 | NSNumber *delayTimeProp = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime]; 181 | if (delayTimeProp) { 182 | frameDuration = [delayTimeProp floatValue]; 183 | } 184 | } 185 | 186 | // Many annoying ads specify a 0 duration to make an image flash as quickly as possible. 187 | // We follow Firefox's behavior and use a duration of 100 ms for any frames that specify 188 | // a duration of <= 10 ms. See and 189 | // for more information. 190 | 191 | if (frameDuration < 0.011f) { 192 | frameDuration = 0.100f; 193 | } 194 | 195 | CFRelease(cfFrameProperties); 196 | return frameDuration; 197 | } 198 | 199 | @end 200 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImageManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // TZImageManager.h 3 | // TZImagePickerController 4 | // 5 | // Created by 谭真 on 16/1/4. 6 | // Copyright © 2016年 谭真. All rights reserved. 7 | // 图片资源获取管理类 8 | 9 | #import 10 | #import 11 | #import 12 | #import 13 | #import "TZAssetModel.h" 14 | 15 | @class TZAlbumModel,TZAssetModel; 16 | @protocol TZImagePickerControllerDelegate; 17 | @interface TZImageManager : NSObject 18 | 19 | @property (nonatomic, strong) PHCachingImageManager *cachingImageManager; 20 | 21 | + (instancetype)manager NS_SWIFT_NAME(default()); 22 | + (void)deallocManager; 23 | 24 | @property (weak, nonatomic) id pickerDelegate; 25 | 26 | @property (nonatomic, assign) BOOL shouldFixOrientation; 27 | 28 | @property (nonatomic, assign) BOOL isPreviewNetworkImage; 29 | 30 | /// Default is 600px / 默认600像素宽 31 | @property (nonatomic, assign) CGFloat photoPreviewMaxWidth; 32 | /// The pixel width of output image, Default is 828px / 导出图片的宽度,默认828像素宽 33 | @property (nonatomic, assign) CGFloat photoWidth; 34 | 35 | /// Default is 4, Use in photos collectionView in TZPhotoPickerController 36 | /// 默认4列, TZPhotoPickerController中的照片collectionView 37 | @property (nonatomic, assign) NSInteger columnNumber; 38 | 39 | /// Sort photos ascending by modificationDate,Default is YES 40 | /// 对照片排序,按修改时间升序,默认是YES。如果设置为NO,最新的照片会显示在最前面,内部的拍照按钮会排在第一个 41 | @property (nonatomic, assign) BOOL sortAscendingByModificationDate; 42 | 43 | /// Minimum selectable photo width, Default is 0 44 | /// 最小可选中的图片宽度,默认是0,小于这个宽度的图片不可选中 45 | @property (nonatomic, assign) NSInteger minPhotoWidthSelectable; 46 | @property (nonatomic, assign) NSInteger minPhotoHeightSelectable; 47 | @property (nonatomic, assign) BOOL hideWhenCanNotSelect; 48 | 49 | /// Return YES if Authorized 返回YES如果得到了授权 50 | - (BOOL)authorizationStatusAuthorized; 51 | - (void)requestAuthorizationWithCompletion:(void (^)(void))completion; 52 | - (BOOL)isPHAuthorizationStatusLimited; 53 | 54 | /// Get Album 获得相册/相册数组 55 | - (void)getCameraRollAlbumWithFetchAssets:(BOOL)needFetchAssets completion:(void (^)(TZAlbumModel *model))completion; 56 | - (void)getCameraRollAlbum:(BOOL)allowPickingVideo allowPickingImage:(BOOL)allowPickingImage needFetchAssets:(BOOL)needFetchAssets completion:(void (^)(TZAlbumModel *model))completion __attribute__((deprecated("Use -getCameraRollAlbumWithFetchAssets:completion:. You can config allowPickingImage、allowPickingVideo by TZImagePickerConfig"))); 57 | - (void)getAllAlbums:(BOOL)allowPickingVideo allowPickingImage:(BOOL)allowPickingImage needFetchAssets:(BOOL)needFetchAssets completion:(void (^)(NSArray *models))completion __attribute__((deprecated("Use -getAllAlbumsWithFetchAssets:completion:. You can config allowPickingImage、allowPickingVideo by TZImagePickerConfig"))); 58 | - (void)getAllAlbumsWithFetchAssets:(BOOL)needFetchAssets completion:(void (^)(NSArray *models))completion; 59 | 60 | /// Get Assets 获得Asset数组 61 | - (void)getAssetsFromFetchResult:(PHFetchResult *)result completion:(void (^)(NSArray *models))completion; 62 | - (void)getAssetsFromFetchResult:(PHFetchResult *)result allowPickingVideo:(BOOL)allowPickingVideo allowPickingImage:(BOOL)allowPickingImage completion:(void (^)(NSArray *models))completion __attribute__((deprecated("Use -getAssetsFromFetchResult:completion:. You can config allowPickingImage、allowPickingVideo by TZImagePickerConfig"))); 63 | - (void)getAssetFromFetchResult:(PHFetchResult *)result atIndex:(NSInteger)index allowPickingVideo:(BOOL)allowPickingVideo allowPickingImage:(BOOL)allowPickingImage completion:(void (^)(TZAssetModel *model))completion __attribute__((deprecated("Use -getAssetFromFetchResult:atIndex:completion:. You can config allowPickingImage、allowPickingVideo by TZImagePickerConfig"))); 64 | - (void)getAssetFromFetchResult:(PHFetchResult *)result atIndex:(NSInteger)index completion:(void (^)(TZAssetModel *model))completion; 65 | 66 | /// Get photo 获得照片 67 | - (PHImageRequestID)getPostImageWithAlbumModel:(TZAlbumModel *)model completion:(void (^)(UIImage *postImage))completion; 68 | 69 | - (PHImageRequestID)getPhotoWithAsset:(PHAsset *)asset completion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion; 70 | - (PHImageRequestID)getPhotoWithAsset:(PHAsset *)asset photoWidth:(CGFloat)photoWidth completion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion; 71 | - (PHImageRequestID)getPhotoWithAsset:(PHAsset *)asset completion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler networkAccessAllowed:(BOOL)networkAccessAllowed; 72 | - (PHImageRequestID)getPhotoWithAsset:(PHAsset *)asset photoWidth:(CGFloat)photoWidth completion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler networkAccessAllowed:(BOOL)networkAccessAllowed; 73 | - (PHImageRequestID)requestImageDataForAsset:(PHAsset *)asset completion:(void (^)(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info))completion progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler; 74 | 75 | /// Get full Image 获取原图 76 | /// 如下两个方法completion一般会调多次,一般会先返回缩略图,再返回原图(详见方法内部使用的系统API的说明),如果info[PHImageResultIsDegradedKey] 为 YES,则表明当前返回的是缩略图,否则是原图。 77 | - (PHImageRequestID)getOriginalPhotoWithAsset:(PHAsset *)asset completion:(void (^)(UIImage *photo,NSDictionary *info))completion; 78 | - (PHImageRequestID)getOriginalPhotoWithAsset:(PHAsset *)asset newCompletion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion; 79 | - (PHImageRequestID)getOriginalPhotoWithAsset:(PHAsset *)asset progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler newCompletion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion; 80 | // 该方法中,completion只会走一次 81 | - (PHImageRequestID)getOriginalPhotoDataWithAsset:(PHAsset *)asset completion:(void (^)(NSData *data,NSDictionary *info,BOOL isDegraded))completion; 82 | - (PHImageRequestID)getOriginalPhotoDataWithAsset:(PHAsset *)asset progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler completion:(void (^)(NSData *data,NSDictionary *info,BOOL isDegraded))completion; 83 | 84 | /// Get Image For VideoURL 85 | - (UIImage *)getImageWithVideoURL:(NSURL *)videoURL; 86 | 87 | /// Save photo 保存照片 88 | - (void)savePhotoWithImage:(UIImage *)image completion:(void (^)(PHAsset *asset, NSError *error))completion; 89 | - (void)savePhotoWithImage:(UIImage *)image location:(CLLocation *)location completion:(void (^)(PHAsset *asset, NSError *error))completion; 90 | - (void)savePhotoWithImage:(UIImage *)image meta:(NSDictionary *)meta location:(CLLocation *)location completion:(void (^)(PHAsset *asset, NSError *error))completion; 91 | 92 | /// Save video 保存视频 93 | - (void)saveVideoWithUrl:(NSURL *)url completion:(void (^)(PHAsset *asset, NSError *error))completion; 94 | - (void)saveVideoWithUrl:(NSURL *)url location:(CLLocation *)location completion:(void (^)(PHAsset *asset, NSError *error))completion; 95 | 96 | /// Get video 获得视频 97 | - (void)getVideoWithAsset:(PHAsset *)asset completion:(void (^)(AVPlayerItem * playerItem, NSDictionary * info))completion; 98 | - (void)getVideoWithAsset:(PHAsset *)asset progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler completion:(void (^)(AVPlayerItem *, NSDictionary *))completion; 99 | 100 | /// Export video 导出视频 presetName: 预设名字,默认值是AVAssetExportPreset640x480 101 | - (void)getVideoOutputPathWithAsset:(PHAsset *)asset success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure; 102 | - (void)getVideoOutputPathWithAsset:(PHAsset *)asset presetName:(NSString *)presetName success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure; 103 | - (void)getVideoOutputPathWithAsset:(PHAsset *)asset presetName:(NSString *)presetName timeRange:(CMTimeRange)timeRange success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure; 104 | /// 新的导出视频API,解决iOS14 iCloud视频导出失败的问题,未大量测试,请大家多多测试,有问题群里反馈 105 | - (void)requestVideoOutputPathWithAsset:(PHAsset *)asset presetName:(NSString *)presetName success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure; 106 | /// 得到视频原始文件地址 107 | - (void)requestVideoURLWithAsset:(PHAsset *)asset success:(void (^)(NSURL *videoURL))success failure:(void (^)(NSDictionary* info))failure; 108 | 109 | /// Get photo bytes 获得一组照片的大小 110 | - (void)getPhotosBytesWithArray:(NSArray *)photos completion:(void (^)(NSString *totalBytes))completion; 111 | 112 | - (BOOL)isCameraRollAlbum:(PHAssetCollection *)metadata; 113 | 114 | /// 检查照片大小是否满足最小要求 115 | - (BOOL)isPhotoSelectableWithAsset:(PHAsset *)asset; 116 | 117 | /// 检查照片能否被选中 118 | - (BOOL)isAssetCannotBeSelected:(PHAsset *)asset; 119 | 120 | /// 修正图片转向 121 | - (UIImage *)fixOrientation:(UIImage *)aImage; 122 | 123 | /// 获取asset的资源类型 124 | - (TZAssetModelMediaType)getAssetType:(PHAsset *)asset; 125 | /// 缩放图片至新尺寸 126 | - (UIImage *)scaleImage:(UIImage *)image toSize:(CGSize)size; 127 | 128 | /// 判断asset是否是视频 129 | - (BOOL)isVideo:(PHAsset *)asset; 130 | 131 | /// for TZImagePreviewController 132 | - (NSString *)getNewTimeFromDurationSecond:(NSInteger)duration; 133 | 134 | - (TZAssetModel *)createModelWithAsset:(PHAsset *)asset; 135 | 136 | @end 137 | 138 | //@interface TZSortDescriptor : NSSortDescriptor 139 | // 140 | //@end 141 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/MMVideoPreviewPlay@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/MMVideoPreviewPlay@2x.png -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/MMVideoPreviewPlayHL@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/MMVideoPreviewPlayHL@2x.png -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/VideoSendIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/VideoSendIcon@2x.png -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/addMore@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/addMore@2x.png -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/ar.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "KEY" = "阿拉伯语"; 2 | "OK" = "حسنا"; 3 | "Back" = "الى الخلف"; 4 | "Done" = "فعله"; 5 | "Edit" = "تعديل"; 6 | "Sorry" = "آسف"; 7 | "Cancel" = "إلغاء"; 8 | "Setting" = "ضبط"; 9 | "Photos" = "الصور"; 10 | "Videos" = "أشرطة فيديو"; 11 | "Preview" = "معاينة"; 12 | "Full image" = "الصورة كاملة"; 13 | "Processing..." = "معالجة..."; 14 | "No Photos or Videos" = "لا توجد صور أو مقاطع فيديو"; 15 | "Synchronizing photos from iCloud" = "مزامنة الصور من iCloud"; 16 | "iCloud sync failed" = "iCloud فشلت المزامنة"; 17 | "Can not use camera" = "لا يمكن استخدام الكاميرا"; 18 | "Can not choose both video and photo" = "لا يمكن اختيار كل من الفيديو والصور"; 19 | "Can not choose both photo and GIF" = "لا يمكن اختيار كل من الصور و GIF"; 20 | "Select the video when in multi state, we will handle the video as a photo" = "حدد مقطع الفيديو عندما يكون في حالة متعددة، وسنعمل على معالجة مقطع الفيديو كصورة"; 21 | "Can not jump to the privacy settings page, please go to the settings page by self, thank you" = "إذا تعذّر الانتقال إلى صفحة \"إعدادات الخصوصية\"، فيرجى الانتقال إلى صفحة \"الإعدادات\" بنفسك، شكرًا لك"; 22 | "Select a maximum of %zd photos" = "حدد فقط ما يصل إلى %zd صورة"; 23 | "Select a minimum of %zd photos" = "الرجاء تحديد %zd صورة على الأقل"; 24 | "Allow %@ to access your album in \"Settings -> Privacy -> Photos\"" = "السماح لـ %@ بالوصول إلى الألبوم في \"الإعدادات > الخصوصية > الصور\""; 25 | "Please allow %@ to access your camera in \"Settings -> Privacy -> Camera\"" = "الرجاء السماح لـ %@ بالوصول إلى الكاميرا في \"الإعدادات > الخصوصية > الكاميرا\""; 26 | "Selected for %ld seconds" = "محدد لمدة %ld ثانية"; 27 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/de.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "KEY" = "德语"; 2 | "OK" = "OK"; 3 | "Back" = "Zurück"; 4 | "Done" = "Erledigt"; 5 | "Edit" = "Bearbeiten"; 6 | "Sorry" = "Es tut uns leid"; 7 | "Cancel" = "Stornieren"; 8 | "Setting" = "Rahmen"; 9 | "Photos" = "Fotos"; 10 | "Videos" = "Videos"; 11 | "Preview" = "Vorschau"; 12 | "Full image" = "Vollbild"; 13 | "Processing..." = "Wird bearbeitet..."; 14 | "No Photos or Videos" = "Keine Fotos oder Videos"; 15 | "Synchronizing photos from iCloud" = "Fotos aus iCloud synchronisieren"; 16 | "iCloud sync failed" = "iCloud Synchronisierung fehlgeschlagen"; 17 | "Can not use camera" = "Kann die Kamera nicht benutzen"; 18 | "Can not choose both video and photo" = "Video und Foto können nicht ausgewählt werden"; 19 | "Can not choose both photo and GIF" = "Foto und GIF können nicht ausgewählt werden"; 20 | "Select the video when in multi state, we will handle the video as a photo" = "Wenn Sie das Video im Multi-Status auswählen, wird es als Foto behandelt"; 21 | "Can not jump to the privacy settings page, please go to the settings page by self, thank you" = "Sie können nicht zur Seite mit den Datenschutz-Einstellungen springen; bitte navigieren Sie selbst zur Einstellungsseite. Vielen Dank."; 22 | "Select a maximum of %zd photos" = "Wählen Sie maximal %zd Bilder aus"; 23 | "Select a minimum of %zd photos" = "Bitte wählen Sie mindestens %zd Fotos aus"; 24 | "Allow %@ to access your album in \"Settings -> Privacy -> Photos\"" = "Erlauben Sie %@ den Zugriff auf Ihr Album unter: „Einstellungen > Datenschutz > Fotos“"; 25 | "Please allow %@ to access your camera in \"Settings -> Privacy -> Camera\"" = "Erlauben Sie %@ den Zugriff auf Ihre Kamera unter: „Einstellungen > Datenschutz > Kamera“"; 26 | "Selected for %ld seconds" = "Ausgewählt für %ld Sekunden"; 27 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/en.lproj/Localizable.strings -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/es.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "KEY" = "西班牙语"; 2 | "OK" = "DE ACUERDO"; 3 | "Back" = "Espalda"; 4 | "Done" = "Hecho"; 5 | "Edit" = "επεξεργασία"; 6 | "Sorry" = "Lo siento"; 7 | "Cancel" = "Cancelar"; 8 | "Setting" = "Ajuste"; 9 | "Photos" = "Las fotos"; 10 | "Videos" = "Videos"; 11 | "Preview" = "Avance"; 12 | "Full image" = "Imagen completa"; 13 | "Processing..." = "Tratamiento..."; 14 | "No Photos or Videos" = "No hay fotos o videos"; 15 | "Synchronizing photos from iCloud" = "Sincronizando fotos desde iCloud"; 16 | "iCloud sync failed" = "la sincronización falló"; 17 | "Can not use camera" = "No puedo usar la camara"; 18 | "Can not choose both video and photo" = "No se puede elegir tanto el video como la foto."; 19 | "Can not choose both photo and GIF" = "No se puede elegir tanto foto como GIF"; 20 | "Select the video when in multi state, we will handle the video as a photo" = "Seleccione el vídeo en estado múltiple, trataremos el vídeo como una fotografía"; 21 | "Can not jump to the privacy settings page, please go to the settings page by self, thank you" = "No se puede saltar a la página de ajustes de privacidad, vaya a la página de ajustes manualmente, muchas gracias"; 22 | "Select a maximum of %zd photos" = "Seleccione solamente hasta %zd imágenes"; 23 | "Select a minimum of %zd photos" = "Seleccione al menos %zd fotografías"; 24 | "Allow %@ to access your album in \"Settings -> Privacy -> Photos\"" = "Permita que %@ acceda a su galería en \"Ajustes > Privacidad > Fotografías\""; 25 | "Please allow %@ to access your camera in \"Settings -> Privacy -> Camera\"" = "Permita que %@ acceda a su cámara en \"Ajustes > Privacidad > Cámara\""; 26 | "Selected for %ld seconds" = "Seleccionado para %ld segundos"; 27 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/fr.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "KEY" = "法语"; 2 | "OK" = "D'accord"; 3 | "Back" = "Retour"; 4 | "Done" = "Terminé"; 5 | "Edit" = "Éditer"; 6 | "Sorry" = "Pardon"; 7 | "Cancel" = "Annuler"; 8 | "Setting" = "Réglage"; 9 | "Photos" = "Photos"; 10 | "Videos" = "Vidéos"; 11 | "Preview" = "Aperçu"; 12 | "Full image" = "Image complète"; 13 | "Processing..." = "En traitement..."; 14 | "No Photos or Videos" = "Aucune photo ou vidéo"; 15 | "Synchronizing photos from iCloud" = "Synchroniser des photos depuis iCloud"; 16 | "iCloud sync failed" = "iCloud échec de la synchronisation"; 17 | "Can not use camera" = "Impossible d'utiliser la caméra"; 18 | "Can not choose both video and photo" = "Impossible de choisir à la fois la vidéo et la photo"; 19 | "Can not choose both photo and GIF" = "Impossible de choisir à la fois photo et GIF"; 20 | "Select the video when in multi state, we will handle the video as a photo" = "Sélectionnez la vidéo lorsqu’elle est en état multiple, nous la traiterons comme une photo"; 21 | "Can not jump to the privacy settings page, please go to the settings page by self, thank you" = "Impossible d'ouvrir la page des paramètres de confidentialité, veuillez accéder vous-même à la page des paramètres, merci"; 22 | "Select a maximum of %zd photos" = "Vous pouvez uniquement sélectionner un maximum de %zd images"; 23 | "Select a minimum of %zd photos" = "Veuillez sélectionner un minimum de %zd photos"; 24 | "Allow %@ to access your album in \"Settings -> Privacy -> Photos\"" = "Autorisez %@ à accéder à votre album dans « Paramètres > Confidentialité > Photos »"; 25 | "Please allow %@ to access your camera in \"Settings -> Privacy -> Camera\"" = "Autorisez %@ à accéder à votre appareil photo dans « Paramètres > Confidentialité > Appareil photo »"; 26 | "Selected for %ld seconds" = "Sélectionné pendant %ld secondes"; 27 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/iCloudError@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/iCloudError@2x.png -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/ja.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "KEY" = "日语"; 2 | "OK" = "OK"; 3 | "Back" = "バック"; 4 | "Done" = "完了"; 5 | "Edit" = "編集する"; 6 | "Sorry" = "ごめんなさい"; 7 | "Cancel" = "キャンセル"; 8 | "Setting" = "設定"; 9 | "Photos" = "写真"; 10 | "Videos" = "動画"; 11 | "Preview" = "プレビュー"; 12 | "Full image" = "フルイメージ"; 13 | "Processing..." = "処理..."; 14 | "No Photos or Videos" = "写真やビデオはありません"; 15 | "Synchronizing photos from iCloud" = "iCloudから写真を同期する"; 16 | "iCloud sync failed" = "iCloud同期に失敗しました"; 17 | "Can not use camera" = "カメラが使えない"; 18 | "Can not choose both video and photo" = "ビデオと写真の両方を選択することはできません"; 19 | "Can not choose both photo and GIF" = "写真とGIFの両方を選択することはできません"; 20 | "Select the video when in multi state, we will handle the video as a photo" = "多肢選択の状態で、ビデオを選択すると、ビデオをデフォルトに画像として送信します。"; 21 | "Can not jump to the privacy settings page, please go to the settings page by self, thank you" = "プライバシー設定画面にジャンプできません。手動で設定画面を表示してください。"; 22 | "Select a maximum of %zd photos" = "写真は多くとも%zd 枚選択できます。"; 23 | "Select a minimum of %zd photos" = "少なくとも %zd 枚の写真を選択してください。"; 24 | "Allow %@ to access your album in \"Settings -> Privacy -> Photos\"" = "iPhoneの「設定-プライバシー-写真」のオプションで、r%@の携帯電話のアルバムへのアクセス権限を許可してください。"; 25 | "Please allow %@ to access your camera in \"Settings -> Privacy -> Camera\"" = "iPhoneの「設定-プライバシー-カメラ」で、%@のカメラへのアクセス権限を許可してください。"; 26 | "Selected for %ld seconds" = "%ld 秒間選択されました"; 27 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/ko-KP.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "KEY" = "朝鲜语"; 2 | "OK" = "그래"; 3 | "Back" = "뒤로"; 4 | "Done" = "완료"; 5 | "Edit" = "편집하다"; 6 | "Sorry" = "미안해요"; 7 | "Cancel" = "취소"; 8 | "Setting" = "설정"; 9 | "Photos" = "사진"; 10 | "Videos" = "동영상"; 11 | "Preview" = "미리 보기"; 12 | "Full image" = "전체 이미지"; 13 | "Processing..." = "처리..."; 14 | "No Photos or Videos" = "아무 사진이 나 동영상"; 15 | "Synchronizing photos from iCloud" = "ICloud에서 사진을 동기화"; 16 | "iCloud sync failed" = "iCloud동기화 실패"; 17 | "Can not use camera" = "카메라를 사용할 수 없습니다."; 18 | "Can not choose both video and photo" = "비디오와 사진 둘 다를 선택할 수 없습니다."; 19 | "Can not choose both photo and GIF" = "사진 및 GIF를 선택할 수 없습니다."; 20 | "Select the video when in multi state, we will handle the video as a photo" = "다중 선택 모드에서 비디오를 선택하면 비디오를 사진으로 처리합니다."; 21 | "Can not jump to the privacy settings page, please go to the settings page by self, thank you" = "개인 정보 보호 설정 페이지로 바로 이동할 수 없습니다. 설정 페이지로 직접 이동해 주세요. 감사합니다."; 22 | "Select a maximum of %zd photos" = "최대 %zd장의 이미지만 선택할 수 있습니다."; 23 | "Select a minimum of %zd photos" = "최소 %zd장의 사진을 선택해 주세요."; 24 | "Allow %@ to access your album in \"Settings -> Privacy -> Photos\"" = "\"설정 > 개인 정보 보호 > 사진\"에서 %@이(가) 앨범에 접근할 수 있도록 허용하세요."; 25 | "Please allow %@ to access your camera in \"Settings -> Privacy -> Camera\"" = "\"설정 > 개인 정보 보호 > 카메라\"에서 %@이(가) 카메라에 접근할 수 있도록 허용하세요."; 26 | "Selected for %ld seconds" = "%ld 초 동안 선택됨"; 27 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/navi_back@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/navi_back@2x.png -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/photo_def_photoPickerVc@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/photo_def_photoPickerVc@2x.png -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/photo_def_previewVc@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/photo_def_previewVc@2x.png -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/photo_number_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/photo_number_icon@2x.png -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/photo_original_def@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/photo_original_def@2x.png -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/photo_original_sel@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/photo_original_sel@2x.png -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/photo_sel_photoPickerVc@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/photo_sel_photoPickerVc@2x.png -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/photo_sel_previewVc@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/photo_sel_previewVc@2x.png -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/preview_number_icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/preview_number_icon@2x.png -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/preview_original_def@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/preview_original_def@2x.png -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/pt.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "KEY" = "葡萄牙语"; 2 | "OK" = "Está bem"; 3 | "Back" = "De volta"; 4 | "Done" = "Feito"; 5 | "Edit" = "editar"; 6 | "Sorry" = "Desculpa"; 7 | "Cancel" = "Cancelar"; 8 | "Setting" = "Configuração"; 9 | "Photos" = "Fotos"; 10 | "Videos" = "Vídeos"; 11 | "Preview" = "Visualizar"; 12 | "Full image" = "Imagem Completa"; 13 | "Processing..." = "Em processamento..."; 14 | "No Photos or Videos" = "Sem fotos ou vídeos"; 15 | "Synchronizing photos from iCloud" = "Sincronizando fotos do iCloud"; 16 | "iCloud sync failed" = "iCloud falha na sincronização"; 17 | "Can not use camera" = "Não pode usar a câmera"; 18 | "Can not choose both video and photo" = "Não é possível escolher vídeo e foto"; 19 | "Can not choose both photo and GIF" = "Não é possível escolher foto e GIF"; 20 | "Select the video when in multi state, we will handle the video as a photo" = "Se estiver em estado múltiplo, selecione a opção vídeo; iremos utilizar o vídeo como uma foto"; 21 | "Can not jump to the privacy settings page, please go to the settings page by self, thank you" = "Não é possível avançar para a página de definições de privacidade, aceda à página de definições você mesmo, obrigado"; 22 | "Select a maximum of %zd photos" = "Selecione apenas %zd imagens,no máximo"; 23 | "Select a minimum of %zd photos" = "Selecione %zd fotos,no mínimo"; 24 | "Allow %@ to access your album in \"Settings -> Privacy -> Photos\"" = "Permita a %@ aceder ao seu álbum em “Definições > Privacidade > Fotos”"; 25 | "Please allow %@ to access your camera in \"Settings -> Privacy -> Camera\"" = "Permita a %@ aceder à sua câmara em “Definições > Privacidade > Câmara”"; 26 | "Selected for %ld seconds" = "Selecionado por %ld segundos"; 27 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/right_arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/right_arrow@2x.png -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/ru.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "KEY" = "俄语"; 2 | "OK" = "Хорошо"; 3 | "Back" = "назад"; 4 | "Done" = "Готово"; 5 | "Edit" = "редактировать"; 6 | "Sorry" = "сожалею"; 7 | "Cancel" = "отменить"; 8 | "Setting" = "настройка"; 9 | "Photos" = "Фото"; 10 | "Videos" = "Видео"; 11 | "Preview" = "предварительный просмотр"; 12 | "Full image" = "Полное изображение"; 13 | "Processing..." = "Обработка ..."; 14 | "No Photos or Videos" = "Нет фото или видео"; 15 | "Synchronizing photos from iCloud" = "Синхронизация фотографий из iCloud"; 16 | "iCloud sync failed" = "iCloud сбой синхронизации"; 17 | "Can not use camera" = "Не могу использовать камеру"; 18 | "Can not choose both video and photo" = "Не могу выбрать как видео,так и фото"; 19 | "Can not choose both photo and GIF" = "Не могу выбрать фото и GIF"; 20 | "Select the video when in multi state, we will handle the video as a photo" = "В случае выбора видео при нахождении в мультирежиме видео будет обработано как фотография"; 21 | "Can not jump to the privacy settings page, please go to the settings page by self, thank you" = "Не удается перейти на страницу настроек конфиденциальности. Перейдите на эту страницу самостоятельно"; 22 | "Select a maximum of %zd photos" = "Вы можете выбрать до %zd изображений"; 23 | "Select a minimum of %zd photos" = "Вы можете выбрать не менее %zd изображений"; 24 | "Allow %@ to access your album in \"Settings -> Privacy -> Photos\"" = "Разрешите доступ %@ к вашему альбому,перейдя в Настройки > Конфиденциальность > Фото"; 25 | "Please allow %@ to access your camera in \"Settings -> Privacy -> Camera\"" = "Разрешите доступ %@ к камере вашего устройства,перейдя в Настройки > Конфиденциальность > Камера"; 26 | "Selected for %ld seconds" = "Выбрано для %ld секунд"; 27 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/takePicture80@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/takePicture80@2x.png -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/takePicture@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/takePicture@2x.png -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/tip@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/tip@2x.png -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/vi.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "KEY" = "越南语"; 2 | "OK" = "Xác nhận"; 3 | "Back" = "Quay lại"; 4 | "Done" = "Hoàn thành"; 5 | "Edit" = "biên tập"; 6 | "Sorry" = "Xin lỗi"; 7 | "Cancel" = "Hủy"; 8 | "Setting" = "Cài đặt"; 9 | "Photos" = "Hình"; 10 | "Videos" = "Clip"; 11 | "Preview" = "Xem trước"; 12 | "Full image" = "Hình gốc"; 13 | "Processing..." = "Đang xử lý..."; 14 | "No Photos or Videos" = "Không có ảnh hoặc video"; 15 | "Can not use camera" = "Máy chụp hình không khả dụng"; 16 | "Synchronizing photos from iCloud" = "Đang đồng bộ hình ảnh từ ICloud"; 17 | "iCloud sync failed" = "iCloud đồng bộ hóa không thành công"; 18 | "Can not choose both video and photo" = "Trong lúc chọn hình ảnh không cùng lúc chọn video"; 19 | "Can not choose both photo and GIF" = "Trong lúc chọn hình ảnh không cùng lúc chọn hình GIF"; 20 | "Select the video when in multi state, we will handle the video as a photo" = "Chọn hình ảnh cùng video, video sẽ bị mặc nhận thành hình ảnh và gửi đi."; 21 | "Can not jump to the privacy settings page, please go to the settings page by self, thank you" = "Không thể chuyển tự động qua trang cài đặt riêng tư, bạn hãy thoát ra cà điều chỉnh lại, cám ơn bạn."; 22 | "Select a maximum of %zd photos" = "Bạn chỉ được chọn nhiều nhất %zd tấm hình"; 23 | "Select a minimum of %zd photos" = "Chọn ít nhất %zd tấm hình"; 24 | "Allow %@ to access your album in \"Settings -> Privacy -> Photos\"" = "Vui lòng tại mục iPhone \" Cài đặt – quyền riêng tư - Ảnh\" mở quyền cho phép %@ truy cập ảnh."; 25 | "Please allow %@ to access your camera in \"Settings -> Privacy -> Camera\"" = "Vui lòng tại mục iPhone \" Cài đặt – quyền riêng tư - Ảnh\" mở quyền cho phép %@ truy cập máy ảnh"; 26 | "Selected for %ld seconds" = "Đã chọn cho %ld giây"; 27 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/zh-Hans.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/zh-Hans.lproj/Localizable.strings -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/zh-Hant.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle/zh-Hant.lproj/Localizable.strings -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImageRequestOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // TZImageRequestOperation.h 3 | // TZImagePickerControllerFramework 4 | // 5 | // Created by 谭真 on 2018/12/20. 6 | // Copyright © 2018 谭真. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface TZImageRequestOperation : NSOperation 15 | 16 | typedef void(^TZImageRequestCompletedBlock)(UIImage *photo, NSDictionary *info, BOOL isDegraded); 17 | typedef void(^TZImageRequestProgressBlock)(double progress, NSError *error, BOOL *stop, NSDictionary *info); 18 | 19 | @property (nonatomic, copy, nullable) TZImageRequestCompletedBlock completedBlock; 20 | @property (nonatomic, copy, nullable) TZImageRequestProgressBlock progressBlock; 21 | @property (nonatomic, strong, nullable) PHAsset *asset; 22 | 23 | @property (assign, nonatomic, getter = isExecuting) BOOL executing; 24 | @property (assign, nonatomic, getter = isFinished) BOOL finished; 25 | 26 | - (instancetype)initWithAsset:(PHAsset *)asset completion:(TZImageRequestCompletedBlock)completionBlock progressHandler:(TZImageRequestProgressBlock)progressHandler; 27 | - (void)done; 28 | @end 29 | 30 | NS_ASSUME_NONNULL_END 31 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZImageRequestOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // TZImageRequestOperation.m 3 | // TZImagePickerControllerFramework 4 | // 5 | // Created by 谭真 on 2018/12/20. 6 | // Copyright © 2018 谭真. All rights reserved. 7 | // 8 | 9 | #import "TZImageRequestOperation.h" 10 | #import "TZImageManager.h" 11 | 12 | @implementation TZImageRequestOperation 13 | 14 | @synthesize executing = _executing; 15 | @synthesize finished = _finished; 16 | 17 | - (instancetype)initWithAsset:(PHAsset *)asset completion:(TZImageRequestCompletedBlock)completionBlock progressHandler:(TZImageRequestProgressBlock)progressHandler { 18 | self = [super init]; 19 | self.asset = asset; 20 | self.completedBlock = completionBlock; 21 | self.progressBlock = progressHandler; 22 | _executing = NO; 23 | _finished = NO; 24 | return self; 25 | } 26 | 27 | - (void)start { 28 | self.executing = YES; 29 | [[TZImageManager manager] getPhotoWithAsset:self.asset completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) { 30 | dispatch_async(dispatch_get_main_queue(), ^{ 31 | if (!isDegraded) { 32 | if (self.completedBlock) { 33 | self.completedBlock(photo, info, isDegraded); 34 | } 35 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 36 | [self done]; 37 | }); 38 | } 39 | }); 40 | } progressHandler:^(double progress, NSError *error, BOOL *stop, NSDictionary *info) { 41 | dispatch_async(dispatch_get_main_queue(), ^{ 42 | if (self.progressBlock) { 43 | self.progressBlock(progress, error, stop, info); 44 | } 45 | }); 46 | } networkAccessAllowed:YES]; 47 | } 48 | 49 | - (void)done { 50 | self.finished = YES; 51 | self.executing = NO; 52 | [self reset]; 53 | } 54 | 55 | - (void)reset { 56 | self.asset = nil; 57 | self.completedBlock = nil; 58 | self.progressBlock = nil; 59 | } 60 | 61 | - (void)setFinished:(BOOL)finished { 62 | [self willChangeValueForKey:@"isFinished"]; 63 | _finished = finished; 64 | [self didChangeValueForKey:@"isFinished"]; 65 | } 66 | 67 | - (void)setExecuting:(BOOL)executing { 68 | [self willChangeValueForKey:@"isExecuting"]; 69 | _executing = executing; 70 | [self didChangeValueForKey:@"isExecuting"]; 71 | } 72 | 73 | - (BOOL)isAsynchronous { 74 | return YES; 75 | } 76 | 77 | @end 78 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZPhotoPickerController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TZPhotoPickerController.h 3 | // TZImagePickerController 4 | // 5 | // Created by 谭真 on 15/12/24. 6 | // Copyright © 2015年 谭真. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class TZAlbumModel; 12 | @interface TZPhotoPickerController : UIViewController 13 | 14 | @property (nonatomic, assign) BOOL isFirstAppear; 15 | @property (nonatomic, assign) NSInteger columnNumber; 16 | @property (nonatomic, strong) TZAlbumModel *model; 17 | @end 18 | 19 | 20 | @interface TZCollectionView : UICollectionView 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZPhotoPreviewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // TZPhotoPreviewCell.h 3 | // TZImagePickerController 4 | // 5 | // Created by 谭真 on 15/12/24. 6 | // Copyright © 2015年 谭真. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class TZAssetModel; 12 | @interface TZAssetPreviewCell : UICollectionViewCell 13 | @property (nonatomic, strong) TZAssetModel *model; 14 | @property (nonatomic, copy) void (^singleTapGestureBlock)(void); 15 | - (void)configSubviews; 16 | - (void)photoPreviewCollectionViewDidScroll; 17 | @end 18 | 19 | 20 | @class TZAssetModel,TZProgressView,TZPhotoPreviewView; 21 | @interface TZPhotoPreviewCell : TZAssetPreviewCell 22 | 23 | @property (nonatomic, copy) void (^imageProgressUpdateBlock)(double progress); 24 | 25 | @property (nonatomic, strong) TZPhotoPreviewView *previewView; 26 | 27 | @property (nonatomic, assign) BOOL allowCrop; 28 | @property (nonatomic, assign) CGRect cropRect; 29 | @property (nonatomic, assign) BOOL scaleAspectFillCrop; 30 | 31 | - (void)recoverSubviews; 32 | 33 | @end 34 | 35 | 36 | @interface TZPhotoPreviewView : UIView 37 | @property (nonatomic, strong) UIImageView *imageView; 38 | @property (nonatomic, strong) UIScrollView *scrollView; 39 | @property (nonatomic, strong) UIView *imageContainerView; 40 | @property (nonatomic, strong) TZProgressView *progressView; 41 | @property (nonatomic, strong) UIImageView *iCloudErrorIcon; 42 | @property (nonatomic, strong) UILabel *iCloudErrorLabel; 43 | @property (nonatomic, copy) void (^iCloudSyncFailedHandle)(id asset, BOOL isSyncFailed); 44 | 45 | 46 | @property (nonatomic, assign) BOOL allowCrop; 47 | @property (nonatomic, assign) CGRect cropRect; 48 | @property (nonatomic, assign) BOOL scaleAspectFillCrop; 49 | @property (nonatomic, strong) TZAssetModel *model; 50 | @property (nonatomic, strong) id asset; 51 | @property (nonatomic, copy) void (^singleTapGestureBlock)(void); 52 | @property (nonatomic, copy) void (^imageProgressUpdateBlock)(double progress); 53 | 54 | @property (nonatomic, assign) int32_t imageRequestID; 55 | 56 | - (void)recoverSubviews; 57 | @end 58 | 59 | 60 | @class AVPlayer, AVPlayerLayer; 61 | @interface TZVideoPreviewCell : TZAssetPreviewCell 62 | @property (strong, nonatomic) AVPlayer *player; 63 | @property (strong, nonatomic) AVPlayerLayer *playerLayer; 64 | @property (strong, nonatomic) UIButton *playButton; 65 | @property (strong, nonatomic) UIImage *cover; 66 | @property (nonatomic, strong) NSURL *videoURL; 67 | @property (nonatomic, strong) UIImageView *iCloudErrorIcon; 68 | @property (nonatomic, strong) UILabel *iCloudErrorLabel; 69 | @property (nonatomic, copy) void (^iCloudSyncFailedHandle)(id asset, BOOL isSyncFailed); 70 | - (void)pausePlayerAndShowNaviBar; 71 | @end 72 | 73 | 74 | @interface TZGifPreviewCell : TZAssetPreviewCell 75 | @property (strong, nonatomic) TZPhotoPreviewView *previewView; 76 | @end 77 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZPhotoPreviewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TZPhotoPreviewController.h 3 | // TZImagePickerController 4 | // 5 | // Created by 谭真 on 15/12/24. 6 | // Copyright © 2015年 谭真. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TZPhotoPreviewController : UIViewController 12 | 13 | @property (nonatomic, strong) NSMutableArray *models; ///< All photo models / 所有图片模型数组 14 | @property (nonatomic, strong) NSMutableArray *photos; ///< All photos / 所有图片数组 15 | @property (nonatomic, assign) NSInteger currentIndex; ///< Index of the photo user click / 用户点击的图片的索引 16 | @property (nonatomic, assign) BOOL isSelectOriginalPhoto; ///< If YES,return original photo / 是否返回原图 17 | @property (nonatomic, assign) BOOL isCropImage; 18 | 19 | /// Return the new selected photos / 返回最新的选中图片数组 20 | @property (nonatomic, copy) void (^backButtonClickBlock)(BOOL isSelectOriginalPhoto); 21 | @property (nonatomic, copy) void (^doneButtonClickBlock)(BOOL isSelectOriginalPhoto); 22 | @property (nonatomic, copy) void (^doneButtonClickBlockCropMode)(UIImage *cropedImage,id asset); 23 | @property (nonatomic, copy) void (^doneButtonClickBlockWithPreviewType)(NSArray *photos,NSArray *assets,BOOL isSelectOriginalPhoto); 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZProgressView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TZProgressView.h 3 | // TZImagePickerController 4 | // 5 | // Created by ttouch on 2016/12/6. 6 | // Copyright © 2016年 谭真. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TZProgressView : UIView 12 | 13 | @property (nonatomic, assign) double progress; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZProgressView.m: -------------------------------------------------------------------------------- 1 | // 2 | // TZProgressView.m 3 | // TZImagePickerController 4 | // 5 | // Created by ttouch on 2016/12/6. 6 | // Copyright © 2016年 谭真. All rights reserved. 7 | // 8 | 9 | #import "TZProgressView.h" 10 | 11 | @interface TZProgressView () 12 | @property (nonatomic, strong) CAShapeLayer *progressLayer; 13 | @end 14 | 15 | @implementation TZProgressView 16 | 17 | - (instancetype)init { 18 | self = [super init]; 19 | if (self) { 20 | self.backgroundColor = [UIColor clearColor]; 21 | 22 | _progressLayer = [CAShapeLayer layer]; 23 | _progressLayer.fillColor = [[UIColor clearColor] CGColor]; 24 | _progressLayer.strokeColor = [[UIColor whiteColor] CGColor]; 25 | _progressLayer.opacity = 1; 26 | _progressLayer.lineCap = kCALineCapRound; 27 | _progressLayer.lineWidth = 5; 28 | 29 | [_progressLayer setShadowColor:[UIColor blackColor].CGColor]; 30 | [_progressLayer setShadowOffset:CGSizeMake(1, 1)]; 31 | [_progressLayer setShadowOpacity:0.5]; 32 | [_progressLayer setShadowRadius:2]; 33 | } 34 | return self; 35 | } 36 | 37 | - (void)drawRect:(CGRect)rect { 38 | CGPoint center = CGPointMake(rect.size.width / 2, rect.size.height / 2); 39 | CGFloat radius = rect.size.width / 2; 40 | CGFloat startA = - M_PI_2; 41 | CGFloat endA = - M_PI_2 + M_PI * 2 * _progress; 42 | _progressLayer.frame = self.bounds; 43 | UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES]; 44 | _progressLayer.path =[path CGPath]; 45 | 46 | [_progressLayer removeFromSuperlayer]; 47 | [self.layer addSublayer:_progressLayer]; 48 | } 49 | 50 | - (void)setProgress:(double)progress { 51 | _progress = progress; 52 | [self setNeedsDisplay]; 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZVideoCropController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TZVideoCropController.h 3 | // TZImagePickerController 4 | // 5 | // Created by 肖兰月 on 2021/5/27. 6 | // Copyright © 2021 谭真. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @class TZAssetModel,TZImagePickerController; 14 | 15 | @interface TZVideoCropController : UIViewController 16 | @property (nonatomic, strong) TZAssetModel *model; 17 | @property (nonatomic, weak) TZImagePickerController *imagePickerVc; 18 | @end 19 | 20 | @protocol TZVideoEditViewDelegate 21 | - (void)editViewCropRectBeginChange; 22 | - (void)editViewCropRectEndChange; 23 | @end 24 | 25 | @interface TZVideoEditView : UIView 26 | @property (strong, nonatomic) UIImageView *beginImgView; 27 | @property (strong, nonatomic) UIImageView *endImgView; 28 | @property (strong, nonatomic) UIView *indicatorLine; 29 | @property (assign, nonatomic) CGFloat videoDuration; 30 | @property (assign, nonatomic) NSInteger maxCropVideoDuration; 31 | @property (assign, nonatomic) CGRect cropRect; 32 | @property (assign, nonatomic) CGFloat allImgWidth; 33 | @property (assign, nonatomic) CGFloat minCropRectWidth; 34 | 35 | @property (nonatomic, weak) id delegate; 36 | 37 | - (void)resetIndicatorLine; 38 | - (void)indicatorLineAnimateWithDuration:(NSTimeInterval)duration cropRect:(CGRect)cropRect; 39 | @end 40 | 41 | 42 | 43 | @interface TZVideoPictureCell : UICollectionViewCell 44 | @property (strong, nonatomic) UIImageView *imgView; 45 | @end 46 | 47 | NS_ASSUME_NONNULL_END 48 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZVideoEditedPreviewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TZVideoEditedPreviewController.h 3 | // TZImagePickerController 4 | // 5 | // Created by 肖兰月 on 2021/5/29. 6 | // Copyright © 2021 谭真. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface TZVideoEditedPreviewController : UIViewController 14 | @property (nonatomic, copy) NSURL *videoURL; 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZVideoEditedPreviewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TZVideoEditedPreviewController.m 3 | // TZImagePickerController 4 | // 5 | // Created by 肖兰月 on 2021/5/29. 6 | // Copyright © 2021 谭真. All rights reserved. 7 | // 8 | 9 | #import "TZVideoEditedPreviewController.h" 10 | #import 11 | #import "TZImageManager.h" 12 | #import "TZImagePickerController.h" 13 | #import "UIView+TZLayout.h" 14 | 15 | @interface TZVideoEditedPreviewController () { 16 | AVPlayer *_player; 17 | AVPlayerLayer *_playerLayer; 18 | UIButton *_playButton; 19 | UIImage *_cover; 20 | 21 | UIView *_toolBar; 22 | UIButton *_doneButton; 23 | UIProgressView *_progress; 24 | 25 | UIStatusBarStyle _originStatusBarStyle; 26 | } 27 | 28 | @end 29 | 30 | @implementation TZVideoEditedPreviewController 31 | 32 | - (void)viewDidLoad { 33 | [super viewDidLoad]; 34 | self.view.backgroundColor = [UIColor blackColor]; 35 | [self configMoviePlayer]; 36 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pausePlayerAndShowNaviBar) name:UIApplicationWillResignActiveNotification object:nil]; 37 | } 38 | 39 | - (void)configMoviePlayer { 40 | _player = [AVPlayer playerWithURL:self.videoURL]; 41 | _playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player]; 42 | [self.view.layer addSublayer:_playerLayer]; 43 | 44 | [self configPlayButton]; 45 | [self configBottomToolBar]; 46 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pausePlayerAndShowNaviBar) name:AVPlayerItemDidPlayToEndTimeNotification object:self->_player.currentItem]; 47 | } 48 | 49 | - (void)configPlayButton { 50 | _playButton = [UIButton buttonWithType:UIButtonTypeCustom]; 51 | [_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlay"] forState:UIControlStateNormal]; 52 | [_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlayHL"] forState:UIControlStateHighlighted]; 53 | [_playButton addTarget:self action:@selector(playButtonClick) forControlEvents:UIControlEventTouchUpInside]; 54 | [self.view addSubview:_playButton]; 55 | } 56 | 57 | - (void)configBottomToolBar { 58 | _toolBar = [[UIView alloc] initWithFrame:CGRectZero]; 59 | CGFloat rgb = 34 / 255.0; 60 | _toolBar.backgroundColor = [UIColor colorWithRed:rgb green:rgb blue:rgb alpha:0.7]; 61 | 62 | _doneButton = [UIButton buttonWithType:UIButtonTypeCustom]; 63 | _doneButton.titleLabel.font = [UIFont systemFontOfSize:16]; 64 | [_doneButton addTarget:self action:@selector(doneButtonClick) forControlEvents:UIControlEventTouchUpInside]; 65 | TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; 66 | if (tzImagePickerVc) { 67 | [_doneButton setTitle:tzImagePickerVc.doneBtnTitleStr forState:UIControlStateNormal]; 68 | [_doneButton setTitleColor:tzImagePickerVc.oKButtonTitleColorNormal forState:UIControlStateNormal]; 69 | } else { 70 | [_doneButton setTitle:[NSBundle tz_localizedStringForKey:@"Done"] forState:UIControlStateNormal]; 71 | [_doneButton setTitleColor:[UIColor colorWithRed:(83/255.0) green:(179/255.0) blue:(17/255.0) alpha:1.0] forState:UIControlStateNormal]; 72 | } 73 | [_doneButton setTitleColor:tzImagePickerVc.oKButtonTitleColorDisabled forState:UIControlStateDisabled]; 74 | [_toolBar addSubview:_doneButton]; 75 | [self.view addSubview:_toolBar]; 76 | } 77 | 78 | 79 | #pragma mark - Layout 80 | 81 | - (void)viewDidLayoutSubviews { 82 | [super viewDidLayoutSubviews]; 83 | 84 | BOOL isFullScreen = self.view.tz_height == [UIScreen mainScreen].bounds.size.height; 85 | CGFloat statusBarHeight = isFullScreen ? [TZCommonTools tz_statusBarHeight] : 0; 86 | CGFloat statusBarAndNaviBarHeight = statusBarHeight + self.navigationController.navigationBar.tz_height; 87 | 88 | CGFloat toolBarHeight = 44 + [TZCommonTools tz_safeAreaInsets].bottom; 89 | _toolBar.frame = CGRectMake(0, self.view.tz_height - toolBarHeight, self.view.tz_width, toolBarHeight); 90 | [_doneButton sizeToFit]; 91 | 92 | CGFloat doneButtonWidth = MAX(44, _doneButton.tz_width); 93 | if ([TZCommonTools tz_isRightToLeftLayout]) { 94 | _doneButton.frame = CGRectMake(12, 0, doneButtonWidth, 44); 95 | } else { 96 | _doneButton.frame = CGRectMake(self.view.tz_width - doneButtonWidth - 12, 0, doneButtonWidth, 44); 97 | } 98 | 99 | _playButton.frame = CGRectMake(0, statusBarAndNaviBarHeight, self.view.tz_width, self.view.tz_height - statusBarAndNaviBarHeight - toolBarHeight); 100 | _playerLayer.frame = self.view.bounds; 101 | } 102 | 103 | #pragma mark - Click Event 104 | 105 | - (void)playButtonClick { 106 | CMTime currentTime = _player.currentItem.currentTime; 107 | CMTime durationTime = _player.currentItem.duration; 108 | if (_player.rate == 0.0f) { 109 | if (currentTime.value == durationTime.value) [_player.currentItem seekToTime:CMTimeMake(0, 1)]; 110 | [_player play]; 111 | _toolBar.hidden = YES; 112 | [_playButton setImage:nil forState:UIControlStateNormal]; 113 | } else { 114 | [self pausePlayerAndShowNaviBar]; 115 | } 116 | } 117 | 118 | - (void)doneButtonClick { 119 | [self dismissViewControllerAnimated:YES completion:nil]; 120 | } 121 | 122 | #pragma mark - Notification Method 123 | 124 | - (void)pausePlayerAndShowNaviBar { 125 | [_player pause]; 126 | _toolBar.hidden = NO; 127 | [_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlay"] forState:UIControlStateNormal]; 128 | } 129 | 130 | - (void)dealloc { 131 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 132 | } 133 | 134 | #pragma clang diagnostic pop 135 | 136 | @end 137 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZVideoPlayerController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TZVideoPlayerController.h 3 | // TZImagePickerController 4 | // 5 | // Created by 谭真 on 16/1/5. 6 | // Copyright © 2016年 谭真. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class TZAssetModel; 12 | @interface TZVideoPlayerController : UIViewController 13 | 14 | @property (nonatomic, strong) TZAssetModel *model; 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/TZVideoPlayerController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TZVideoPlayerController.m 3 | // TZImagePickerController 4 | // 5 | // Created by 谭真 on 16/1/5. 6 | // Copyright © 2016年 谭真. All rights reserved. 7 | // 8 | 9 | #import "TZVideoPlayerController.h" 10 | #import 11 | #import "UIView+TZLayout.h" 12 | #import "TZImageManager.h" 13 | #import "TZAssetModel.h" 14 | #import "TZImagePickerController.h" 15 | #import "TZPhotoPreviewController.h" 16 | #import "TZVideoCropController.h" 17 | 18 | @interface TZVideoPlayerController () { 19 | AVPlayer *_player; 20 | AVPlayerLayer *_playerLayer; 21 | UIButton *_playButton; 22 | UIImage *_playButtonNormalImage; 23 | UIImage *_cover; 24 | NSString *_outputPath; 25 | NSString *_errorMsg; 26 | 27 | UIView *_toolBar; 28 | UIButton *_doneButton; 29 | UIButton *_editButton; 30 | UIProgressView *_progress; 31 | 32 | UIStatusBarStyle _originStatusBarStyle; 33 | } 34 | @property (assign, nonatomic) BOOL needShowStatusBar; 35 | 36 | // iCloud无法同步提示UI 37 | @property (nonatomic, strong) UIView *iCloudErrorView; 38 | 39 | @end 40 | 41 | #pragma clang diagnostic push 42 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 43 | 44 | @implementation TZVideoPlayerController 45 | 46 | - (void)viewDidLoad { 47 | [super viewDidLoad]; 48 | self.needShowStatusBar = ![UIApplication sharedApplication].statusBarHidden; 49 | self.view.backgroundColor = [UIColor blackColor]; 50 | TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; 51 | if (tzImagePickerVc) { 52 | self.navigationItem.title = tzImagePickerVc.previewBtnTitleStr; 53 | } 54 | [self configMoviePlayer]; 55 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pausePlayerAndShowNaviBar) name:UIApplicationWillResignActiveNotification object:nil]; 56 | } 57 | 58 | - (void)viewWillAppear:(BOOL)animated { 59 | [super viewWillAppear:animated]; 60 | _originStatusBarStyle = [UIApplication sharedApplication].statusBarStyle; 61 | [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent; 62 | } 63 | 64 | - (void)viewWillDisappear:(BOOL)animated { 65 | [super viewWillDisappear:animated]; 66 | if (self.needShowStatusBar) { 67 | [UIApplication sharedApplication].statusBarHidden = NO; 68 | } 69 | [UIApplication sharedApplication].statusBarStyle = _originStatusBarStyle; 70 | } 71 | 72 | - (void)configMoviePlayer { 73 | [[TZImageManager manager] getPhotoWithAsset:_model.asset completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) { 74 | BOOL iCloudSyncFailed = !photo && [TZCommonTools isICloudSyncError:info[PHImageErrorKey]]; 75 | self.iCloudErrorView.hidden = !iCloudSyncFailed; 76 | if (!isDegraded && photo) { 77 | self->_cover = photo; 78 | self->_doneButton.enabled = YES; 79 | self->_editButton.enabled = YES; 80 | } 81 | }]; 82 | [[TZImageManager manager] getVideoWithAsset:_model.asset completion:^(AVPlayerItem *playerItem, NSDictionary *info) { 83 | dispatch_async(dispatch_get_main_queue(), ^{ 84 | self->_player = [AVPlayer playerWithPlayerItem:playerItem]; 85 | self->_playerLayer = [AVPlayerLayer playerLayerWithPlayer:self->_player]; 86 | self->_playerLayer.frame = self.view.bounds; 87 | [self.view.layer addSublayer:self->_playerLayer]; 88 | [self addProgressObserver]; 89 | [self configPlayButton]; 90 | [self configBottomToolBar]; 91 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pausePlayerAndShowNaviBar) name:AVPlayerItemDidPlayToEndTimeNotification object:self->_player.currentItem]; 92 | }); 93 | }]; 94 | } 95 | 96 | /// Show progress,do it next time / 给播放器添加进度更新,下次加上 97 | - (void)addProgressObserver{ 98 | AVPlayerItem *playerItem = _player.currentItem; 99 | UIProgressView *progress = _progress; 100 | [_player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) { 101 | float current = CMTimeGetSeconds(time); 102 | float total = CMTimeGetSeconds([playerItem duration]); 103 | if (current) { 104 | [progress setProgress:(current/total) animated:YES]; 105 | } 106 | }]; 107 | } 108 | 109 | - (void)configPlayButton { 110 | _playButton = [UIButton buttonWithType:UIButtonTypeCustom]; 111 | [_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlay"] forState:UIControlStateNormal]; 112 | [_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlayHL"] forState:UIControlStateHighlighted]; 113 | [_playButton addTarget:self action:@selector(playButtonClick) forControlEvents:UIControlEventTouchUpInside]; 114 | [self.view addSubview:_playButton]; 115 | } 116 | 117 | - (void)configBottomToolBar { 118 | _toolBar = [[UIView alloc] initWithFrame:CGRectZero]; 119 | CGFloat rgb = 34 / 255.0; 120 | _toolBar.backgroundColor = [UIColor colorWithRed:rgb green:rgb blue:rgb alpha:0.7]; 121 | 122 | _doneButton = [UIButton buttonWithType:UIButtonTypeCustom]; 123 | _doneButton.titleLabel.font = [UIFont systemFontOfSize:16]; 124 | if (!_cover) { 125 | _doneButton.enabled = NO; 126 | } 127 | [_doneButton addTarget:self action:@selector(doneButtonClick) forControlEvents:UIControlEventTouchUpInside]; 128 | TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; 129 | if (tzImagePickerVc) { 130 | [_doneButton setTitle:tzImagePickerVc.doneBtnTitleStr forState:UIControlStateNormal]; 131 | [_doneButton setTitleColor:tzImagePickerVc.oKButtonTitleColorNormal forState:UIControlStateNormal]; 132 | } else { 133 | [_doneButton setTitle:[NSBundle tz_localizedStringForKey:@"Done"] forState:UIControlStateNormal]; 134 | [_doneButton setTitleColor:[UIColor colorWithRed:(83/255.0) green:(179/255.0) blue:(17/255.0) alpha:1.0] forState:UIControlStateNormal]; 135 | } 136 | [_doneButton setTitleColor:tzImagePickerVc.oKButtonTitleColorDisabled forState:UIControlStateDisabled]; 137 | [_toolBar addSubview:_doneButton]; 138 | [self.view addSubview:_toolBar]; 139 | 140 | if (tzImagePickerVc && tzImagePickerVc.allowEditVideo && roundf(self.model.asset.duration) > 1) { 141 | _editButton = [UIButton buttonWithType:UIButtonTypeCustom]; 142 | _editButton.titleLabel.font = [UIFont systemFontOfSize:16]; 143 | if (!_cover) { 144 | _editButton.enabled = NO; 145 | } 146 | [_editButton addTarget:self action:@selector(editButtonClick) forControlEvents:UIControlEventTouchUpInside]; 147 | [_editButton setTitle:tzImagePickerVc.editBtnTitleStr forState:UIControlStateNormal]; 148 | [_editButton setTitleColor:tzImagePickerVc.oKButtonTitleColorNormal forState:UIControlStateNormal]; 149 | [_editButton setTitleColor:tzImagePickerVc.oKButtonTitleColorDisabled forState:UIControlStateDisabled]; 150 | [_toolBar addSubview:_editButton]; 151 | } 152 | 153 | if (tzImagePickerVc.videoPreviewPageUIConfigBlock) { 154 | tzImagePickerVc.videoPreviewPageUIConfigBlock(_playButton, _toolBar, _editButton, _doneButton); 155 | } 156 | } 157 | 158 | - (UIStatusBarStyle)preferredStatusBarStyle { 159 | TZImagePickerController *tzImagePicker = (TZImagePickerController *)self.navigationController; 160 | if (tzImagePicker && [tzImagePicker isKindOfClass:[TZImagePickerController class]]) { 161 | return tzImagePicker.statusBarStyle; 162 | } 163 | return [super preferredStatusBarStyle]; 164 | } 165 | 166 | #pragma mark - Layout 167 | 168 | - (void)viewDidLayoutSubviews { 169 | [super viewDidLayoutSubviews]; 170 | TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController; 171 | 172 | BOOL isFullScreen = self.view.tz_height == [UIScreen mainScreen].bounds.size.height; 173 | BOOL isRTL = [TZCommonTools tz_isRightToLeftLayout]; 174 | 175 | CGFloat statusBarHeight = isFullScreen ? [TZCommonTools tz_statusBarHeight] : 0; 176 | CGFloat statusBarAndNaviBarHeight = statusBarHeight + self.navigationController.navigationBar.tz_height; 177 | _playerLayer.frame = self.view.bounds; 178 | CGFloat toolBarHeight = 44 + [TZCommonTools tz_safeAreaInsets].bottom; 179 | _toolBar.frame = CGRectMake(0, self.view.tz_height - toolBarHeight, self.view.tz_width, toolBarHeight); 180 | [_doneButton sizeToFit]; 181 | if (isRTL) { 182 | _doneButton.frame = CGRectMake(12, 0, MAX(44, _doneButton.tz_width), 44); 183 | } else { 184 | _doneButton.frame = CGRectMake(self.view.tz_width - _doneButton.tz_width - 12, 0, MAX(44, _doneButton.tz_width), 44); 185 | } 186 | _playButton.frame = CGRectMake(0, statusBarAndNaviBarHeight, self.view.tz_width, self.view.tz_height - statusBarAndNaviBarHeight - toolBarHeight); 187 | if (tzImagePickerVc.allowEditVideo) { 188 | [_editButton sizeToFit]; 189 | if (isRTL) { 190 | _editButton.frame = CGRectMake(self.view.tz_width - _editButton.tz_width - 12, 0, _editButton.tz_width, 44); 191 | } else { 192 | _editButton.frame = CGRectMake(12, 0, _editButton.tz_width, 44); 193 | } 194 | } 195 | if (tzImagePickerVc.videoPreviewPageDidLayoutSubviewsBlock) { 196 | tzImagePickerVc.videoPreviewPageDidLayoutSubviewsBlock(_playButton, _toolBar, _editButton, _doneButton); 197 | } 198 | } 199 | 200 | #pragma mark - Click Event 201 | 202 | - (void)playButtonClick { 203 | CMTime currentTime = _player.currentItem.currentTime; 204 | CMTime durationTime = _player.currentItem.duration; 205 | if (_player.rate == 0.0f) { 206 | [[NSNotificationCenter defaultCenter] postNotificationName:@"TZ_VIDEO_PLAY_NOTIFICATION" object:_player]; 207 | if (currentTime.value == durationTime.value) [_player.currentItem seekToTime:CMTimeMake(0, 1)]; 208 | [_player play]; 209 | [self.navigationController setNavigationBarHidden:YES]; 210 | _toolBar.hidden = YES; 211 | _playButtonNormalImage = [_playButton imageForState:UIControlStateNormal]; 212 | [_playButton setImage:nil forState:UIControlStateNormal]; 213 | [UIApplication sharedApplication].statusBarHidden = YES; 214 | } else { 215 | [self pausePlayerAndShowNaviBar]; 216 | } 217 | } 218 | 219 | - (void)editButtonClick { 220 | TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController; 221 | TZVideoCropController *videoCropVc = [[TZVideoCropController alloc] init]; 222 | videoCropVc.model = self.model; 223 | videoCropVc.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; 224 | videoCropVc.modalPresentationStyle = UIModalPresentationFullScreen; 225 | videoCropVc.modalPresentationCapturesStatusBarAppearance = YES; 226 | videoCropVc.imagePickerVc = imagePickerVc; 227 | [self presentViewController:videoCropVc animated:YES completion:nil]; 228 | } 229 | 230 | - (void)doneButtonClick { 231 | if ([[TZImageManager manager] isAssetCannotBeSelected:_model.asset]) { 232 | return; 233 | } 234 | TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController; 235 | if (imagePickerVc.allowEditVideo) { 236 | [imagePickerVc showProgressHUD]; 237 | [[TZImageManager manager] getVideoOutputPathWithAsset:_model.asset presetName:imagePickerVc.presetName success:^(NSString *outputPath) { 238 | [imagePickerVc hideProgressHUD]; 239 | self->_outputPath = outputPath; 240 | [self dismissAndCallDelegateMethod]; 241 | } failure:^(NSString *errorMessage, NSError *error) { 242 | [imagePickerVc hideProgressHUD]; 243 | self->_errorMsg = errorMessage; 244 | [self dismissAndCallDelegateMethod]; 245 | }]; 246 | } else { 247 | [self dismissAndCallDelegateMethod]; 248 | } 249 | } 250 | 251 | - (void)dismissAndCallDelegateMethod { 252 | TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController; 253 | if (!imagePickerVc) { 254 | [self dismissViewControllerAnimated:YES completion:nil]; 255 | return; 256 | } 257 | if (imagePickerVc.autoDismiss) { 258 | [imagePickerVc dismissViewControllerAnimated:YES completion:^{ 259 | [self callDelegateMethod]; 260 | }]; 261 | } else { 262 | [self callDelegateMethod]; 263 | } 264 | } 265 | 266 | - (void)callDelegateMethod { 267 | TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController; 268 | if (imagePickerVc.allowEditVideo) { 269 | if (_outputPath) { 270 | if ([imagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didFinishPickingAndEditingVideo:outputPath:error:)]) { 271 | [imagePickerVc.pickerDelegate imagePickerController:imagePickerVc didFinishPickingAndEditingVideo:self->_cover outputPath:self->_outputPath error:nil]; 272 | } 273 | if (imagePickerVc.didFinishPickingAndEditingVideoHandle) { 274 | imagePickerVc.didFinishPickingAndEditingVideoHandle(self->_cover, self->_outputPath, nil); 275 | } 276 | } else { 277 | if ([imagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didFinishPickingAndEditingVideo:outputPath:error:)]) { 278 | [imagePickerVc.pickerDelegate imagePickerController:imagePickerVc didFinishPickingAndEditingVideo:nil outputPath:nil error:self->_errorMsg]; 279 | } 280 | if (imagePickerVc.didFinishPickingAndEditingVideoHandle) { 281 | imagePickerVc.didFinishPickingAndEditingVideoHandle(nil, nil, self->_errorMsg); 282 | } 283 | } 284 | } else { 285 | if ([imagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didFinishPickingVideo:sourceAssets:)]) { 286 | [imagePickerVc.pickerDelegate imagePickerController:imagePickerVc didFinishPickingVideo:_cover sourceAssets:_model.asset]; 287 | } 288 | if (imagePickerVc.didFinishPickingVideoHandle) { 289 | imagePickerVc.didFinishPickingVideoHandle(_cover,_model.asset); 290 | } 291 | } 292 | } 293 | 294 | #pragma mark - Notification Method 295 | 296 | - (void)pausePlayerAndShowNaviBar { 297 | [_player pause]; 298 | _toolBar.hidden = NO; 299 | [self.navigationController setNavigationBarHidden:NO]; 300 | UIImage *normalImage = _playButtonNormalImage ?: [UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlay"]; 301 | [_playButton setImage:normalImage forState:UIControlStateNormal]; 302 | 303 | if (self.needShowStatusBar) { 304 | [UIApplication sharedApplication].statusBarHidden = NO; 305 | } 306 | } 307 | 308 | #pragma mark - lazy 309 | - (UIView *)iCloudErrorView{ 310 | if (!_iCloudErrorView) { 311 | _iCloudErrorView = [[UIView alloc] initWithFrame:CGRectMake(0, [TZCommonTools tz_statusBarHeight] + 44 + 10, self.view.tz_width, 28)]; 312 | UIImageView *icloud = [[UIImageView alloc] init]; 313 | icloud.image = [UIImage tz_imageNamedFromMyBundle:@"iCloudError"]; 314 | icloud.frame = CGRectMake(20, 0, 28, 28); 315 | [_iCloudErrorView addSubview:icloud]; 316 | UILabel *label = [[UILabel alloc] init]; 317 | label.frame = CGRectMake(53, 0, self.view.tz_width - 63, 28); 318 | label.font = [UIFont systemFontOfSize:10]; 319 | label.textColor = [UIColor whiteColor]; 320 | label.text = [NSBundle tz_localizedStringForKey:@"iCloud sync failed"]; 321 | [_iCloudErrorView addSubview:label]; 322 | [self.view addSubview:_iCloudErrorView]; 323 | _iCloudErrorView.hidden = YES; 324 | } 325 | return _iCloudErrorView; 326 | } 327 | 328 | - (void)dealloc { 329 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 330 | } 331 | 332 | #pragma clang diagnostic pop 333 | 334 | @end 335 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/UIView+TZLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+TZLayout.h 3 | // TZImagePickerController 4 | // 5 | // Created by 谭真 on 15/2/24. 6 | // Copyright © 2015年 谭真. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef enum : NSUInteger { 12 | TZOscillatoryAnimationToBigger, 13 | TZOscillatoryAnimationToSmaller, 14 | } TZOscillatoryAnimationType; 15 | 16 | @interface UIView (TZLayout) 17 | 18 | @property (nonatomic) CGFloat tz_left; ///< Shortcut for frame.origin.x. 19 | @property (nonatomic) CGFloat tz_top; ///< Shortcut for frame.origin.y 20 | @property (nonatomic) CGFloat tz_right; ///< Shortcut for frame.origin.x + frame.size.width 21 | @property (nonatomic) CGFloat tz_bottom; ///< Shortcut for frame.origin.y + frame.size.height 22 | @property (nonatomic) CGFloat tz_width; ///< Shortcut for frame.size.width. 23 | @property (nonatomic) CGFloat tz_height; ///< Shortcut for frame.size.height. 24 | @property (nonatomic) CGFloat tz_centerX; ///< Shortcut for center.x 25 | @property (nonatomic) CGFloat tz_centerY; ///< Shortcut for center.y 26 | @property (nonatomic) CGPoint tz_origin; ///< Shortcut for frame.origin. 27 | @property (nonatomic) CGSize tz_size; ///< Shortcut for frame.size. 28 | 29 | + (void)showOscillatoryAnimationWithLayer:(CALayer *)layer type:(TZOscillatoryAnimationType)type; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImagePickerController/UIView+TZLayout.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+TZLayout.m 3 | // TZImagePickerController 4 | // 5 | // Created by 谭真 on 15/2/24. 6 | // Copyright © 2015年 谭真. All rights reserved. 7 | // 8 | 9 | #import "UIView+TZLayout.h" 10 | 11 | @implementation UIView (TZLayout) 12 | 13 | - (CGFloat)tz_left { 14 | return self.frame.origin.x; 15 | } 16 | 17 | - (void)setTz_left:(CGFloat)x { 18 | CGRect frame = self.frame; 19 | frame.origin.x = x; 20 | self.frame = frame; 21 | } 22 | 23 | - (CGFloat)tz_top { 24 | return self.frame.origin.y; 25 | } 26 | 27 | - (void)setTz_top:(CGFloat)y { 28 | CGRect frame = self.frame; 29 | frame.origin.y = y; 30 | self.frame = frame; 31 | } 32 | 33 | - (CGFloat)tz_right { 34 | return self.frame.origin.x + self.frame.size.width; 35 | } 36 | 37 | - (void)setTz_right:(CGFloat)right { 38 | CGRect frame = self.frame; 39 | frame.origin.x = right - frame.size.width; 40 | self.frame = frame; 41 | } 42 | 43 | - (CGFloat)tz_bottom { 44 | return self.frame.origin.y + self.frame.size.height; 45 | } 46 | 47 | - (void)setTz_bottom:(CGFloat)bottom { 48 | CGRect frame = self.frame; 49 | frame.origin.y = bottom - frame.size.height; 50 | self.frame = frame; 51 | } 52 | 53 | - (CGFloat)tz_width { 54 | return self.frame.size.width; 55 | } 56 | 57 | - (void)setTz_width:(CGFloat)width { 58 | CGRect frame = self.frame; 59 | frame.size.width = width; 60 | self.frame = frame; 61 | } 62 | 63 | - (CGFloat)tz_height { 64 | return self.frame.size.height; 65 | } 66 | 67 | - (void)setTz_height:(CGFloat)height { 68 | CGRect frame = self.frame; 69 | frame.size.height = height; 70 | self.frame = frame; 71 | } 72 | 73 | - (CGFloat)tz_centerX { 74 | return self.center.x; 75 | } 76 | 77 | - (void)setTz_centerX:(CGFloat)centerX { 78 | self.center = CGPointMake(centerX, self.center.y); 79 | } 80 | 81 | - (CGFloat)tz_centerY { 82 | return self.center.y; 83 | } 84 | 85 | - (void)setTz_centerY:(CGFloat)centerY { 86 | self.center = CGPointMake(self.center.x, centerY); 87 | } 88 | 89 | - (CGPoint)tz_origin { 90 | return self.frame.origin; 91 | } 92 | 93 | - (void)setTz_origin:(CGPoint)origin { 94 | CGRect frame = self.frame; 95 | frame.origin = origin; 96 | self.frame = frame; 97 | } 98 | 99 | - (CGSize)tz_size { 100 | return self.frame.size; 101 | } 102 | 103 | - (void)setTz_size:(CGSize)size { 104 | CGRect frame = self.frame; 105 | frame.size = size; 106 | self.frame = frame; 107 | } 108 | 109 | + (void)showOscillatoryAnimationWithLayer:(CALayer *)layer type:(TZOscillatoryAnimationType)type{ 110 | NSNumber *animationScale1 = type == TZOscillatoryAnimationToBigger ? @(1.15) : @(0.5); 111 | NSNumber *animationScale2 = type == TZOscillatoryAnimationToBigger ? @(0.92) : @(1.15); 112 | 113 | [UIView animateWithDuration:0.15 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut animations:^{ 114 | [layer setValue:animationScale1 forKeyPath:@"transform.scale"]; 115 | } completion:^(BOOL finished) { 116 | [UIView animateWithDuration:0.15 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut animations:^{ 117 | [layer setValue:animationScale2 forKeyPath:@"transform.scale"]; 118 | } completion:^(BOOL finished) { 119 | [UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut animations:^{ 120 | [layer setValue:@(1.0) forKeyPath:@"transform.scale"]; 121 | } completion:nil]; 122 | }]; 123 | }]; 124 | } 125 | 126 | @end 127 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImageUploadOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // TZImageUploadOperation.h 3 | // TZImagePickerController 4 | // 5 | // Created by 谭真 on 2019/1/14. 6 | // Copyright © 2019 谭真. All rights reserved. 7 | // 8 | 9 | #import "TZImageRequestOperation.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface TZImageUploadOperation : TZImageRequestOperation 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /TZImagePickerController/TZImageUploadOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // TZImageUploadOperation.m 3 | // TZImagePickerController 4 | // 5 | // Created by 谭真 on 2019/1/14. 6 | // Copyright © 2019 谭真. All rights reserved. 7 | // 8 | 9 | #import "TZImageUploadOperation.h" 10 | #import "TZImageManager.h" 11 | 12 | @implementation TZImageUploadOperation 13 | 14 | - (void)start { 15 | NSLog(@"TZImageUploadOperation start"); 16 | self.executing = YES; 17 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 18 | #pragma mark - 获取&上传大图 19 | /* 20 | [[TZImageManager manager] getPhotoWithAsset:self.asset completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) { 21 | dispatch_async(dispatch_get_main_queue(), ^{ 22 | if (!isDegraded) { 23 | if (self.completedBlock) { 24 | self.completedBlock(photo, info, isDegraded); 25 | } 26 | // 在这里上传图片(代码略),图片上传完毕后调用[self done] 27 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 28 | [self done]; 29 | }); 30 | } 31 | }); 32 | } progressHandler:^(double progress, NSError *error, BOOL *stop, NSDictionary *info) { 33 | dispatch_async(dispatch_get_main_queue(), ^{ 34 | if (self.progressBlock) { 35 | self.progressBlock(progress, error, stop, info); 36 | } 37 | }); 38 | } networkAccessAllowed:YES]; 39 | */ 40 | 41 | #pragma mark - 获取&上传原图 42 | [[TZImageManager manager] getOriginalPhotoWithAsset:self.asset progressHandler:^(double progress, NSError *error, BOOL *stop, NSDictionary *info) { 43 | dispatch_async(dispatch_get_main_queue(), ^{ 44 | if (self.progressBlock) { 45 | self.progressBlock(progress, error, stop, info); 46 | } 47 | }); 48 | } newCompletion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) { 49 | dispatch_async(dispatch_get_main_queue(), ^{ 50 | if (!isDegraded) { 51 | if (self.completedBlock) { 52 | self.completedBlock(photo, info, isDegraded); 53 | } 54 | // 在这里上传图片(代码略),图片上传完毕后调用[self done] 55 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 56 | [self done]; 57 | }); 58 | } 59 | }); 60 | }]; 61 | }); 62 | } 63 | 64 | - (void)done { 65 | [super done]; 66 | // NSLog(@"TZImageUploadOperation done"); 67 | } 68 | 69 | @end 70 | -------------------------------------------------------------------------------- /TZImagePickerController/TZTestCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // TZTestCell.h 3 | // TZImagePickerController 4 | // 5 | // Created by 谭真 on 16/1/3. 6 | // Copyright © 2016年 谭真. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TZTestCell : UICollectionViewCell 12 | 13 | @property (nonatomic, strong) UIImageView *imageView; 14 | @property (nonatomic, strong) UIImageView *videoImageView; 15 | @property (nonatomic, strong) UIButton *deleteBtn; 16 | @property (nonatomic, strong) UILabel *gifLable; 17 | @property (nonatomic, assign) NSInteger row; 18 | @property (nonatomic, strong) id asset; 19 | 20 | - (UIView *)snapshotView; 21 | 22 | @end 23 | 24 | -------------------------------------------------------------------------------- /TZImagePickerController/TZTestCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // TZTestCell.m 3 | // TZImagePickerController 4 | // 5 | // Created by 谭真 on 16/1/3. 6 | // Copyright © 2016年 谭真. All rights reserved. 7 | // 8 | 9 | #import "TZTestCell.h" 10 | #import "UIView+TZLayout.h" 11 | #import 12 | #import "TZImagePickerController/TZImagePickerController.h" 13 | 14 | @implementation TZTestCell 15 | 16 | - (instancetype)initWithFrame:(CGRect)frame { 17 | self = [super initWithFrame:frame]; 18 | if (self) { 19 | self.backgroundColor = [UIColor whiteColor]; 20 | _imageView = [[UIImageView alloc] init]; 21 | _imageView.backgroundColor = [UIColor colorWithWhite:1.000 alpha:0.500]; 22 | _imageView.contentMode = UIViewContentModeScaleAspectFit; 23 | [self addSubview:_imageView]; 24 | self.clipsToBounds = YES; 25 | 26 | _videoImageView = [[UIImageView alloc] init]; 27 | _videoImageView.image = [UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlay"]; 28 | _videoImageView.contentMode = UIViewContentModeScaleAspectFill; 29 | _videoImageView.hidden = YES; 30 | [self addSubview:_videoImageView]; 31 | 32 | _deleteBtn = [UIButton buttonWithType:UIButtonTypeCustom]; 33 | [_deleteBtn setImage:[UIImage imageNamed:@"photo_delete"] forState:UIControlStateNormal]; 34 | _deleteBtn.imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 0, -10); 35 | _deleteBtn.alpha = 0.6; 36 | [self addSubview:_deleteBtn]; 37 | 38 | _gifLable = [[UILabel alloc] init]; 39 | _gifLable.text = @"GIF"; 40 | _gifLable.textColor = [UIColor whiteColor]; 41 | _gifLable.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.8]; 42 | _gifLable.textAlignment = NSTextAlignmentCenter; 43 | _gifLable.font = [UIFont systemFontOfSize:10]; 44 | [self addSubview:_gifLable]; 45 | } 46 | return self; 47 | } 48 | 49 | - (void)layoutSubviews { 50 | [super layoutSubviews]; 51 | _imageView.frame = self.bounds; 52 | _gifLable.frame = CGRectMake(self.tz_width - 25, self.tz_height - 14, 25, 14); 53 | _deleteBtn.frame = CGRectMake(self.tz_width - 36, 0, 36, 36); 54 | CGFloat width = self.tz_width / 3.0; 55 | _videoImageView.frame = CGRectMake(width, width, width, width); 56 | } 57 | 58 | - (void)setAsset:(PHAsset *)asset { 59 | _asset = asset; 60 | _videoImageView.hidden = asset.mediaType != PHAssetMediaTypeVideo; 61 | _gifLable.hidden = ![[asset valueForKey:@"filename"] containsString:@"GIF"]; 62 | } 63 | 64 | - (void)setRow:(NSInteger)row { 65 | _row = row; 66 | _deleteBtn.tag = row; 67 | } 68 | 69 | - (UIView *)snapshotView { 70 | UIView *snapshotView = [[UIView alloc]init]; 71 | 72 | UIView *cellSnapshotView = nil; 73 | 74 | if ([self respondsToSelector:@selector(snapshotViewAfterScreenUpdates:)]) { 75 | cellSnapshotView = [self snapshotViewAfterScreenUpdates:NO]; 76 | } else { 77 | CGSize size = CGSizeMake(self.bounds.size.width + 20, self.bounds.size.height + 20); 78 | UIGraphicsBeginImageContextWithOptions(size, self.opaque, 0); 79 | [self.layer renderInContext:UIGraphicsGetCurrentContext()]; 80 | UIImage * cellSnapshotImage = UIGraphicsGetImageFromCurrentImageContext(); 81 | UIGraphicsEndImageContext(); 82 | cellSnapshotView = [[UIImageView alloc]initWithImage:cellSnapshotImage]; 83 | } 84 | 85 | snapshotView.frame = CGRectMake(0, 0, cellSnapshotView.frame.size.width, cellSnapshotView.frame.size.height); 86 | cellSnapshotView.frame = CGRectMake(0, 0, cellSnapshotView.frame.size.width, cellSnapshotView.frame.size.height); 87 | 88 | [snapshotView addSubview:cellSnapshotView]; 89 | return snapshotView; 90 | } 91 | 92 | @end 93 | -------------------------------------------------------------------------------- /TZImagePickerController/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // TZImagePickerController 4 | // 5 | // Created by 谭真 on 15/12/24. 6 | // Copyright © 2015年 谭真. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /TZImagePickerController/ar-001.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /TZImagePickerController/ar-001.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/ar-001.lproj/Localizable.strings -------------------------------------------------------------------------------- /TZImagePickerController/ar-001.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UITextField"; text = "9"; ObjectID = "37K-cO-hgf"; */ 3 | "37K-cO-hgf.text" = "9"; 4 | 5 | /* Class = "UILabel"; text = "允许选择照片原图"; ObjectID = "7mi-Lf-2N5"; */ 6 | "7mi-Lf-2N5.text" = "允许选择照片原图"; 7 | 8 | /* Class = "UILabel"; text = "允许多选视频/GIF/图片"; ObjectID = "7q9-aI-kbz"; */ 9 | "7q9-aI-kbz.text" = "允许多选视频/GIF/图片"; 10 | 11 | /* Class = "UILabel"; text = "允许拍视频"; ObjectID = "8H2-cq-aUq"; */ 12 | "8H2-cq-aUq.text" = "允许拍视频"; 13 | 14 | /* Class = "UILabel"; text = "单选模式下允许裁剪"; ObjectID = "Ibb-kN-MWC"; */ 15 | "Ibb-kN-MWC.text" = "单选模式下允许裁剪"; 16 | 17 | /* Class = "UILabel"; text = "允许选择Gif图片"; ObjectID = "Ly2-uJ-7DN"; */ 18 | "Ly2-uJ-7DN.text" = "允许选择Gif图片"; 19 | 20 | /* Class = "UILabel"; text = "允许拍照"; ObjectID = "R92-Zn-NWV"; */ 21 | "R92-Zn-NWV.text" = "允许拍照"; 22 | 23 | /* Class = "UILabel"; text = "使用圆形裁剪框"; ObjectID = "TiG-df-jDs"; */ 24 | "TiG-df-jDs.text" = "使用圆形裁剪框"; 25 | 26 | /* Class = "UILabel"; text = "照片按修改时间升序排列"; ObjectID = "UL3-n3-Hmt"; */ 27 | "UL3-n3-Hmt.text" = "照片按修改时间升序排列"; 28 | 29 | /* Class = "UITextField"; text = "4"; ObjectID = "YR1-nJ-q0v"; */ 30 | "YR1-nJ-q0v.text" = "4"; 31 | 32 | /* Class = "UILabel"; text = "每行展示照片张数"; ObjectID = "fbH-6q-oeE"; */ 33 | "fbH-6q-oeE.text" = "每行展示照片张数"; 34 | 35 | /* Class = "UILabel"; text = "把拍照/拍视频按钮放在外面"; ObjectID = "jop-5R-ioj"; */ 36 | "jop-5R-ioj.text" = "把拍照/拍视频按钮放在外面"; 37 | 38 | /* Class = "UILabel"; text = "右上角显示图片选中序号"; ObjectID = "nm2-mT-siy"; */ 39 | "nm2-mT-siy.text" = "右上角显示图片选中序号"; 40 | 41 | /* Class = "UILabel"; text = "照片最大可选张数"; ObjectID = "rK3-1U-kcW"; */ 42 | "rK3-1U-kcW.text" = "照片最大可选张数"; 43 | 44 | /* Class = "UILabel"; text = "允许选择照片"; ObjectID = "vNN-OI-9z1"; */ 45 | "vNN-OI-9z1.text" = "允许选择照片"; 46 | 47 | /* Class = "UILabel"; text = "允许选择视频"; ObjectID = "xCu-D3-IZg"; */ 48 | "xCu-D3-IZg.text" = "允许选择视频"; 49 | -------------------------------------------------------------------------------- /TZImagePickerController/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // TZImagePickerController 4 | // 5 | // Created by 谭真 on 15/12/24. 6 | // Copyright © 2015年 谭真. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /TZImagePickerController/tz-ru.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banchichen/TZImagePickerController/5deb14dfa3132b76e36ca85a511d111685ec3f9f/TZImagePickerController/tz-ru.lproj/Localizable.strings -------------------------------------------------------------------------------- /TZImagePickerController/zh-Hans.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /TZImagePickerController/zh-Hans.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = "允许选择照片原图"; ObjectID = "7mi-Lf-2N5"; */ 3 | "7mi-Lf-2N5.text" = "允许选择照片原图"; 4 | 5 | /* Class = "UILabel"; text = "显示内部拍照按钮"; ObjectID = "R92-Zn-NWV"; */ 6 | "R92-Zn-NWV.text" = "显示内部拍照按钮"; 7 | 8 | /* Class = "UILabel"; text = "照片按修改时间升序排列"; ObjectID = "UL3-n3-Hmt"; */ 9 | "UL3-n3-Hmt.text" = "照片按修改时间升序排列"; 10 | 11 | /* Class = "UILabel"; text = "把拍照按钮放在外面"; ObjectID = "jop-5R-ioj"; */ 12 | "jop-5R-ioj.text" = "把拍照按钮放在外面"; 13 | 14 | /* Class = "UILabel"; text = "允许选择照片"; ObjectID = "vNN-OI-9z1"; */ 15 | "vNN-OI-9z1.text" = "允许选择照片"; 16 | 17 | /* Class = "UILabel"; text = "允许选择视频"; ObjectID = "xCu-D3-IZg"; */ 18 | "xCu-D3-IZg.text" = "允许选择视频"; 19 | -------------------------------------------------------------------------------- /TZImagePickerControllerFramework/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /TZImagePickerControllerFramework/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyAccessedAPITypes 6 | 7 | 8 | NSPrivacyAccessedAPIType 9 | NSPrivacyAccessedAPICategoryFileTimestamp 10 | NSPrivacyAccessedAPITypeReasons 11 | 12 | 3B52.1 13 | 14 | 15 | 16 | NSPrivacyCollectedDataTypes 17 | 18 | 19 | 20 | NSPrivacyTrackingDomains 21 | 22 | NSPrivacyTracking 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /TZImagePickerControllerTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /TZImagePickerControllerTests/TZImagePickerControllerTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TZImagePickerControllerTests.m 3 | // TZImagePickerControllerTests 4 | // 5 | // Created by 谭真 on 15/12/24. 6 | // Copyright © 2015年 谭真. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TZImagePickerControllerTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation TZImagePickerControllerTests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | - (void)tearDown { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample { 28 | // This is an example of a functional test case. 29 | // Use XCTAssert and related functions to verify your tests produce the correct results. 30 | } 31 | 32 | - (void)testPerformanceExample { 33 | // This is an example of a performance test case. 34 | [self measureBlock:^{ 35 | // Put the code you want to measure the time of here. 36 | }]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /TZImagePickerControllerUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /TZImagePickerControllerUITests/TZImagePickerControllerUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TZImagePickerControllerUITests.m 3 | // TZImagePickerControllerUITests 4 | // 5 | // Created by 谭真 on 15/12/24. 6 | // Copyright © 2015年 谭真. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TZImagePickerControllerUITests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation TZImagePickerControllerUITests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | 22 | // In UI tests it is usually best to stop immediately when a failure occurs. 23 | self.continueAfterFailure = NO; 24 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 25 | [[[XCUIApplication alloc] init] launch]; 26 | 27 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 28 | } 29 | 30 | - (void)tearDown { 31 | // Put teardown code here. This method is called after the invocation of each test method in the class. 32 | [super tearDown]; 33 | } 34 | 35 | - (void)testExample { 36 | // Use recording to get started writing UI tests. 37 | // Use XCTAssert and related functions to verify your tests produce the correct results. 38 | } 39 | 40 | @end 41 | --------------------------------------------------------------------------------