首次提交
22
Pods/TZImagePickerController/LICENSE
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Zhen Tan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
176
Pods/TZImagePickerController/README.md
generated
Normal file
@@ -0,0 +1,176 @@
|
||||
# TZImagePickerController
|
||||
[](https://github.com/banchichen/TZImagePickerController)
|
||||
[](https://github.com/Carthage/Carthage)
|
||||
|
||||
|
||||
A clone of UIImagePickerController, support picking multiple photos、original photo、video, also allow preview photo and video, support iOS10+.
|
||||
一个支持多选、选原图和视频的图片选择器,同时有预览功能,支持iOS10+。
|
||||
|
||||
## 重要提示1:提issue前,请先对照Demo、常见问题自查!Demo正常说明你可以升级下新版试试。
|
||||
|
||||
## 重要提示2:3.8.8版本修复了iOS18下无照片和openURL失效的问题
|
||||
关于iOS14模拟器的问题
|
||||
PHAuthorizationStatusLimited授权模式下,iOS14模拟器有bug,未授权照片无法显示,真机正常,暂可忽略:https://github.com/banchichen/TZImagePickerController/issues/1347
|
||||
|
||||
关于升级iOS10和Xcdoe8的提示:
|
||||
在Xcode8环境下将项目运行在iOS10的设备/模拟器中,访问相册和相机需要额外配置info.plist文件。分别是Privacy - Photo Library Usage Description和Privacy - Camera Usage Description字段,详见Demo中info.plist中的设置。
|
||||
|
||||
项目截图 1.Demo首页 2.照片列表页 3.照片预览页 4.视频预览页
|
||||
<img src="https://github.com/banchichen/TZImagePickerController/blob/master/TZImagePickerController/ScreenShots/DemoPage.png" width="40%" height="40%"><img src="https://github.com/banchichen/TZImagePickerController/blob/master/TZImagePickerController/ScreenShots/photoPickerVc.PNG" width="40%" height="40%">
|
||||
<img src="https://github.com/banchichen/TZImagePickerController/blob/master/TZImagePickerController/ScreenShots/photoPreviewVc.PNG" width="40%" height="40%"><img src="https://github.com/banchichen/TZImagePickerController/blob/master/TZImagePickerController/ScreenShots/videoPlayerVc.PNG" width="40%" height="40%">
|
||||
|
||||
## 一. Installation 安装
|
||||
|
||||
#### CocoaPods
|
||||
> pod 'TZImagePickerController' # Full version with all features
|
||||
> pod 'TZImagePickerController/Basic' # No location code
|
||||
|
||||
#### Carthage
|
||||
> github "banchichen/TZImagePickerController"
|
||||
|
||||
#### 手动安装
|
||||
> 将TZImagePickerController文件夹拽入项目中,导入头文件:#import "TZImagePickerController.h"
|
||||
|
||||
## 二. Example 例子
|
||||
|
||||
TZImagePickerController *imagePickerVc = [[TZImagePickerController alloc] initWithMaxImagesCount:9 delegate:self];
|
||||
|
||||
// You can get the photos by block, the same as by delegate.
|
||||
// 你可以通过block或者代理,来得到用户选择的照片.
|
||||
[imagePickerVc setDidFinishPickingPhotosHandle:^(NSArray<UIImage *> *photos, NSArray *assets, BOOL isSelectOriginalPhoto) {
|
||||
|
||||
}];
|
||||
[self presentViewController:imagePickerVc animated:YES completion:nil];
|
||||
|
||||
## 三. Requirements 要求
|
||||
iOS 10 or later.
|
||||
支持iOS10及以上系统。
|
||||
|
||||
TZImagePickerController uses Camera、Location、Microphone、Photo Library,you need add these properties to info.plist like Demo:
|
||||
TZImagePickerController使用了相机、定位、麦克风、相册,请参考Demo添加下列属性到info.plist文件:
|
||||
`Privacy - Camera Usage Description`
|
||||
`Privacy - Location Usage Description`
|
||||
`Privacy - Location When In Use Usage Description`
|
||||
`Privacy - Microphone Usage Description`
|
||||
`Privacy - Photo Library Usage Description`
|
||||
`Prevent limited photos access alert`
|
||||
|
||||
## 四. More 更多
|
||||
|
||||
If you find a bug, please create a issue.
|
||||
More information please view code.
|
||||
如果你发现了bug,请提一个issue。
|
||||
更多信息详见代码,也可查看我的博客: [我的博客](http://www.jianshu.com/p/1975411a31bb "半尺尘 - 简书")
|
||||
|
||||
关于issue:
|
||||
请尽可能详细地描述**系统版本**、**手机型号**、**库的版本**、**崩溃日志**和**复现步骤**,**请先更新到最新版再测试一下**,如果新版还存在再提~如果已有开启的类似issue,请直接在该issue下评论说出你的问题
|
||||
|
||||
## 五. FAQ 常见问题
|
||||
|
||||
**Q:pod search TZImagePickerController 搜索出来的不是最新版本**
|
||||
A:需要在终端执行cd转换文件路径命令退回到Desktop,然后执行pod setup命令更新本地spec缓存(可能需要几分钟),然后再搜索就可以了
|
||||
|
||||
**Q:拍照后照片保存失败**
|
||||
A:请参考issue481:https://github.com/banchichen/TZImagePickerController/issues/481 的信息排查,若还有问题请直接在issue内评论
|
||||
|
||||
**Q:photos数组图片不是原图,如何获取原图?**
|
||||
A:请参考issue457的解释:https://github.com/banchichen/TZImagePickerController/issues/457
|
||||
|
||||
**Q:系统语言是中文/英文,界面上却有部分相册名字、返回按钮显示成了英文/中文?**
|
||||
A:请参考 https://github.com/banchichen/TZImagePickerController/issues/443 和 https://github.com/banchichen/TZImagePickerController/issues/929
|
||||
|
||||
**Q:预览界面能否支持传入NSURL、UIImage对象?**
|
||||
A:3.0.1版本已支持,需新接一个库:[TZImagePreviewController](https://github.com/banchichen/TZImagePreviewController),请参考里面的Demo使用。
|
||||
|
||||
**Q:设置可选视频的最大/最小时长?照片的最小/最大尺寸?不符合要求的不显示**
|
||||
A:可以的,参照Demo的isAssetCanBeDisplayed方法实现。我会返回asset出来,显示与否你来决定,注意这个是一个同步方法,对于需要根据asset去异步获取的信息如视频的大小、视频是否存在iCloud里来过滤的,无法做到。如果真要这样做,相册打开速度会变慢,你需要改我源码。
|
||||
如果需要显示,选择时才提醒用户不可选,则实现isAssetCanBeSelected,用户选择时会调用它
|
||||
|
||||
**Q:预览页面出现了导航栏?**
|
||||
A:https://github.com/banchichen/TZImagePickerController/issues/652
|
||||
|
||||
**Q:可否增加微信编辑图片的功能?**
|
||||
A:考虑下,优先级低
|
||||
|
||||
**Q:是否有QQ/微信群/钉钉群?**
|
||||
A:有「钉钉群:33192786」和「QQ群:859033147」,推荐加钉钉群,答疑响应更快
|
||||
|
||||
**Q:想提交一个Pull Request?**
|
||||
A:请先加钉钉群(33192786)说下方案,和我确认下,避免同时改动同一处内容。**一个PR请只修复1个问题,变动内容越少越好**。
|
||||
|
||||
**Q:demo在真机上跑不起来?**
|
||||
A:1、team选你自己的;2、bundleId也改成你自己的或改成一个不会和别人重复的。可参考[简书的这篇博客](https://www.jianshu.com/p/cbe59138fca6)
|
||||
|
||||
**Q:3.6.4以上版本设置导航栏颜色无效?**
|
||||
A:参考Demo里的代码,加上imagePickerVc.navigationBar.standardAppearance的相关设置
|
||||
|
||||
**Q:设置导航栏颜色无效?导航栏颜色总是白色?**
|
||||
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)
|
||||
|
||||
**Q:导航栏没了?**
|
||||
A:是否有集成GKNavigationBarViewController?需要升级到2.0.4及以上版本,详见issue:[https://github.com/QuintGao/GKNavigationBarViewController/issues/7](https://github.com/QuintGao/GKNavigationBarViewController/issues/7)。
|
||||
|
||||
**Q:有的视频导出失败?**
|
||||
A:升级到2.2.6及以上版本试试,发现是修正视频转向导致的,2.2.6开始默认不再主动修正。如需打开,可设置needFixComposition为YES,但有几率导致安卓拍的视频导出失败。此外也可参考这个issue:https://github.com/banchichen/TZImagePickerController/issues/1073
|
||||
|
||||
**Q:视频导出慢?**
|
||||
A:视频导出分两步,第一步是通过PHAsset获取AVURLAsset,如是iCloud视频则涉及到网络请求,耗时容易不可控,第二步是通过AVURLAsset把视频保存到沙盒,耗时不算多。但第一步耗时不可控,你可以拷贝我源码出来拿到第一步的进度给用户一个进度提示...
|
||||
|
||||
**Q:有的图片info里没有PHImageFileURLKey?**
|
||||
A:不要去拿PHImageFileURLKey,没用的,只有通过Photos框架才能访问相册照片,光拿一个路径没用。
|
||||
如果需要通过路径上传照片,请先把UIImage保存到沙盒,**用沙盒路径**。
|
||||
如果你上传照片需要一个名字参数,请参考Demo**直接用照片名字**。
|
||||
|
||||
## 六. Release Notes 最近更新
|
||||
|
||||
**3.8.8 支持iOS18,修复openURL的失效问题** [#1686](https://github.com/banchichen/TZImagePickerController/issues/1686)
|
||||
**3.8.5 新增隐私清单文件** [#1675](https://github.com/banchichen/TZImagePickerController/pull/1675)
|
||||
**3.8.4 支持使用不带定位代码的版本** [#1606](https://github.com/banchichen/TZImagePickerController/pull/1606)
|
||||
3.8.1 iOS14下可添加访问更多照片,详见PR内的评论 [#1526](https://github.com/banchichen/TZImagePickerController/pull/1526)
|
||||
3.7.6 修复iOS15.2下初次授权相册权限时的长时间卡顿&白屏问题 [#1547](https://github.com/banchichen/TZImagePickerController/issues/1547)
|
||||
**3.6.7 修复Xcode13&iOS15下导航栏颜色异常问题**
|
||||
3.6.2 新增allowEditVideo,单选视频时支持裁剪
|
||||
3.6.0 修复iOS14下iCloud视频导出失败问题
|
||||
**3.5.2 适配iPhone12系列设备**
|
||||
3.4.4 支持Dark Mode
|
||||
3.4.2 适配iOS14,若干问题修复
|
||||
3.3.2 适配iOS13,若干问题修复
|
||||
3.2.1 新增裁剪用scaleAspectFillCrop属性,设置为YES后,照片尺寸小于裁剪框时会自动放大撑满
|
||||
3.2.0 加入用NSOperationQueue控制获取原图并发数降低内存的示例
|
||||
3.1.8 批量获取图片时加入队列控制,尝试优化大批量选择图片时CPU和内存占用过高的问题(仍然危险,maxImagesCount谨慎设置过大...)
|
||||
3.1.5 相册内无照片时给出提示,修复快速滑动时内存一直增加的问题
|
||||
3.1.3 适配阿拉伯等语言下从右往左布局的特性
|
||||
3.0.8 新增gifImagePlayBlock允许使用FLAnimatedImage等替换内部的GIF播放方案
|
||||
3.0.7 适配iPhoneXR、XS、XS Max
|
||||
3.0.6 优化保存照片、视频的方法
|
||||
3.0.1 新增对[TZImagePreviewController](https://github.com/banchichen/TZImagePreviewController)库的支持,允许预览UIImage、NSURL、PHAsset对象
|
||||
**3.0.0 去除iOS6和7的适配代码,更轻量,最低支持iOS8**
|
||||
2.2.6 新增needFixComposition属性,默认为NO,不再主动修正视频转向,防止部分安卓拍的视频导出失败(**最后一个支持iOS6和7的版本**)
|
||||
2.1.5 修复开启showSelectedIndex后照片列表页iCloud图片进度条紊乱的bug
|
||||
2.1.4 新增多个页面和组件的样式自定义block,允许自定义绝大多数UI样式
|
||||
2.1.2 新增showPhotoCannotSelectLayer属性,当已选照片张数达到最大可选张数时,可像微信一样让其它照片显示一个提示不可选的浮层
|
||||
2.1.1 新增是否显示图片选中序号的属性,优化一些细节
|
||||
2.1.0.3 新增拍摄视频功能,优化一些细节
|
||||
2.0.0.6 优化自定义languageBundle的支持,加入使用示例
|
||||
2.0.0.5 优化性能,提高选择器打开速度,新增越南语支持
|
||||
2.0.0.2 新增繁体语言,可设置首选语言,国际化支持更强大;优化一些细节
|
||||
1.9.8 支持Carthage,优化一些细节
|
||||
1.9.6 优化视频预览和gif预览页toolbar在iPhoneX上的样式
|
||||
...
|
||||
1.8.4 加入横竖屏适配;支持视频/gif多选;支持视频和照片一起选
|
||||
1.8.1 新增2个代理方法,支持由上层来决定相册/照片的显示与否
|
||||
...
|
||||
1.7.7 支持GIF图片的播放和选择
|
||||
1.7.6 支持对共享相册和同步相册的显示
|
||||
1.7.5 允许不进入预览页面直接选择照片
|
||||
1.7.4 支持单选模式下裁剪照片,支持任意矩形和圆形裁剪框
|
||||
1.7.3 优化iCloud照片的显示与选择
|
||||
...
|
||||
1.5.0 可把拍照按钮放在外面;可自定义照片排序方式;Demo页的UI大改版,新增若干开关;
|
||||
...
|
||||
1.4.5 性能大幅提升(性能测试截图请去博客查看);可在照片列表页拍照;Demo大幅优化;
|
||||
...
|
||||
|
||||
## 七. Common links 常用链接
|
||||
1. Json diff online: https://www.jsondiffonline.com/
|
||||
|
||||
27
Pods/TZImagePickerController/TZImagePickerController/Location/TZLocationManager.h
generated
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// TZLocationManager.h
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by 谭真 on 2017/06/03.
|
||||
// Copyright © 2017年 谭真. All rights reserved.
|
||||
// 定位管理类
|
||||
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreLocation/CoreLocation.h>
|
||||
|
||||
@interface TZLocationManager : NSObject
|
||||
|
||||
+ (instancetype)manager NS_SWIFT_NAME(default());
|
||||
|
||||
/// 开始定位
|
||||
- (void)startLocation;
|
||||
- (void)startLocationWithSuccessBlock:(void (^)(NSArray<CLLocation *> *))successBlock failureBlock:(void (^)(NSError *error))failureBlock;
|
||||
- (void)startLocationWithGeocoderBlock:(void (^)(NSArray *geocoderArray))geocoderBlock;
|
||||
- (void)startLocationWithSuccessBlock:(void (^)(NSArray<CLLocation *> *))successBlock failureBlock:(void (^)(NSError *error))failureBlock geocoderBlock:(void (^)(NSArray *geocoderArray))geocoderBlock;
|
||||
|
||||
/// 结束定位
|
||||
- (void)stopUpdatingLocation;
|
||||
|
||||
@end
|
||||
|
||||
90
Pods/TZImagePickerController/TZImagePickerController/Location/TZLocationManager.m
generated
Normal file
@@ -0,0 +1,90 @@
|
||||
//
|
||||
// TZLocationManager.m
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by 谭真 on 2017/06/03.
|
||||
// Copyright © 2017年 谭真. All rights reserved.
|
||||
// 定位管理类
|
||||
|
||||
#import "TZLocationManager.h"
|
||||
|
||||
@interface TZLocationManager ()<CLLocationManagerDelegate>
|
||||
@property (nonatomic, strong) CLLocationManager *locationManager;
|
||||
/// 定位成功的回调block
|
||||
@property (nonatomic, copy) void (^successBlock)(NSArray<CLLocation *> *);
|
||||
/// 编码成功的回调block
|
||||
@property (nonatomic, copy) void (^geocodeBlock)(NSArray *geocodeArray);
|
||||
/// 定位失败的回调block
|
||||
@property (nonatomic, copy) void (^failureBlock)(NSError *error);
|
||||
@end
|
||||
|
||||
@implementation TZLocationManager
|
||||
|
||||
+ (instancetype)manager {
|
||||
static TZLocationManager *manager;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
manager = [[self alloc] init];
|
||||
manager.locationManager = [[CLLocationManager alloc] init];
|
||||
manager.locationManager.delegate = manager;
|
||||
[manager.locationManager requestWhenInUseAuthorization];
|
||||
});
|
||||
return manager;
|
||||
}
|
||||
|
||||
- (void)startLocation {
|
||||
[self startLocationWithSuccessBlock:nil failureBlock:nil geocoderBlock:nil];
|
||||
}
|
||||
|
||||
- (void)startLocationWithSuccessBlock:(void (^)(NSArray<CLLocation *> *))successBlock failureBlock:(void (^)(NSError *error))failureBlock {
|
||||
[self startLocationWithSuccessBlock:successBlock failureBlock:failureBlock geocoderBlock:nil];
|
||||
}
|
||||
|
||||
- (void)startLocationWithGeocoderBlock:(void (^)(NSArray *geocoderArray))geocoderBlock {
|
||||
[self startLocationWithSuccessBlock:nil failureBlock:nil geocoderBlock:geocoderBlock];
|
||||
}
|
||||
|
||||
- (void)startLocationWithSuccessBlock:(void (^)(NSArray<CLLocation *> *))successBlock failureBlock:(void (^)(NSError *error))failureBlock geocoderBlock:(void (^)(NSArray *geocoderArray))geocoderBlock {
|
||||
[self.locationManager startUpdatingLocation];
|
||||
_successBlock = successBlock;
|
||||
_geocodeBlock = geocoderBlock;
|
||||
_failureBlock = failureBlock;
|
||||
}
|
||||
|
||||
- (void)stopUpdatingLocation {
|
||||
[self.locationManager stopUpdatingLocation];
|
||||
}
|
||||
|
||||
#pragma mark - CLLocationManagerDelegate
|
||||
|
||||
/// 地理位置发生改变时触发
|
||||
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
|
||||
[manager stopUpdatingLocation];
|
||||
|
||||
if (_successBlock) {
|
||||
_successBlock(locations);
|
||||
}
|
||||
|
||||
if (_geocodeBlock && locations.count) {
|
||||
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
|
||||
[geocoder reverseGeocodeLocation:[locations firstObject] completionHandler:^(NSArray *array, NSError *error) {
|
||||
self->_geocodeBlock(array);
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
/// 定位失败回调方法
|
||||
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
|
||||
NSLog(@"定位失败, 错误: %@",error);
|
||||
switch([error code]) {
|
||||
case kCLErrorDenied: { // 用户禁止了定位权限
|
||||
|
||||
} break;
|
||||
default: break;
|
||||
}
|
||||
if (_failureBlock) {
|
||||
_failureBlock(error);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
19
Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/NSBundle+TZImagePicker.h
generated
Executable file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// NSBundle+TZImagePicker.h
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by 谭真 on 16/08/18.
|
||||
// Copyright © 2016年 谭真. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface NSBundle (TZImagePicker)
|
||||
|
||||
+ (NSBundle *)tz_imagePickerBundle;
|
||||
|
||||
+ (NSString *)tz_localizedStringForKey:(NSString *)key value:(NSString *)value;
|
||||
+ (NSString *)tz_localizedStringForKey:(NSString *)key;
|
||||
|
||||
@end
|
||||
|
||||
35
Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/NSBundle+TZImagePicker.m
generated
Executable file
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// NSBundle+TZImagePicker.m
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by 谭真 on 16/08/18.
|
||||
// Copyright © 2016年 谭真. All rights reserved.
|
||||
//
|
||||
|
||||
#import "NSBundle+TZImagePicker.h"
|
||||
#import "TZImagePickerController.h"
|
||||
|
||||
@implementation NSBundle (TZImagePicker)
|
||||
|
||||
+ (NSBundle *)tz_imagePickerBundle {
|
||||
#ifdef SWIFT_PACKAGE
|
||||
NSBundle *bundle = SWIFTPM_MODULE_BUNDLE;
|
||||
#else
|
||||
NSBundle *bundle = [NSBundle bundleForClass:[TZImagePickerController class]];
|
||||
#endif
|
||||
NSURL *url = [bundle URLForResource:@"TZImagePickerController" withExtension:@"bundle"];
|
||||
bundle = [NSBundle bundleWithURL:url];
|
||||
return bundle;
|
||||
}
|
||||
|
||||
+ (NSString *)tz_localizedStringForKey:(NSString *)key {
|
||||
return [self tz_localizedStringForKey:key value:@""];
|
||||
}
|
||||
|
||||
+ (NSString *)tz_localizedStringForKey:(NSString *)key value:(NSString *)value {
|
||||
NSBundle *bundle = [TZImagePickerConfig sharedInstance].languageBundle;
|
||||
NSString *value1 = [bundle localizedStringForKey:key value:value table:nil];
|
||||
return value1;
|
||||
}
|
||||
|
||||
@end
|
||||
61
Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZAssetCell.h
generated
Normal file
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// TZAssetCell.h
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by 谭真 on 15/12/24.
|
||||
// Copyright © 2015年 谭真. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <Photos/Photos.h>
|
||||
|
||||
typedef enum : NSUInteger {
|
||||
TZAssetCellTypePhoto = 0,
|
||||
TZAssetCellTypeLivePhoto,
|
||||
TZAssetCellTypePhotoGif,
|
||||
TZAssetCellTypeVideo,
|
||||
TZAssetCellTypeAudio,
|
||||
} TZAssetCellType;
|
||||
|
||||
@class TZAssetModel;
|
||||
@interface TZAssetCell : UICollectionViewCell
|
||||
@property (weak, nonatomic) UIButton *selectPhotoButton;
|
||||
@property (weak, nonatomic) UIButton *cannotSelectLayerButton;
|
||||
@property (nonatomic, strong) TZAssetModel *model;
|
||||
@property (assign, nonatomic) NSInteger index;
|
||||
@property (nonatomic, copy) void (^didSelectPhotoBlock)(BOOL);
|
||||
@property (nonatomic, assign) TZAssetCellType type;
|
||||
@property (nonatomic, assign) BOOL allowPickingGif;
|
||||
@property (nonatomic, assign) BOOL allowPickingMultipleVideo;
|
||||
@property (nonatomic, copy) NSString *representedAssetIdentifier;
|
||||
@property (nonatomic, assign) int32_t imageRequestID;
|
||||
|
||||
@property (nonatomic, strong) UIImage *photoSelImage;
|
||||
@property (nonatomic, strong) UIImage *photoDefImage;
|
||||
|
||||
@property (nonatomic, assign) BOOL showSelectBtn;
|
||||
@property (assign, nonatomic) BOOL allowPreview;
|
||||
|
||||
@property (nonatomic, copy) void (^assetCellDidSetModelBlock)(TZAssetCell *cell, UIImageView *imageView, UIImageView *selectImageView, UILabel *indexLabel, UIView *bottomView, UILabel *timeLength, UIImageView *videoImgView);
|
||||
@property (nonatomic, copy) void (^assetCellDidLayoutSubviewsBlock)(TZAssetCell *cell, UIImageView *imageView, UIImageView *selectImageView, UILabel *indexLabel, UIView *bottomView, UILabel *timeLength, UIImageView *videoImgView);
|
||||
@end
|
||||
|
||||
|
||||
@class TZAlbumModel;
|
||||
@interface TZAlbumCell : UITableViewCell
|
||||
@property (nonatomic, strong) TZAlbumModel *model;
|
||||
@property (weak, nonatomic) UIButton *selectedCountButton;
|
||||
|
||||
@property (nonatomic, copy) void (^albumCellDidSetModelBlock)(TZAlbumCell *cell, UIImageView *posterImageView, UILabel *titleLabel);
|
||||
@property (nonatomic, copy) void (^albumCellDidLayoutSubviewsBlock)(TZAlbumCell *cell, UIImageView *posterImageView, UILabel *titleLabel);
|
||||
@end
|
||||
|
||||
|
||||
@interface TZAssetCameraCell : UICollectionViewCell
|
||||
@property (nonatomic, strong) UIImageView *imageView;
|
||||
@end
|
||||
|
||||
|
||||
@interface TZAssetAddMoreCell : TZAssetCameraCell
|
||||
@property (nonatomic, strong) UILabel *tipLabel;
|
||||
@end
|
||||
548
Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZAssetCell.m
generated
Normal file
@@ -0,0 +1,548 @@
|
||||
//
|
||||
// TZAssetCell.m
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by 谭真 on 15/12/24.
|
||||
// Copyright © 2015年 谭真. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TZAssetCell.h"
|
||||
#import "TZAssetModel.h"
|
||||
#import "UIView+TZLayout.h"
|
||||
#import "TZImageManager.h"
|
||||
#import "TZImagePickerController.h"
|
||||
#import "TZProgressView.h"
|
||||
|
||||
@interface TZAssetCell ()
|
||||
@property (weak, nonatomic) UIImageView *imageView; // The photo / 照片
|
||||
@property (weak, nonatomic) UIImageView *selectImageView;
|
||||
@property (weak, nonatomic) UILabel *indexLabel;
|
||||
@property (weak, nonatomic) UIView *bottomView;
|
||||
@property (weak, nonatomic) UILabel *timeLength;
|
||||
@property (strong, nonatomic) UITapGestureRecognizer *tapGesture;
|
||||
|
||||
@property (nonatomic, weak) UIImageView *videoImgView;
|
||||
@property (nonatomic, strong) TZProgressView *progressView;
|
||||
@property (nonatomic, assign) int32_t bigImageRequestID;
|
||||
@end
|
||||
|
||||
@implementation TZAssetCell
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reload:) name:@"TZ_PHOTO_PICKER_RELOAD_NOTIFICATION" object:nil];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setModel:(TZAssetModel *)model {
|
||||
_model = model;
|
||||
self.representedAssetIdentifier = model.asset.localIdentifier;
|
||||
int32_t imageRequestID = [[TZImageManager manager] getPhotoWithAsset:model.asset photoWidth:self.tz_width completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) {
|
||||
// Set the cell's thumbnail image if it's still showing the same asset.
|
||||
if ([self.representedAssetIdentifier isEqualToString:model.asset.localIdentifier]) {
|
||||
self.imageView.image = photo;
|
||||
[self setNeedsLayout];
|
||||
} else {
|
||||
// NSLog(@"this cell is showing other asset");
|
||||
[[PHImageManager defaultManager] cancelImageRequest:self.imageRequestID];
|
||||
}
|
||||
if (!isDegraded) {
|
||||
[self hideProgressView];
|
||||
self.imageRequestID = 0;
|
||||
}
|
||||
} progressHandler:nil networkAccessAllowed:NO];
|
||||
if (imageRequestID && self.imageRequestID && imageRequestID != self.imageRequestID) {
|
||||
[[PHImageManager defaultManager] cancelImageRequest:self.imageRequestID];
|
||||
// NSLog(@"cancelImageRequest %d",self.imageRequestID);
|
||||
}
|
||||
self.imageRequestID = imageRequestID;
|
||||
self.selectPhotoButton.selected = model.isSelected;
|
||||
self.selectImageView.image = self.selectPhotoButton.isSelected ? self.photoSelImage : self.photoDefImage;
|
||||
self.indexLabel.hidden = !self.selectPhotoButton.isSelected;
|
||||
|
||||
self.type = (NSInteger)model.type;
|
||||
// 让宽度/高度小于 最小可选照片尺寸 的图片不能选中
|
||||
if (![[TZImageManager manager] isPhotoSelectableWithAsset:model.asset]) {
|
||||
if (_selectImageView.hidden == NO) {
|
||||
self.selectPhotoButton.hidden = YES;
|
||||
_selectImageView.hidden = YES;
|
||||
}
|
||||
}
|
||||
// 如果用户选中了该图片,提前获取一下大图
|
||||
if (model.isSelected) {
|
||||
[self requestBigImage];
|
||||
} else {
|
||||
[self cancelBigImageRequest];
|
||||
}
|
||||
[self setNeedsLayout];
|
||||
|
||||
if (self.assetCellDidSetModelBlock) {
|
||||
self.assetCellDidSetModelBlock(self, _imageView, _selectImageView, _indexLabel, _bottomView, _timeLength, _videoImgView);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setIndex:(NSInteger)index {
|
||||
_index = index;
|
||||
self.indexLabel.text = [NSString stringWithFormat:@"%zd", index];
|
||||
[self.contentView bringSubviewToFront:self.indexLabel];
|
||||
}
|
||||
|
||||
- (void)setShowSelectBtn:(BOOL)showSelectBtn {
|
||||
_showSelectBtn = showSelectBtn;
|
||||
BOOL selectable = [[TZImageManager manager] isPhotoSelectableWithAsset:self.model.asset];
|
||||
if (!self.selectPhotoButton.hidden) {
|
||||
self.selectPhotoButton.hidden = !showSelectBtn || !selectable;
|
||||
}
|
||||
if (!self.selectImageView.hidden) {
|
||||
self.selectImageView.hidden = !showSelectBtn || !selectable;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setType:(TZAssetCellType)type {
|
||||
_type = type;
|
||||
if (type == TZAssetCellTypePhoto || type == TZAssetCellTypeLivePhoto || (type == TZAssetCellTypePhotoGif && !self.allowPickingGif) || self.allowPickingMultipleVideo) {
|
||||
_selectImageView.hidden = NO;
|
||||
_selectPhotoButton.hidden = NO;
|
||||
_bottomView.hidden = YES;
|
||||
} else { // Video of Gif
|
||||
_selectImageView.hidden = YES;
|
||||
_selectPhotoButton.hidden = YES;
|
||||
}
|
||||
|
||||
if (type == TZAssetCellTypeVideo) {
|
||||
self.bottomView.hidden = NO;
|
||||
self.timeLength.text = _model.timeLength;
|
||||
self.videoImgView.hidden = NO;
|
||||
_timeLength.tz_left = self.videoImgView.tz_right;
|
||||
_timeLength.textAlignment = NSTextAlignmentRight;
|
||||
} else if (type == TZAssetCellTypePhotoGif && self.allowPickingGif) {
|
||||
self.bottomView.hidden = NO;
|
||||
self.timeLength.text = @"GIF";
|
||||
self.videoImgView.hidden = YES;
|
||||
_timeLength.tz_left = 5;
|
||||
_timeLength.textAlignment = NSTextAlignmentLeft;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setAllowPreview:(BOOL)allowPreview {
|
||||
_allowPreview = allowPreview;
|
||||
if (allowPreview) {
|
||||
_imageView.userInteractionEnabled = NO;
|
||||
_tapGesture.enabled = NO;
|
||||
} else {
|
||||
_imageView.userInteractionEnabled = YES;
|
||||
_tapGesture.enabled = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)selectPhotoButtonClick:(UIButton *)sender {
|
||||
if (self.didSelectPhotoBlock) {
|
||||
self.didSelectPhotoBlock(sender.isSelected);
|
||||
}
|
||||
self.selectImageView.image = sender.isSelected ? self.photoSelImage : self.photoDefImage;
|
||||
if (sender.isSelected) {
|
||||
[UIView showOscillatoryAnimationWithLayer:_selectImageView.layer type:TZOscillatoryAnimationToBigger];
|
||||
// 用户选中了该图片,提前获取一下大图
|
||||
[self requestBigImage];
|
||||
} else { // 取消选中,取消大图的获取
|
||||
[self cancelBigImageRequest];
|
||||
}
|
||||
}
|
||||
|
||||
/// 只在单选状态且allowPreview为NO时会有该事件
|
||||
- (void)didTapImageView {
|
||||
if (self.didSelectPhotoBlock) {
|
||||
self.didSelectPhotoBlock(NO);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)hideProgressView {
|
||||
if (_progressView) {
|
||||
self.progressView.hidden = YES;
|
||||
self.imageView.alpha = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)requestBigImage {
|
||||
if (_bigImageRequestID) {
|
||||
[[PHImageManager defaultManager] cancelImageRequest:_bigImageRequestID];
|
||||
}
|
||||
|
||||
_bigImageRequestID = [[TZImageManager manager] requestImageDataForAsset:_model.asset completion:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
|
||||
BOOL iCloudSyncFailed = !imageData && [TZCommonTools isICloudSyncError:info[PHImageErrorKey]];
|
||||
self.model.iCloudFailed = iCloudSyncFailed;
|
||||
if (iCloudSyncFailed && self.didSelectPhotoBlock) {
|
||||
self.didSelectPhotoBlock(YES);
|
||||
self.selectImageView.image = self.photoDefImage;
|
||||
}
|
||||
[self hideProgressView];
|
||||
} progressHandler:^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
|
||||
if (self.model.isSelected) {
|
||||
progress = progress > 0.02 ? progress : 0.02;;
|
||||
self.progressView.progress = progress;
|
||||
self.progressView.hidden = NO;
|
||||
self.imageView.alpha = 0.4;
|
||||
if (progress >= 1) {
|
||||
[self hideProgressView];
|
||||
}
|
||||
} else {
|
||||
// 快速连续点几次,会EXC_BAD_ACCESS...
|
||||
// *stop = YES;
|
||||
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
|
||||
[self cancelBigImageRequest];
|
||||
}
|
||||
}];
|
||||
if (_model.type == TZAssetCellTypeVideo) {
|
||||
[[TZImageManager manager] getVideoWithAsset:_model.asset completion:^(AVPlayerItem *playerItem, NSDictionary *info) {
|
||||
BOOL iCloudSyncFailed = !playerItem && [TZCommonTools isICloudSyncError:info[PHImageErrorKey]];
|
||||
self.model.iCloudFailed = iCloudSyncFailed;
|
||||
if (iCloudSyncFailed && self.didSelectPhotoBlock) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.didSelectPhotoBlock(YES);
|
||||
self.selectImageView.image = self.photoDefImage;
|
||||
});
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)cancelBigImageRequest {
|
||||
if (_bigImageRequestID) {
|
||||
[[PHImageManager defaultManager] cancelImageRequest:_bigImageRequestID];
|
||||
}
|
||||
[self hideProgressView];
|
||||
}
|
||||
|
||||
#pragma mark - Notification
|
||||
|
||||
- (void)reload:(NSNotification *)noti {
|
||||
TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)noti.object;
|
||||
|
||||
UIViewController *parentViewController = nil;
|
||||
UIResponder *responder = self.nextResponder;
|
||||
do {
|
||||
if ([responder isKindOfClass:[UIViewController class]]) {
|
||||
parentViewController = (UIViewController *)responder;
|
||||
break;
|
||||
}
|
||||
responder = responder.nextResponder;
|
||||
} while (responder);
|
||||
|
||||
if (parentViewController.navigationController != tzImagePickerVc) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.model.isSelected && tzImagePickerVc.showSelectedIndex) {
|
||||
self.index = [tzImagePickerVc.selectedAssetIds indexOfObject:self.model.asset.localIdentifier] + 1;
|
||||
}
|
||||
self.indexLabel.hidden = !self.selectPhotoButton.isSelected;
|
||||
BOOL notSelectable = [TZCommonTools isAssetNotSelectable:self.model tzImagePickerVc:tzImagePickerVc];
|
||||
if (notSelectable && tzImagePickerVc.showPhotoCannotSelectLayer && !self.model.isSelected) {
|
||||
self.cannotSelectLayerButton.backgroundColor = tzImagePickerVc.cannotSelectLayerColor;
|
||||
self.cannotSelectLayerButton.hidden = NO;
|
||||
} else {
|
||||
self.cannotSelectLayerButton.hidden = YES;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Lazy load
|
||||
|
||||
- (UIButton *)selectPhotoButton {
|
||||
if (_selectPhotoButton == nil) {
|
||||
UIButton *selectPhotoButton = [[UIButton alloc] init];
|
||||
[selectPhotoButton addTarget:self action:@selector(selectPhotoButtonClick:) forControlEvents:UIControlEventTouchUpInside];
|
||||
[self.contentView addSubview:selectPhotoButton];
|
||||
_selectPhotoButton = selectPhotoButton;
|
||||
}
|
||||
return _selectPhotoButton;
|
||||
}
|
||||
|
||||
- (UIImageView *)imageView {
|
||||
if (_imageView == nil) {
|
||||
UIImageView *imageView = [[UIImageView alloc] init];
|
||||
imageView.contentMode = UIViewContentModeScaleAspectFill;
|
||||
imageView.clipsToBounds = YES;
|
||||
[self.contentView addSubview:imageView];
|
||||
_imageView = imageView;
|
||||
|
||||
_tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTapImageView)];
|
||||
[_imageView addGestureRecognizer:_tapGesture];
|
||||
self.allowPreview = self.allowPreview;
|
||||
}
|
||||
return _imageView;
|
||||
}
|
||||
|
||||
- (UIImageView *)selectImageView {
|
||||
if (_selectImageView == nil) {
|
||||
UIImageView *selectImageView = [[UIImageView alloc] init];
|
||||
selectImageView.contentMode = UIViewContentModeCenter;
|
||||
selectImageView.clipsToBounds = YES;
|
||||
[self.contentView addSubview:selectImageView];
|
||||
_selectImageView = selectImageView;
|
||||
}
|
||||
return _selectImageView;
|
||||
}
|
||||
|
||||
- (UIView *)bottomView {
|
||||
if (_bottomView == nil) {
|
||||
UIView *bottomView = [[UIView alloc] init];
|
||||
static NSInteger rgb = 0;
|
||||
bottomView.userInteractionEnabled = NO;
|
||||
bottomView.backgroundColor = [UIColor colorWithRed:rgb green:rgb blue:rgb alpha:0.8];
|
||||
[self.contentView addSubview:bottomView];
|
||||
_bottomView = bottomView;
|
||||
}
|
||||
return _bottomView;
|
||||
}
|
||||
|
||||
- (UIButton *)cannotSelectLayerButton {
|
||||
if (_cannotSelectLayerButton == nil) {
|
||||
UIButton *cannotSelectLayerButton = [[UIButton alloc] init];
|
||||
[self.contentView addSubview:cannotSelectLayerButton];
|
||||
_cannotSelectLayerButton = cannotSelectLayerButton;
|
||||
}
|
||||
return _cannotSelectLayerButton;
|
||||
}
|
||||
|
||||
- (UIImageView *)videoImgView {
|
||||
if (_videoImgView == nil) {
|
||||
UIImageView *videoImgView = [[UIImageView alloc] init];
|
||||
[videoImgView setImage:[UIImage tz_imageNamedFromMyBundle:@"VideoSendIcon"]];
|
||||
[self.bottomView addSubview:videoImgView];
|
||||
_videoImgView = videoImgView;
|
||||
}
|
||||
return _videoImgView;
|
||||
}
|
||||
|
||||
- (UILabel *)timeLength {
|
||||
if (_timeLength == nil) {
|
||||
UILabel *timeLength = [[UILabel alloc] init];
|
||||
timeLength.font = [UIFont boldSystemFontOfSize:11];
|
||||
timeLength.textColor = [UIColor whiteColor];
|
||||
timeLength.textAlignment = NSTextAlignmentRight;
|
||||
[self.bottomView addSubview:timeLength];
|
||||
_timeLength = timeLength;
|
||||
}
|
||||
return _timeLength;
|
||||
}
|
||||
|
||||
- (UILabel *)indexLabel {
|
||||
if (_indexLabel == nil) {
|
||||
UILabel *indexLabel = [[UILabel alloc] init];
|
||||
indexLabel.font = [UIFont systemFontOfSize:14];
|
||||
indexLabel.adjustsFontSizeToFitWidth = YES;
|
||||
indexLabel.textColor = [UIColor whiteColor];
|
||||
indexLabel.textAlignment = NSTextAlignmentCenter;
|
||||
[self.contentView addSubview:indexLabel];
|
||||
_indexLabel = indexLabel;
|
||||
}
|
||||
return _indexLabel;
|
||||
}
|
||||
|
||||
- (TZProgressView *)progressView {
|
||||
if (_progressView == nil) {
|
||||
_progressView = [[TZProgressView alloc] init];
|
||||
_progressView.hidden = YES;
|
||||
[self addSubview:_progressView];
|
||||
}
|
||||
return _progressView;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
_cannotSelectLayerButton.frame = self.bounds;
|
||||
if (self.allowPreview) {
|
||||
_selectPhotoButton.frame = CGRectMake(self.tz_width - 44, 0, 44, 44);
|
||||
} else {
|
||||
_selectPhotoButton.frame = self.bounds;
|
||||
}
|
||||
_selectImageView.frame = CGRectMake(self.tz_width - 27, 3, 24, 24);
|
||||
if (_selectImageView.image.size.width <= 27) {
|
||||
_selectImageView.contentMode = UIViewContentModeCenter;
|
||||
} else {
|
||||
_selectImageView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
}
|
||||
_indexLabel.frame = _selectImageView.frame;
|
||||
_imageView.frame = self.bounds;
|
||||
|
||||
static CGFloat progressWH = 20;
|
||||
CGFloat progressXY = (self.tz_width - progressWH) / 2;
|
||||
_progressView.frame = CGRectMake(progressXY, progressXY, progressWH, progressWH);
|
||||
|
||||
_bottomView.frame = CGRectMake(0, self.tz_height - 17, self.tz_width, 17);
|
||||
_videoImgView.frame = CGRectMake(8, 0, 17, 17);
|
||||
_timeLength.frame = CGRectMake(self.videoImgView.tz_right, 0, self.tz_width - self.videoImgView.tz_right - 5, 17);
|
||||
|
||||
self.type = (NSInteger)self.model.type;
|
||||
self.showSelectBtn = self.showSelectBtn;
|
||||
|
||||
[self.contentView bringSubviewToFront:_bottomView];
|
||||
[self.contentView bringSubviewToFront:_cannotSelectLayerButton];
|
||||
[self.contentView bringSubviewToFront:_selectPhotoButton];
|
||||
[self.contentView bringSubviewToFront:_selectImageView];
|
||||
[self.contentView bringSubviewToFront:_indexLabel];
|
||||
|
||||
if (self.assetCellDidLayoutSubviewsBlock) {
|
||||
self.assetCellDidLayoutSubviewsBlock(self, _imageView, _selectImageView, _indexLabel, _bottomView, _timeLength, _videoImgView);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface TZAlbumCell ()
|
||||
@property (weak, nonatomic) UIImageView *posterImageView;
|
||||
@property (weak, nonatomic) UILabel *titleLabel;
|
||||
@end
|
||||
|
||||
@implementation TZAlbumCell
|
||||
|
||||
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
|
||||
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
|
||||
self.backgroundColor = [UIColor whiteColor];
|
||||
self.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setModel:(TZAlbumModel *)model {
|
||||
_model = model;
|
||||
|
||||
UIColor *nameColor = UIColor.blackColor;
|
||||
if (@available(iOS 13.0, *)) {
|
||||
nameColor = UIColor.labelColor;
|
||||
}
|
||||
NSMutableAttributedString *nameString = [[NSMutableAttributedString alloc] initWithString:model.name attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:16],NSForegroundColorAttributeName:nameColor}];
|
||||
NSAttributedString *countString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@" (%zd)",model.count] attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:16],NSForegroundColorAttributeName:[UIColor lightGrayColor]}];
|
||||
[nameString appendAttributedString:countString];
|
||||
self.titleLabel.attributedText = nameString;
|
||||
[[TZImageManager manager] getPostImageWithAlbumModel:model completion:^(UIImage *postImage) {
|
||||
self.posterImageView.image = postImage;
|
||||
[self setNeedsLayout];
|
||||
}];
|
||||
if (model.selectedCount) {
|
||||
self.selectedCountButton.hidden = NO;
|
||||
[self.selectedCountButton setTitle:[NSString stringWithFormat:@"%zd",model.selectedCount] forState:UIControlStateNormal];
|
||||
} else {
|
||||
self.selectedCountButton.hidden = YES;
|
||||
}
|
||||
|
||||
if (self.albumCellDidSetModelBlock) {
|
||||
self.albumCellDidSetModelBlock(self, _posterImageView, _titleLabel);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
_selectedCountButton.frame = CGRectMake(self.contentView.tz_width - 24, 23, 24, 24);
|
||||
NSInteger titleHeight = ceil(self.titleLabel.font.lineHeight);
|
||||
self.titleLabel.frame = CGRectMake(80, (self.tz_height - titleHeight) / 2, self.tz_width - 80 - 50, titleHeight);
|
||||
self.posterImageView.frame = CGRectMake(0, 0, 70, 70);
|
||||
|
||||
if (self.albumCellDidLayoutSubviewsBlock) {
|
||||
self.albumCellDidLayoutSubviewsBlock(self, _posterImageView, _titleLabel);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)layoutSublayersOfLayer:(CALayer *)layer {
|
||||
[super layoutSublayersOfLayer:layer];
|
||||
}
|
||||
|
||||
#pragma mark - Lazy load
|
||||
|
||||
- (UIImageView *)posterImageView {
|
||||
if (_posterImageView == nil) {
|
||||
UIImageView *posterImageView = [[UIImageView alloc] init];
|
||||
posterImageView.contentMode = UIViewContentModeScaleAspectFill;
|
||||
posterImageView.clipsToBounds = YES;
|
||||
[self.contentView addSubview:posterImageView];
|
||||
_posterImageView = posterImageView;
|
||||
}
|
||||
return _posterImageView;
|
||||
}
|
||||
|
||||
- (UILabel *)titleLabel {
|
||||
if (_titleLabel == nil) {
|
||||
UILabel *titleLabel = [[UILabel alloc] init];
|
||||
titleLabel.font = [UIFont boldSystemFontOfSize:17];
|
||||
if (@available(iOS 13.0, *)) {
|
||||
titleLabel.textColor = UIColor.labelColor;
|
||||
} else {
|
||||
titleLabel.textColor = [UIColor blackColor];
|
||||
}
|
||||
titleLabel.textAlignment = NSTextAlignmentLeft;
|
||||
[self.contentView addSubview:titleLabel];
|
||||
_titleLabel = titleLabel;
|
||||
}
|
||||
return _titleLabel;
|
||||
}
|
||||
|
||||
- (UIButton *)selectedCountButton {
|
||||
if (_selectedCountButton == nil) {
|
||||
UIButton *selectedCountButton = [[UIButton alloc] init];
|
||||
selectedCountButton.titleLabel.adjustsFontSizeToFitWidth = YES;
|
||||
selectedCountButton.layer.cornerRadius = 12;
|
||||
selectedCountButton.clipsToBounds = YES;
|
||||
selectedCountButton.backgroundColor = [UIColor redColor];
|
||||
[selectedCountButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
||||
selectedCountButton.titleLabel.font = [UIFont systemFontOfSize:15];
|
||||
[self.contentView addSubview:selectedCountButton];
|
||||
_selectedCountButton = selectedCountButton;
|
||||
}
|
||||
return _selectedCountButton;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@implementation TZAssetCameraCell
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.backgroundColor = [UIColor whiteColor];
|
||||
_imageView = [[UIImageView alloc] init];
|
||||
_imageView.backgroundColor = [UIColor colorWithWhite:1.000 alpha:0.500];
|
||||
_imageView.contentMode = UIViewContentModeScaleAspectFill;
|
||||
[self.contentView addSubview:_imageView];
|
||||
self.clipsToBounds = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
_imageView.frame = self.bounds;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation TZAssetAddMoreCell
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
_tipLabel = [[UILabel alloc] init];
|
||||
_tipLabel.numberOfLines = 2;
|
||||
_tipLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_tipLabel.font = [UIFont systemFontOfSize:12];
|
||||
_tipLabel.lineBreakMode = NSLineBreakByTruncatingMiddle;
|
||||
CGFloat rgb = 156 / 255.0;
|
||||
_tipLabel.textColor = [UIColor colorWithRed:rgb green:rgb blue:rgb alpha:1.0];
|
||||
[self.contentView addSubview:_tipLabel];
|
||||
|
||||
self.clipsToBounds = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
_tipLabel.frame = CGRectMake(5, self.tz_height / 2, self.tz_width - 10, self.tz_height / 2 - 5);
|
||||
}
|
||||
|
||||
@end
|
||||
56
Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZAssetModel.h
generated
Executable file
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// TZAssetModel.h
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by 谭真 on 15/12/24.
|
||||
// Copyright © 2015年 谭真. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <Photos/Photos.h>
|
||||
|
||||
typedef enum : NSUInteger {
|
||||
TZAssetModelMediaTypePhoto = 0,
|
||||
TZAssetModelMediaTypeLivePhoto,
|
||||
TZAssetModelMediaTypePhotoGif,
|
||||
TZAssetModelMediaTypeVideo,
|
||||
TZAssetModelMediaTypeAudio
|
||||
} TZAssetModelMediaType;
|
||||
|
||||
@class PHAsset;
|
||||
@interface TZAssetModel : NSObject
|
||||
|
||||
@property (nonatomic, strong) PHAsset *asset;
|
||||
@property (nonatomic, assign) BOOL isSelected; ///< The select status of a photo, default is No
|
||||
@property (nonatomic, assign) TZAssetModelMediaType type;
|
||||
@property (nonatomic, copy) NSString *timeLength;
|
||||
@property (nonatomic, assign) BOOL iCloudFailed;
|
||||
|
||||
/// Init a photo dataModel With a PHAsset
|
||||
/// 用一个PHAsset实例,初始化一个照片模型
|
||||
+ (instancetype)modelWithAsset:(PHAsset *)asset type:(TZAssetModelMediaType)type;
|
||||
+ (instancetype)modelWithAsset:(PHAsset *)asset type:(TZAssetModelMediaType)type timeLength:(NSString *)timeLength;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@class PHFetchResult;
|
||||
@interface TZAlbumModel : NSObject
|
||||
|
||||
@property (nonatomic, strong) NSString *name; ///< The album name
|
||||
@property (nonatomic, assign) NSInteger count; ///< Count of photos the album contain
|
||||
@property (nonatomic, strong) PHFetchResult *result;
|
||||
@property (nonatomic, strong) PHAssetCollection *collection;
|
||||
@property (nonatomic, strong) PHFetchOptions *options;
|
||||
|
||||
@property (nonatomic, strong) NSArray *models;
|
||||
@property (nonatomic, strong) NSArray *selectedModels;
|
||||
@property (nonatomic, assign) NSUInteger selectedCount;
|
||||
|
||||
@property (nonatomic, assign) BOOL isCameraRoll;
|
||||
|
||||
- (void)setResult:(PHFetchResult *)result needFetchAssets:(BOOL)needFetchAssets;
|
||||
- (void)refreshFetchResult;
|
||||
|
||||
@end
|
||||
79
Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZAssetModel.m
generated
Normal file
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// TZAssetModel.m
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by 谭真 on 15/12/24.
|
||||
// Copyright © 2015年 谭真. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TZAssetModel.h"
|
||||
#import "TZImageManager.h"
|
||||
|
||||
@implementation TZAssetModel
|
||||
|
||||
+ (instancetype)modelWithAsset:(PHAsset *)asset type:(TZAssetModelMediaType)type{
|
||||
TZAssetModel *model = [[TZAssetModel alloc] init];
|
||||
model.asset = asset;
|
||||
model.isSelected = NO;
|
||||
model.type = type;
|
||||
return model;
|
||||
}
|
||||
|
||||
+ (instancetype)modelWithAsset:(PHAsset *)asset type:(TZAssetModelMediaType)type timeLength:(NSString *)timeLength {
|
||||
TZAssetModel *model = [self modelWithAsset:asset type:type];
|
||||
model.timeLength = timeLength;
|
||||
return model;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@implementation TZAlbumModel
|
||||
|
||||
- (void)setResult:(PHFetchResult *)result needFetchAssets:(BOOL)needFetchAssets {
|
||||
_result = result;
|
||||
if (needFetchAssets) {
|
||||
[[TZImageManager manager] getAssetsFromFetchResult:result completion:^(NSArray<TZAssetModel *> *models) {
|
||||
self->_models = models;
|
||||
if (self->_selectedModels) {
|
||||
[self checkSelectedModels];
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)refreshFetchResult {
|
||||
PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:self.collection options:self.options];
|
||||
self.count = fetchResult.count;
|
||||
[self setResult:fetchResult];
|
||||
}
|
||||
|
||||
- (void)setSelectedModels:(NSArray *)selectedModels {
|
||||
_selectedModels = selectedModels;
|
||||
if (_models) {
|
||||
[self checkSelectedModels];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)checkSelectedModels {
|
||||
self.selectedCount = 0;
|
||||
NSMutableSet *selectedAssets = [NSMutableSet setWithCapacity:_selectedModels.count];
|
||||
for (TZAssetModel *model in _selectedModels) {
|
||||
[selectedAssets addObject:model.asset];
|
||||
}
|
||||
for (TZAssetModel *model in _models) {
|
||||
if ([selectedAssets containsObject:model.asset]) {
|
||||
self.selectedCount ++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)name {
|
||||
if (_name) {
|
||||
return _name;
|
||||
}
|
||||
return @"";
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// TZAuthLimitedFooterTipView.h
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by qiaoxy on 2021/8/24.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TZAuthLimitedFooterTipView : UIView
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,87 @@
|
||||
//
|
||||
// TZAuthLimitedFooterTipView.m
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by qiaoxy on 2021/8/24.
|
||||
//
|
||||
|
||||
#import "TZAuthLimitedFooterTipView.h"
|
||||
#import "TZImagePickerController.h"
|
||||
|
||||
@interface TZAuthLimitedFooterTipView()
|
||||
@property (nonatomic,strong) UIImageView *tipImgView;
|
||||
@property (nonatomic,strong) UILabel *tipLable;
|
||||
@property (nonatomic,strong) UIImageView *detailImgView;
|
||||
@end
|
||||
|
||||
@implementation TZAuthLimitedFooterTipView
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
[self initSubViews];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self initSubViews];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initSubViews {
|
||||
[self addSubview:self.tipImgView];
|
||||
[self addSubview:self.tipLable];
|
||||
[self addSubview:self.detailImgView];
|
||||
CGFloat margin = 15;
|
||||
CGFloat tipImgViewWH = 20;
|
||||
CGFloat detailImgViewWH = 12;
|
||||
CGFloat screenW = [UIScreen mainScreen].bounds.size.width;
|
||||
|
||||
self.tipImgView.frame = CGRectMake(margin, 0, tipImgViewWH, tipImgViewWH);
|
||||
self.detailImgView.frame = CGRectMake(screenW - margin - detailImgViewWH, 0, detailImgViewWH, detailImgViewWH);
|
||||
|
||||
CGFloat tipLabelX = CGRectGetMaxX(self.tipImgView.frame) + 10;
|
||||
CGFloat tipLabelW = screenW - tipLabelX - detailImgViewWH - margin - 4;
|
||||
self.tipLable.frame = CGRectMake(tipLabelX, 0, tipLabelW, self.bounds.size.height);
|
||||
|
||||
self.tipImgView.center = CGPointMake(self.tipImgView.center.x, self.tipLable.center.y);
|
||||
self.detailImgView.center = CGPointMake(self.detailImgView.center.x, self.tipLable.center.y);
|
||||
}
|
||||
|
||||
#pragma mark - Getter
|
||||
|
||||
- (UIImageView *)tipImgView {
|
||||
if (!_tipImgView) {
|
||||
_tipImgView = [[UIImageView alloc] init];
|
||||
_tipImgView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
_tipImgView.image = [UIImage tz_imageNamedFromMyBundle:@"tip"];
|
||||
}
|
||||
return _tipImgView;
|
||||
}
|
||||
|
||||
- (UILabel *)tipLable {
|
||||
if (!_tipLable) {
|
||||
_tipLable = [[UILabel alloc] init];
|
||||
NSString *appName = [TZCommonTools tz_getAppName];
|
||||
_tipLable.text = [NSString stringWithFormat:[NSBundle tz_localizedStringForKey:@"Allow %@ to access your all photos"], appName];
|
||||
_tipLable.numberOfLines = 0;
|
||||
_tipLable.font = [UIFont systemFontOfSize:14];
|
||||
_tipLable.textColor = [UIColor colorWithRed:0.40 green:0.40 blue:0.40 alpha:1.0];
|
||||
}
|
||||
return _tipLable;
|
||||
}
|
||||
|
||||
- (UIImageView *)detailImgView {
|
||||
if (!_detailImgView) {
|
||||
_detailImgView = [[UIImageView alloc] init];
|
||||
_detailImgView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
_detailImgView.image = [UIImage tz_imageNamedFromMyBundle:@"right_arrow"];
|
||||
}
|
||||
return _detailImgView;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// TZGifPhotoPreviewController.h
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by ttouch on 2016/12/13.
|
||||
// Copyright © 2016年 谭真. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class TZAssetModel;
|
||||
@interface TZGifPhotoPreviewController : UIViewController
|
||||
|
||||
@property (nonatomic, strong) TZAssetModel *model;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,173 @@
|
||||
//
|
||||
// TZGifPhotoPreviewController.m
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by ttouch on 2016/12/13.
|
||||
// Copyright © 2016年 谭真. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TZGifPhotoPreviewController.h"
|
||||
#import "TZImagePickerController.h"
|
||||
#import "TZAssetModel.h"
|
||||
#import "UIView+TZLayout.h"
|
||||
#import "TZPhotoPreviewCell.h"
|
||||
#import "TZImageManager.h"
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
|
||||
@interface TZGifPhotoPreviewController () {
|
||||
UIView *_toolBar;
|
||||
UIButton *_doneButton;
|
||||
UIProgressView *_progress;
|
||||
|
||||
TZPhotoPreviewView *_previewView;
|
||||
|
||||
UIStatusBarStyle _originStatusBarStyle;
|
||||
}
|
||||
@property (assign, nonatomic) BOOL needShowStatusBar;
|
||||
@end
|
||||
|
||||
@implementation TZGifPhotoPreviewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
self.needShowStatusBar = ![UIApplication sharedApplication].statusBarHidden;
|
||||
self.view.backgroundColor = [UIColor blackColor];
|
||||
TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
if (tzImagePickerVc) {
|
||||
self.navigationItem.title = [NSString stringWithFormat:@"GIF %@",tzImagePickerVc.previewBtnTitleStr];
|
||||
}
|
||||
[self configPreviewView];
|
||||
[self configBottomToolBar];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
_originStatusBarStyle = [UIApplication sharedApplication].statusBarStyle;
|
||||
[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
if (self.needShowStatusBar) {
|
||||
[UIApplication sharedApplication].statusBarHidden = NO;
|
||||
}
|
||||
[UIApplication sharedApplication].statusBarStyle = _originStatusBarStyle;
|
||||
}
|
||||
|
||||
- (void)configPreviewView {
|
||||
_previewView = [[TZPhotoPreviewView alloc] initWithFrame:CGRectZero];
|
||||
_previewView.model = self.model;
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[_previewView setSingleTapGestureBlock:^{
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
[strongSelf signleTapAction];
|
||||
}];
|
||||
[self.view addSubview:_previewView];
|
||||
}
|
||||
|
||||
- (void)configBottomToolBar {
|
||||
_toolBar = [[UIView alloc] initWithFrame:CGRectZero];
|
||||
CGFloat rgb = 34 / 255.0;
|
||||
_toolBar.backgroundColor = [UIColor colorWithRed:rgb green:rgb blue:rgb alpha:0.7];
|
||||
|
||||
_doneButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
_doneButton.titleLabel.font = [UIFont systemFontOfSize:16];
|
||||
[_doneButton addTarget:self action:@selector(doneButtonClick) forControlEvents:UIControlEventTouchUpInside];
|
||||
TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
if (tzImagePickerVc) {
|
||||
[_doneButton setTitle:tzImagePickerVc.doneBtnTitleStr forState:UIControlStateNormal];
|
||||
[_doneButton setTitleColor:tzImagePickerVc.oKButtonTitleColorNormal forState:UIControlStateNormal];
|
||||
} else {
|
||||
[_doneButton setTitle:[NSBundle tz_localizedStringForKey:@"Done"] forState:UIControlStateNormal];
|
||||
[_doneButton setTitleColor:[UIColor colorWithRed:(83/255.0) green:(179/255.0) blue:(17/255.0) alpha:1.0] forState:UIControlStateNormal];
|
||||
}
|
||||
[_toolBar addSubview:_doneButton];
|
||||
|
||||
UILabel *byteLabel = [[UILabel alloc] init];
|
||||
byteLabel.textColor = [UIColor whiteColor];
|
||||
byteLabel.font = [UIFont systemFontOfSize:13];
|
||||
byteLabel.frame = CGRectMake(10, 0, 100, 44);
|
||||
[[TZImageManager manager] getPhotosBytesWithArray:@[_model] completion:^(NSString *totalBytes) {
|
||||
byteLabel.text = totalBytes;
|
||||
}];
|
||||
[_toolBar addSubview:byteLabel];
|
||||
|
||||
[self.view addSubview:_toolBar];
|
||||
|
||||
if (tzImagePickerVc.gifPreviewPageUIConfigBlock) {
|
||||
tzImagePickerVc.gifPreviewPageUIConfigBlock(_toolBar, _doneButton);
|
||||
}
|
||||
}
|
||||
|
||||
- (UIStatusBarStyle)preferredStatusBarStyle {
|
||||
TZImagePickerController *tzImagePicker = (TZImagePickerController *)self.navigationController;
|
||||
if (tzImagePicker && [tzImagePicker isKindOfClass:[TZImagePickerController class]]) {
|
||||
return tzImagePicker.statusBarStyle;
|
||||
}
|
||||
return [super preferredStatusBarStyle];
|
||||
}
|
||||
|
||||
#pragma mark - Layout
|
||||
|
||||
- (void)viewDidLayoutSubviews {
|
||||
[super viewDidLayoutSubviews];
|
||||
|
||||
_previewView.frame = self.view.bounds;
|
||||
_previewView.scrollView.frame = self.view.bounds;
|
||||
CGFloat toolBarHeight = 44 + [TZCommonTools tz_safeAreaInsets].bottom;
|
||||
_toolBar.frame = CGRectMake(0, self.view.tz_height - toolBarHeight, self.view.tz_width, toolBarHeight);
|
||||
[_doneButton sizeToFit];
|
||||
_doneButton.frame = CGRectMake(self.view.tz_width - _doneButton.tz_width - 12, 0, MAX(44, _doneButton.tz_width), 44);
|
||||
|
||||
TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
if (tzImagePickerVc.gifPreviewPageDidLayoutSubviewsBlock) {
|
||||
tzImagePickerVc.gifPreviewPageDidLayoutSubviewsBlock(_toolBar, _doneButton);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Click Event
|
||||
|
||||
- (void)signleTapAction {
|
||||
_toolBar.hidden = !_toolBar.isHidden;
|
||||
[self.navigationController setNavigationBarHidden:_toolBar.isHidden];
|
||||
TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
if (_toolBar.isHidden) {
|
||||
[UIApplication sharedApplication].statusBarHidden = YES;
|
||||
} else if (tzImagePickerVc.needShowStatusBar) {
|
||||
[UIApplication sharedApplication].statusBarHidden = NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)doneButtonClick {
|
||||
if (self.navigationController) {
|
||||
TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
if (imagePickerVc.autoDismiss) {
|
||||
[self.navigationController dismissViewControllerAnimated:YES completion:^{
|
||||
[self callDelegateMethod];
|
||||
}];
|
||||
} else {
|
||||
[self callDelegateMethod];
|
||||
}
|
||||
} else {
|
||||
[self dismissViewControllerAnimated:YES completion:^{
|
||||
[self callDelegateMethod];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)callDelegateMethod {
|
||||
TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
UIImage *animatedImage = _previewView.imageView.image;
|
||||
if ([imagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didFinishPickingGifImage:sourceAssets:)]) {
|
||||
[imagePickerVc.pickerDelegate imagePickerController:imagePickerVc didFinishPickingGifImage:animatedImage sourceAssets:_model.asset];
|
||||
}
|
||||
if (imagePickerVc.didFinishPickingGifImageHandle) {
|
||||
imagePickerVc.didFinishPickingGifImageHandle(animatedImage,_model.asset);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
@end
|
||||
39
Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZImageCropManager.h
generated
Normal file
@@ -0,0 +1,39 @@
|
||||
//
|
||||
// TZImageCropManager.h
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by 谭真 on 2016/12/5.
|
||||
// Copyright © 2016年 谭真. All rights reserved.
|
||||
// 图片裁剪管理类
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface TZImageCropManager : NSObject
|
||||
|
||||
/// 裁剪框背景的处理
|
||||
+ (void)overlayClippingWithView:(UIView *)view cropRect:(CGRect)cropRect containerView:(UIView *)containerView needCircleCrop:(BOOL)needCircleCrop;
|
||||
|
||||
/*
|
||||
1.7.2 为了解决多位同学对于图片裁剪的需求,我这两天有空便在研究图片裁剪
|
||||
幸好有tuyou的PhotoTweaks库做参考,裁剪的功能实现起来简单许多
|
||||
该方法和其内部引用的方法基本来自于tuyou的PhotoTweaks库,我做了稍许删减和修改
|
||||
感谢tuyou同学在github开源了优秀的裁剪库PhotoTweaks,表示感谢
|
||||
PhotoTweaks库的github链接:https://github.com/itouch2/PhotoTweaks
|
||||
*/
|
||||
/// 获得裁剪后的图片
|
||||
+ (UIImage *)cropImageView:(UIImageView *)imageView toRect:(CGRect)rect zoomScale:(double)zoomScale containerView:(UIView *)containerView;
|
||||
|
||||
/// 获取圆形图片
|
||||
+ (UIImage *)circularClipImage:(UIImage *)image;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/// 该分类的代码来自SDWebImage:https://github.com/rs/SDWebImage
|
||||
/// 为了防止冲突,我将分类名字和方法名字做了修改
|
||||
@interface UIImage (TZGif)
|
||||
|
||||
+ (UIImage *)sd_tz_animatedGIFWithData:(NSData *)data;
|
||||
|
||||
@end
|
||||
199
Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZImageCropManager.m
generated
Normal file
@@ -0,0 +1,199 @@
|
||||
//
|
||||
// TZImageCropManager.m
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by 谭真 on 2016/12/5.
|
||||
// Copyright © 2016年 谭真. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TZImageCropManager.h"
|
||||
#import "UIView+TZLayout.h"
|
||||
#import <ImageIO/ImageIO.h>
|
||||
#import "TZImageManager.h"
|
||||
#import "TZImagePickerController.h"
|
||||
|
||||
@implementation TZImageCropManager
|
||||
|
||||
/// 裁剪框背景的处理
|
||||
+ (void)overlayClippingWithView:(UIView *)view cropRect:(CGRect)cropRect containerView:(UIView *)containerView needCircleCrop:(BOOL)needCircleCrop {
|
||||
UIBezierPath *path= [UIBezierPath bezierPathWithRect:[UIScreen mainScreen].bounds];
|
||||
CAShapeLayer *layer = [CAShapeLayer layer];
|
||||
if (needCircleCrop) { // 圆形裁剪框
|
||||
[path appendPath:[UIBezierPath bezierPathWithRoundedRect:cropRect cornerRadius:cropRect.size.width / 2]];
|
||||
} else { // 矩形裁剪框
|
||||
[path appendPath:[UIBezierPath bezierPathWithRect:cropRect]];
|
||||
}
|
||||
layer.path = path.CGPath;
|
||||
layer.fillRule = kCAFillRuleEvenOdd;
|
||||
layer.fillColor = [[UIColor blackColor] CGColor];
|
||||
layer.opacity = 0.5;
|
||||
[view.layer addSublayer:layer];
|
||||
}
|
||||
|
||||
/// 获得裁剪后的图片
|
||||
+ (UIImage *)cropImageView:(UIImageView *)imageView toRect:(CGRect)rect zoomScale:(double)zoomScale containerView:(UIView *)containerView {
|
||||
CGAffineTransform transform = CGAffineTransformIdentity;
|
||||
// 平移的处理
|
||||
CGRect imageViewRect = [imageView convertRect:imageView.bounds toView:containerView];
|
||||
CGPoint point = CGPointMake(imageViewRect.origin.x + imageViewRect.size.width / 2, imageViewRect.origin.y + imageViewRect.size.height / 2);
|
||||
CGFloat xMargin = containerView.tz_width - CGRectGetMaxX(rect) - rect.origin.x;
|
||||
CGPoint zeroPoint = CGPointMake((CGRectGetWidth(containerView.frame) - xMargin) / 2, containerView.center.y);
|
||||
CGPoint translation = CGPointMake(point.x - zeroPoint.x, point.y - zeroPoint.y);
|
||||
transform = CGAffineTransformTranslate(transform, translation.x, translation.y);
|
||||
// 缩放的处理
|
||||
transform = CGAffineTransformScale(transform, zoomScale, zoomScale);
|
||||
|
||||
CGImageRef imageRef = [self newTransformedImage:transform
|
||||
sourceImage:imageView.image.CGImage
|
||||
sourceSize:imageView.image.size
|
||||
outputWidth:rect.size.width * [UIScreen mainScreen].scale
|
||||
cropSize:rect.size
|
||||
imageViewSize:imageView.frame.size];
|
||||
UIImage *cropedImage = [UIImage imageWithCGImage:imageRef];
|
||||
cropedImage = [[TZImageManager manager] fixOrientation:cropedImage];
|
||||
CGImageRelease(imageRef);
|
||||
return cropedImage;
|
||||
}
|
||||
|
||||
+ (CGImageRef)newTransformedImage:(CGAffineTransform)transform sourceImage:(CGImageRef)sourceImage sourceSize:(CGSize)sourceSize outputWidth:(CGFloat)outputWidth cropSize:(CGSize)cropSize imageViewSize:(CGSize)imageViewSize {
|
||||
CGImageRef source = [self newScaledImage:sourceImage toSize:sourceSize];
|
||||
|
||||
CGFloat aspect = cropSize.height/cropSize.width;
|
||||
CGSize outputSize = CGSizeMake(outputWidth, outputWidth*aspect);
|
||||
|
||||
CGContextRef context = CGBitmapContextCreate(NULL, outputSize.width, outputSize.height, CGImageGetBitsPerComponent(source), 0, CGImageGetColorSpace(source), CGImageGetBitmapInfo(source));
|
||||
CGContextSetFillColorWithColor(context, [[UIColor clearColor] CGColor]);
|
||||
CGContextFillRect(context, CGRectMake(0, 0, outputSize.width, outputSize.height));
|
||||
|
||||
CGAffineTransform uiCoords = CGAffineTransformMakeScale(outputSize.width / cropSize.width, outputSize.height / cropSize.height);
|
||||
uiCoords = CGAffineTransformTranslate(uiCoords, cropSize.width/2.0, cropSize.height / 2.0);
|
||||
uiCoords = CGAffineTransformScale(uiCoords, 1.0, -1.0);
|
||||
CGContextConcatCTM(context, uiCoords);
|
||||
|
||||
CGContextConcatCTM(context, transform);
|
||||
CGContextScaleCTM(context, 1.0, -1.0);
|
||||
|
||||
CGContextDrawImage(context, CGRectMake(-imageViewSize.width/2, -imageViewSize.height/2.0, imageViewSize.width, imageViewSize.height), source);
|
||||
CGImageRef resultRef = CGBitmapContextCreateImage(context);
|
||||
CGContextRelease(context);
|
||||
CGImageRelease(source);
|
||||
return resultRef;
|
||||
}
|
||||
|
||||
+ (CGImageRef)newScaledImage:(CGImageRef)source toSize:(CGSize)size {
|
||||
CGSize srcSize = size;
|
||||
CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
|
||||
CGContextRef context = CGBitmapContextCreate(NULL, size.width, size.height, 8, 0, rgbColorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
|
||||
CGColorSpaceRelease(rgbColorSpace);
|
||||
|
||||
CGContextSetInterpolationQuality(context, kCGInterpolationNone);
|
||||
CGContextTranslateCTM(context, size.width/2, size.height/2);
|
||||
|
||||
CGContextDrawImage(context, CGRectMake(-srcSize.width/2, -srcSize.height/2, srcSize.width, srcSize.height), source);
|
||||
|
||||
CGImageRef resultRef = CGBitmapContextCreateImage(context);
|
||||
CGContextRelease(context);
|
||||
return resultRef;
|
||||
}
|
||||
|
||||
/// 获取圆形图片
|
||||
+ (UIImage *)circularClipImage:(UIImage *)image {
|
||||
UIGraphicsBeginImageContextWithOptions(image.size, NO, [UIScreen mainScreen].scale);
|
||||
|
||||
CGContextRef ctx = UIGraphicsGetCurrentContext();
|
||||
CGRect rect = CGRectMake(0, 0, image.size.width, image.size.height);
|
||||
CGContextAddEllipseInRect(ctx, rect);
|
||||
CGContextClip(ctx);
|
||||
|
||||
[image drawInRect:rect];
|
||||
UIImage *circleImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
|
||||
UIGraphicsEndImageContext();
|
||||
return circleImage;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation UIImage (TZGif)
|
||||
|
||||
+ (UIImage *)sd_tz_animatedGIFWithData:(NSData *)data {
|
||||
if (!data) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
|
||||
|
||||
size_t count = CGImageSourceGetCount(source);
|
||||
|
||||
UIImage *animatedImage;
|
||||
|
||||
if (count <= 1) {
|
||||
animatedImage = [[UIImage alloc] initWithData:data];
|
||||
}
|
||||
else {
|
||||
// images数组过大时内存会飙升,在这里限制下最大count
|
||||
NSInteger maxCount = [TZImagePickerConfig sharedInstance].gifPreviewMaxImagesCount ?: 50;
|
||||
NSInteger interval = MAX((count + maxCount / 2) / maxCount, 1);
|
||||
|
||||
NSMutableArray *images = [NSMutableArray array];
|
||||
|
||||
NSTimeInterval duration = 0.0f;
|
||||
|
||||
for (size_t i = 0; i < count; i+=interval) {
|
||||
CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, NULL);
|
||||
if (!image) {
|
||||
continue;
|
||||
}
|
||||
|
||||
duration += [self sd_frameDurationAtIndex:i source:source] * MIN(interval, 3);
|
||||
|
||||
[images addObject:[UIImage imageWithCGImage:image scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp]];
|
||||
|
||||
CGImageRelease(image);
|
||||
}
|
||||
|
||||
if (!duration) {
|
||||
duration = (1.0f / 10.0f) * count;
|
||||
}
|
||||
|
||||
animatedImage = [UIImage animatedImageWithImages:images duration:duration];
|
||||
}
|
||||
|
||||
CFRelease(source);
|
||||
|
||||
return animatedImage;
|
||||
}
|
||||
|
||||
+ (float)sd_frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source {
|
||||
float frameDuration = 0.1f;
|
||||
CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil);
|
||||
NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties;
|
||||
NSDictionary *gifProperties = frameProperties[(NSString *)kCGImagePropertyGIFDictionary];
|
||||
|
||||
NSNumber *delayTimeUnclampedProp = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime];
|
||||
if (delayTimeUnclampedProp) {
|
||||
frameDuration = [delayTimeUnclampedProp floatValue];
|
||||
}
|
||||
else {
|
||||
|
||||
NSNumber *delayTimeProp = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime];
|
||||
if (delayTimeProp) {
|
||||
frameDuration = [delayTimeProp floatValue];
|
||||
}
|
||||
}
|
||||
|
||||
// Many annoying ads specify a 0 duration to make an image flash as quickly as possible.
|
||||
// We follow Firefox's behavior and use a duration of 100 ms for any frames that specify
|
||||
// a duration of <= 10 ms. See <rdar://problem/7689300> and <http://webkit.org/b/36082>
|
||||
// for more information.
|
||||
|
||||
if (frameDuration < 0.011f) {
|
||||
frameDuration = 0.100f;
|
||||
}
|
||||
|
||||
CFRelease(cfFrameProperties);
|
||||
return frameDuration;
|
||||
}
|
||||
|
||||
@end
|
||||
140
Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZImageManager.h
generated
Executable file
@@ -0,0 +1,140 @@
|
||||
//
|
||||
// TZImageManager.h
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by 谭真 on 16/1/4.
|
||||
// Copyright © 2016年 谭真. All rights reserved.
|
||||
// 图片资源获取管理类
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <Photos/Photos.h>
|
||||
#import "TZAssetModel.h"
|
||||
|
||||
@class TZAlbumModel,TZAssetModel;
|
||||
@protocol TZImagePickerControllerDelegate;
|
||||
@interface TZImageManager : NSObject
|
||||
|
||||
@property (nonatomic, strong) PHCachingImageManager *cachingImageManager;
|
||||
|
||||
+ (instancetype)manager NS_SWIFT_NAME(default());
|
||||
+ (void)deallocManager;
|
||||
|
||||
@property (weak, nonatomic) id<TZImagePickerControllerDelegate> pickerDelegate;
|
||||
|
||||
@property (nonatomic, assign) BOOL shouldFixOrientation;
|
||||
|
||||
@property (nonatomic, assign) BOOL isPreviewNetworkImage;
|
||||
|
||||
/// Default is 600px / 默认600像素宽
|
||||
@property (nonatomic, assign) CGFloat photoPreviewMaxWidth;
|
||||
/// The pixel width of output image, Default is 828px / 导出图片的宽度,默认828像素宽
|
||||
@property (nonatomic, assign) CGFloat photoWidth;
|
||||
|
||||
/// Default is 4, Use in photos collectionView in TZPhotoPickerController
|
||||
/// 默认4列, TZPhotoPickerController中的照片collectionView
|
||||
@property (nonatomic, assign) NSInteger columnNumber;
|
||||
|
||||
/// Sort photos ascending by modificationDate,Default is YES
|
||||
/// 对照片排序,按修改时间升序,默认是YES。如果设置为NO,最新的照片会显示在最前面,内部的拍照按钮会排在第一个
|
||||
@property (nonatomic, assign) BOOL sortAscendingByModificationDate;
|
||||
|
||||
/// Minimum selectable photo width, Default is 0
|
||||
/// 最小可选中的图片宽度,默认是0,小于这个宽度的图片不可选中
|
||||
@property (nonatomic, assign) NSInteger minPhotoWidthSelectable;
|
||||
@property (nonatomic, assign) NSInteger minPhotoHeightSelectable;
|
||||
@property (nonatomic, assign) BOOL hideWhenCanNotSelect;
|
||||
|
||||
/// Return YES if Authorized 返回YES如果得到了授权
|
||||
- (BOOL)authorizationStatusAuthorized;
|
||||
- (void)requestAuthorizationWithCompletion:(void (^)(void))completion;
|
||||
- (BOOL)isPHAuthorizationStatusLimited;
|
||||
|
||||
/// Get Album 获得相册/相册数组
|
||||
- (void)getCameraRollAlbumWithFetchAssets:(BOOL)needFetchAssets completion:(void (^)(TZAlbumModel *model))completion;
|
||||
- (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")));
|
||||
- (void)getAllAlbums:(BOOL)allowPickingVideo allowPickingImage:(BOOL)allowPickingImage needFetchAssets:(BOOL)needFetchAssets completion:(void (^)(NSArray<TZAlbumModel *> *models))completion __attribute__((deprecated("Use -getAllAlbumsWithFetchAssets:completion:. You can config allowPickingImage、allowPickingVideo by TZImagePickerConfig")));
|
||||
- (void)getAllAlbumsWithFetchAssets:(BOOL)needFetchAssets completion:(void (^)(NSArray<TZAlbumModel *> *models))completion;
|
||||
|
||||
/// Get Assets 获得Asset数组
|
||||
- (void)getAssetsFromFetchResult:(PHFetchResult *)result completion:(void (^)(NSArray<TZAssetModel *> *models))completion;
|
||||
- (void)getAssetsFromFetchResult:(PHFetchResult *)result allowPickingVideo:(BOOL)allowPickingVideo allowPickingImage:(BOOL)allowPickingImage completion:(void (^)(NSArray<TZAssetModel *> *models))completion __attribute__((deprecated("Use -getAssetsFromFetchResult:completion:. You can config allowPickingImage、allowPickingVideo by TZImagePickerConfig")));
|
||||
- (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")));
|
||||
- (void)getAssetFromFetchResult:(PHFetchResult *)result atIndex:(NSInteger)index completion:(void (^)(TZAssetModel *model))completion;
|
||||
|
||||
/// Get photo 获得照片
|
||||
- (PHImageRequestID)getPostImageWithAlbumModel:(TZAlbumModel *)model completion:(void (^)(UIImage *postImage))completion;
|
||||
|
||||
- (PHImageRequestID)getPhotoWithAsset:(PHAsset *)asset completion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion;
|
||||
- (PHImageRequestID)getPhotoWithAsset:(PHAsset *)asset photoWidth:(CGFloat)photoWidth completion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion;
|
||||
- (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;
|
||||
- (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;
|
||||
- (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;
|
||||
|
||||
/// Get full Image 获取原图
|
||||
/// 如下两个方法completion一般会调多次,一般会先返回缩略图,再返回原图(详见方法内部使用的系统API的说明),如果info[PHImageResultIsDegradedKey] 为 YES,则表明当前返回的是缩略图,否则是原图。
|
||||
- (PHImageRequestID)getOriginalPhotoWithAsset:(PHAsset *)asset completion:(void (^)(UIImage *photo,NSDictionary *info))completion;
|
||||
- (PHImageRequestID)getOriginalPhotoWithAsset:(PHAsset *)asset newCompletion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion;
|
||||
- (PHImageRequestID)getOriginalPhotoWithAsset:(PHAsset *)asset progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler newCompletion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion;
|
||||
// 该方法中,completion只会走一次
|
||||
- (PHImageRequestID)getOriginalPhotoDataWithAsset:(PHAsset *)asset completion:(void (^)(NSData *data,NSDictionary *info,BOOL isDegraded))completion;
|
||||
- (PHImageRequestID)getOriginalPhotoDataWithAsset:(PHAsset *)asset progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler completion:(void (^)(NSData *data,NSDictionary *info,BOOL isDegraded))completion;
|
||||
|
||||
/// Get Image For VideoURL
|
||||
- (UIImage *)getImageWithVideoURL:(NSURL *)videoURL;
|
||||
|
||||
/// Save photo 保存照片
|
||||
- (void)savePhotoWithImage:(UIImage *)image completion:(void (^)(PHAsset *asset, NSError *error))completion;
|
||||
- (void)savePhotoWithImage:(UIImage *)image location:(CLLocation *)location completion:(void (^)(PHAsset *asset, NSError *error))completion;
|
||||
- (void)savePhotoWithImage:(UIImage *)image meta:(NSDictionary *)meta location:(CLLocation *)location completion:(void (^)(PHAsset *asset, NSError *error))completion;
|
||||
|
||||
/// Save video 保存视频
|
||||
- (void)saveVideoWithUrl:(NSURL *)url completion:(void (^)(PHAsset *asset, NSError *error))completion;
|
||||
- (void)saveVideoWithUrl:(NSURL *)url location:(CLLocation *)location completion:(void (^)(PHAsset *asset, NSError *error))completion;
|
||||
|
||||
/// Get video 获得视频
|
||||
- (void)getVideoWithAsset:(PHAsset *)asset completion:(void (^)(AVPlayerItem * playerItem, NSDictionary * info))completion;
|
||||
- (void)getVideoWithAsset:(PHAsset *)asset progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler completion:(void (^)(AVPlayerItem *, NSDictionary *))completion;
|
||||
|
||||
/// Export video 导出视频 presetName: 预设名字,默认值是AVAssetExportPreset640x480
|
||||
- (void)getVideoOutputPathWithAsset:(PHAsset *)asset success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure;
|
||||
- (void)getVideoOutputPathWithAsset:(PHAsset *)asset presetName:(NSString *)presetName success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure;
|
||||
- (void)getVideoOutputPathWithAsset:(PHAsset *)asset presetName:(NSString *)presetName timeRange:(CMTimeRange)timeRange success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure;
|
||||
/// 新的导出视频API,解决iOS14 iCloud视频导出失败的问题,未大量测试,请大家多多测试,有问题群里反馈
|
||||
- (void)requestVideoOutputPathWithAsset:(PHAsset *)asset presetName:(NSString *)presetName success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure;
|
||||
/// 得到视频原始文件地址
|
||||
- (void)requestVideoURLWithAsset:(PHAsset *)asset success:(void (^)(NSURL *videoURL))success failure:(void (^)(NSDictionary* info))failure;
|
||||
|
||||
/// Get photo bytes 获得一组照片的大小
|
||||
- (void)getPhotosBytesWithArray:(NSArray *)photos completion:(void (^)(NSString *totalBytes))completion;
|
||||
|
||||
- (BOOL)isCameraRollAlbum:(PHAssetCollection *)metadata;
|
||||
|
||||
/// 检查照片大小是否满足最小要求
|
||||
- (BOOL)isPhotoSelectableWithAsset:(PHAsset *)asset;
|
||||
|
||||
/// 检查照片能否被选中
|
||||
- (BOOL)isAssetCannotBeSelected:(PHAsset *)asset;
|
||||
|
||||
/// 修正图片转向
|
||||
- (UIImage *)fixOrientation:(UIImage *)aImage;
|
||||
|
||||
/// 获取asset的资源类型
|
||||
- (TZAssetModelMediaType)getAssetType:(PHAsset *)asset;
|
||||
/// 缩放图片至新尺寸
|
||||
- (UIImage *)scaleImage:(UIImage *)image toSize:(CGSize)size;
|
||||
|
||||
/// 判断asset是否是视频
|
||||
- (BOOL)isVideo:(PHAsset *)asset;
|
||||
|
||||
/// for TZImagePreviewController
|
||||
- (NSString *)getNewTimeFromDurationSecond:(NSInteger)duration;
|
||||
|
||||
- (TZAssetModel *)createModelWithAsset:(PHAsset *)asset;
|
||||
|
||||
@end
|
||||
|
||||
//@interface TZSortDescriptor : NSSortDescriptor
|
||||
//
|
||||
//@end
|
||||
1077
Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZImageManager.m
generated
Executable file
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 223 B |
|
After Width: | Height: | Size: 643 B |
@@ -0,0 +1,26 @@
|
||||
"KEY" = "阿拉伯语";
|
||||
"OK" = "حسنا";
|
||||
"Back" = "الى الخلف";
|
||||
"Done" = "فعله";
|
||||
"Edit" = "تعديل";
|
||||
"Sorry" = "آسف";
|
||||
"Cancel" = "إلغاء";
|
||||
"Setting" = "ضبط";
|
||||
"Photos" = "الصور";
|
||||
"Videos" = "أشرطة فيديو";
|
||||
"Preview" = "معاينة";
|
||||
"Full image" = "الصورة كاملة";
|
||||
"Processing..." = "معالجة...";
|
||||
"No Photos or Videos" = "لا توجد صور أو مقاطع فيديو";
|
||||
"Synchronizing photos from iCloud" = "مزامنة الصور من iCloud";
|
||||
"iCloud sync failed" = "iCloud فشلت المزامنة";
|
||||
"Can not use camera" = "لا يمكن استخدام الكاميرا";
|
||||
"Can not choose both video and photo" = "لا يمكن اختيار كل من الفيديو والصور";
|
||||
"Can not choose both photo and GIF" = "لا يمكن اختيار كل من الصور و GIF";
|
||||
"Select the video when in multi state, we will handle the video as a photo" = "حدد مقطع الفيديو عندما يكون في حالة متعددة، وسنعمل على معالجة مقطع الفيديو كصورة";
|
||||
"Can not jump to the privacy settings page, please go to the settings page by self, thank you" = "إذا تعذّر الانتقال إلى صفحة \"إعدادات الخصوصية\"، فيرجى الانتقال إلى صفحة \"الإعدادات\" بنفسك، شكرًا لك";
|
||||
"Select a maximum of %zd photos" = "حدد فقط ما يصل إلى %zd صورة";
|
||||
"Select a minimum of %zd photos" = "الرجاء تحديد %zd صورة على الأقل";
|
||||
"Allow %@ to access your album in \"Settings -> Privacy -> Photos\"" = "السماح لـ %@ بالوصول إلى الألبوم في \"الإعدادات > الخصوصية > الصور\"";
|
||||
"Please allow %@ to access your camera in \"Settings -> Privacy -> Camera\"" = "الرجاء السماح لـ %@ بالوصول إلى الكاميرا في \"الإعدادات > الخصوصية > الكاميرا\"";
|
||||
"Selected for %ld seconds" = "محدد لمدة %ld ثانية";
|
||||
@@ -0,0 +1,26 @@
|
||||
"KEY" = "德语";
|
||||
"OK" = "OK";
|
||||
"Back" = "Zurück";
|
||||
"Done" = "Erledigt";
|
||||
"Edit" = "Bearbeiten";
|
||||
"Sorry" = "Es tut uns leid";
|
||||
"Cancel" = "Stornieren";
|
||||
"Setting" = "Rahmen";
|
||||
"Photos" = "Fotos";
|
||||
"Videos" = "Videos";
|
||||
"Preview" = "Vorschau";
|
||||
"Full image" = "Vollbild";
|
||||
"Processing..." = "Wird bearbeitet...";
|
||||
"No Photos or Videos" = "Keine Fotos oder Videos";
|
||||
"Synchronizing photos from iCloud" = "Fotos aus iCloud synchronisieren";
|
||||
"iCloud sync failed" = "iCloud Synchronisierung fehlgeschlagen";
|
||||
"Can not use camera" = "Kann die Kamera nicht benutzen";
|
||||
"Can not choose both video and photo" = "Video und Foto können nicht ausgewählt werden";
|
||||
"Can not choose both photo and GIF" = "Foto und GIF können nicht ausgewählt werden";
|
||||
"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";
|
||||
"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.";
|
||||
"Select a maximum of %zd photos" = "Wählen Sie maximal %zd Bilder aus";
|
||||
"Select a minimum of %zd photos" = "Bitte wählen Sie mindestens %zd Fotos aus";
|
||||
"Allow %@ to access your album in \"Settings -> Privacy -> Photos\"" = "Erlauben Sie %@ den Zugriff auf Ihr Album unter: „Einstellungen > Datenschutz > Fotos“";
|
||||
"Please allow %@ to access your camera in \"Settings -> Privacy -> Camera\"" = "Erlauben Sie %@ den Zugriff auf Ihre Kamera unter: „Einstellungen > Datenschutz > Kamera“";
|
||||
"Selected for %ld seconds" = "Ausgewählt für %ld Sekunden";
|
||||
@@ -0,0 +1,26 @@
|
||||
"KEY" = "西班牙语";
|
||||
"OK" = "DE ACUERDO";
|
||||
"Back" = "Espalda";
|
||||
"Done" = "Hecho";
|
||||
"Edit" = "επεξεργασία";
|
||||
"Sorry" = "Lo siento";
|
||||
"Cancel" = "Cancelar";
|
||||
"Setting" = "Ajuste";
|
||||
"Photos" = "Las fotos";
|
||||
"Videos" = "Videos";
|
||||
"Preview" = "Avance";
|
||||
"Full image" = "Imagen completa";
|
||||
"Processing..." = "Tratamiento...";
|
||||
"No Photos or Videos" = "No hay fotos o videos";
|
||||
"Synchronizing photos from iCloud" = "Sincronizando fotos desde iCloud";
|
||||
"iCloud sync failed" = "la sincronización falló";
|
||||
"Can not use camera" = "No puedo usar la camara";
|
||||
"Can not choose both video and photo" = "No se puede elegir tanto el video como la foto.";
|
||||
"Can not choose both photo and GIF" = "No se puede elegir tanto foto como GIF";
|
||||
"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";
|
||||
"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";
|
||||
"Select a maximum of %zd photos" = "Seleccione solamente hasta %zd imágenes";
|
||||
"Select a minimum of %zd photos" = "Seleccione al menos %zd fotografías";
|
||||
"Allow %@ to access your album in \"Settings -> Privacy -> Photos\"" = "Permita que %@ acceda a su galería en \"Ajustes > Privacidad > Fotografías\"";
|
||||
"Please allow %@ to access your camera in \"Settings -> Privacy -> Camera\"" = "Permita que %@ acceda a su cámara en \"Ajustes > Privacidad > Cámara\"";
|
||||
"Selected for %ld seconds" = "Seleccionado para %ld segundos";
|
||||
@@ -0,0 +1,26 @@
|
||||
"KEY" = "法语";
|
||||
"OK" = "D'accord";
|
||||
"Back" = "Retour";
|
||||
"Done" = "Terminé";
|
||||
"Edit" = "Éditer";
|
||||
"Sorry" = "Pardon";
|
||||
"Cancel" = "Annuler";
|
||||
"Setting" = "Réglage";
|
||||
"Photos" = "Photos";
|
||||
"Videos" = "Vidéos";
|
||||
"Preview" = "Aperçu";
|
||||
"Full image" = "Image complète";
|
||||
"Processing..." = "En traitement...";
|
||||
"No Photos or Videos" = "Aucune photo ou vidéo";
|
||||
"Synchronizing photos from iCloud" = "Synchroniser des photos depuis iCloud";
|
||||
"iCloud sync failed" = "iCloud échec de la synchronisation";
|
||||
"Can not use camera" = "Impossible d'utiliser la caméra";
|
||||
"Can not choose both video and photo" = "Impossible de choisir à la fois la vidéo et la photo";
|
||||
"Can not choose both photo and GIF" = "Impossible de choisir à la fois photo et GIF";
|
||||
"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";
|
||||
"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";
|
||||
"Select a maximum of %zd photos" = "Vous pouvez uniquement sélectionner un maximum de %zd images";
|
||||
"Select a minimum of %zd photos" = "Veuillez sélectionner un minimum de %zd photos";
|
||||
"Allow %@ to access your album in \"Settings -> Privacy -> Photos\"" = "Autorisez %@ à accéder à votre album dans « Paramètres > Confidentialité > Photos »";
|
||||
"Please allow %@ to access your camera in \"Settings -> Privacy -> Camera\"" = "Autorisez %@ à accéder à votre appareil photo dans « Paramètres > Confidentialité > Appareil photo »";
|
||||
"Selected for %ld seconds" = "Sélectionné pendant %ld secondes";
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
@@ -0,0 +1,26 @@
|
||||
"KEY" = "日语";
|
||||
"OK" = "OK";
|
||||
"Back" = "バック";
|
||||
"Done" = "完了";
|
||||
"Edit" = "編集する";
|
||||
"Sorry" = "ごめんなさい";
|
||||
"Cancel" = "キャンセル";
|
||||
"Setting" = "設定";
|
||||
"Photos" = "写真";
|
||||
"Videos" = "動画";
|
||||
"Preview" = "プレビュー";
|
||||
"Full image" = "フルイメージ";
|
||||
"Processing..." = "処理...";
|
||||
"No Photos or Videos" = "写真やビデオはありません";
|
||||
"Synchronizing photos from iCloud" = "iCloudから写真を同期する";
|
||||
"iCloud sync failed" = "iCloud同期に失敗しました";
|
||||
"Can not use camera" = "カメラが使えない";
|
||||
"Can not choose both video and photo" = "ビデオと写真の両方を選択することはできません";
|
||||
"Can not choose both photo and GIF" = "写真とGIFの両方を選択することはできません";
|
||||
"Select the video when in multi state, we will handle the video as a photo" = "多肢選択の状態で、ビデオを選択すると、ビデオをデフォルトに画像として送信します。";
|
||||
"Can not jump to the privacy settings page, please go to the settings page by self, thank you" = "プライバシー設定画面にジャンプできません。手動で設定画面を表示してください。";
|
||||
"Select a maximum of %zd photos" = "写真は多くとも%zd 枚選択できます。";
|
||||
"Select a minimum of %zd photos" = "少なくとも %zd 枚の写真を選択してください。";
|
||||
"Allow %@ to access your album in \"Settings -> Privacy -> Photos\"" = "iPhoneの「設定-プライバシー-写真」のオプションで、r%@の携帯電話のアルバムへのアクセス権限を許可してください。";
|
||||
"Please allow %@ to access your camera in \"Settings -> Privacy -> Camera\"" = "iPhoneの「設定-プライバシー-カメラ」で、%@のカメラへのアクセス権限を許可してください。";
|
||||
"Selected for %ld seconds" = "%ld 秒間選択されました";
|
||||
@@ -0,0 +1,26 @@
|
||||
"KEY" = "朝鲜语";
|
||||
"OK" = "그래";
|
||||
"Back" = "뒤로";
|
||||
"Done" = "완료";
|
||||
"Edit" = "편집하다";
|
||||
"Sorry" = "미안해요";
|
||||
"Cancel" = "취소";
|
||||
"Setting" = "설정";
|
||||
"Photos" = "사진";
|
||||
"Videos" = "동영상";
|
||||
"Preview" = "미리 보기";
|
||||
"Full image" = "전체 이미지";
|
||||
"Processing..." = "처리...";
|
||||
"No Photos or Videos" = "아무 사진이 나 동영상";
|
||||
"Synchronizing photos from iCloud" = "ICloud에서 사진을 동기화";
|
||||
"iCloud sync failed" = "iCloud동기화 실패";
|
||||
"Can not use camera" = "카메라를 사용할 수 없습니다.";
|
||||
"Can not choose both video and photo" = "비디오와 사진 둘 다를 선택할 수 없습니다.";
|
||||
"Can not choose both photo and GIF" = "사진 및 GIF를 선택할 수 없습니다.";
|
||||
"Select the video when in multi state, we will handle the video as a photo" = "다중 선택 모드에서 비디오를 선택하면 비디오를 사진으로 처리합니다.";
|
||||
"Can not jump to the privacy settings page, please go to the settings page by self, thank you" = "개인 정보 보호 설정 페이지로 바로 이동할 수 없습니다. 설정 페이지로 직접 이동해 주세요. 감사합니다.";
|
||||
"Select a maximum of %zd photos" = "최대 %zd장의 이미지만 선택할 수 있습니다.";
|
||||
"Select a minimum of %zd photos" = "최소 %zd장의 사진을 선택해 주세요.";
|
||||
"Allow %@ to access your album in \"Settings -> Privacy -> Photos\"" = "\"설정 > 개인 정보 보호 > 사진\"에서 %@이(가) 앨범에 접근할 수 있도록 허용하세요.";
|
||||
"Please allow %@ to access your camera in \"Settings -> Privacy -> Camera\"" = "\"설정 > 개인 정보 보호 > 카메라\"에서 %@이(가) 카메라에 접근할 수 있도록 허용하세요.";
|
||||
"Selected for %ld seconds" = "%ld 초 동안 선택됨";
|
||||
|
After Width: | Height: | Size: 116 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 501 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 620 B |
|
After Width: | Height: | Size: 1006 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 501 B |
|
After Width: | Height: | Size: 392 B |
@@ -0,0 +1,26 @@
|
||||
"KEY" = "葡萄牙语";
|
||||
"OK" = "Está bem";
|
||||
"Back" = "De volta";
|
||||
"Done" = "Feito";
|
||||
"Edit" = "editar";
|
||||
"Sorry" = "Desculpa";
|
||||
"Cancel" = "Cancelar";
|
||||
"Setting" = "Configuração";
|
||||
"Photos" = "Fotos";
|
||||
"Videos" = "Vídeos";
|
||||
"Preview" = "Visualizar";
|
||||
"Full image" = "Imagem Completa";
|
||||
"Processing..." = "Em processamento...";
|
||||
"No Photos or Videos" = "Sem fotos ou vídeos";
|
||||
"Synchronizing photos from iCloud" = "Sincronizando fotos do iCloud";
|
||||
"iCloud sync failed" = "iCloud falha na sincronização";
|
||||
"Can not use camera" = "Não pode usar a câmera";
|
||||
"Can not choose both video and photo" = "Não é possível escolher vídeo e foto";
|
||||
"Can not choose both photo and GIF" = "Não é possível escolher foto e GIF";
|
||||
"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";
|
||||
"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";
|
||||
"Select a maximum of %zd photos" = "Selecione apenas %zd imagens,no máximo";
|
||||
"Select a minimum of %zd photos" = "Selecione %zd fotos,no mínimo";
|
||||
"Allow %@ to access your album in \"Settings -> Privacy -> Photos\"" = "Permita a %@ aceder ao seu álbum em “Definições > Privacidade > Fotos”";
|
||||
"Please allow %@ to access your camera in \"Settings -> Privacy -> Camera\"" = "Permita a %@ aceder à sua câmara em “Definições > Privacidade > Câmara”";
|
||||
"Selected for %ld seconds" = "Selecionado por %ld segundos";
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,26 @@
|
||||
"KEY" = "俄语";
|
||||
"OK" = "Хорошо";
|
||||
"Back" = "назад";
|
||||
"Done" = "Готово";
|
||||
"Edit" = "редактировать";
|
||||
"Sorry" = "сожалею";
|
||||
"Cancel" = "отменить";
|
||||
"Setting" = "настройка";
|
||||
"Photos" = "Фото";
|
||||
"Videos" = "Видео";
|
||||
"Preview" = "предварительный просмотр";
|
||||
"Full image" = "Полное изображение";
|
||||
"Processing..." = "Обработка ...";
|
||||
"No Photos or Videos" = "Нет фото или видео";
|
||||
"Synchronizing photos from iCloud" = "Синхронизация фотографий из iCloud";
|
||||
"iCloud sync failed" = "iCloud сбой синхронизации";
|
||||
"Can not use camera" = "Не могу использовать камеру";
|
||||
"Can not choose both video and photo" = "Не могу выбрать как видео,так и фото";
|
||||
"Can not choose both photo and GIF" = "Не могу выбрать фото и GIF";
|
||||
"Select the video when in multi state, we will handle the video as a photo" = "В случае выбора видео при нахождении в мультирежиме видео будет обработано как фотография";
|
||||
"Can not jump to the privacy settings page, please go to the settings page by self, thank you" = "Не удается перейти на страницу настроек конфиденциальности. Перейдите на эту страницу самостоятельно";
|
||||
"Select a maximum of %zd photos" = "Вы можете выбрать до %zd изображений";
|
||||
"Select a minimum of %zd photos" = "Вы можете выбрать не менее %zd изображений";
|
||||
"Allow %@ to access your album in \"Settings -> Privacy -> Photos\"" = "Разрешите доступ %@ к вашему альбому,перейдя в Настройки > Конфиденциальность > Фото";
|
||||
"Please allow %@ to access your camera in \"Settings -> Privacy -> Camera\"" = "Разрешите доступ %@ к камере вашего устройства,перейдя в Настройки > Конфиденциальность > Камера";
|
||||
"Selected for %ld seconds" = "Выбрано для %ld секунд";
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 671 B |
@@ -0,0 +1,26 @@
|
||||
"KEY" = "越南语";
|
||||
"OK" = "Xác nhận";
|
||||
"Back" = "Quay lại";
|
||||
"Done" = "Hoàn thành";
|
||||
"Edit" = "biên tập";
|
||||
"Sorry" = "Xin lỗi";
|
||||
"Cancel" = "Hủy";
|
||||
"Setting" = "Cài đặt";
|
||||
"Photos" = "Hình";
|
||||
"Videos" = "Clip";
|
||||
"Preview" = "Xem trước";
|
||||
"Full image" = "Hình gốc";
|
||||
"Processing..." = "Đang xử lý...";
|
||||
"No Photos or Videos" = "Không có ảnh hoặc video";
|
||||
"Can not use camera" = "Máy chụp hình không khả dụng";
|
||||
"Synchronizing photos from iCloud" = "Đang đồng bộ hình ảnh từ ICloud";
|
||||
"iCloud sync failed" = "iCloud đồng bộ hóa không thành công";
|
||||
"Can not choose both video and photo" = "Trong lúc chọn hình ảnh không cùng lúc chọn video";
|
||||
"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";
|
||||
"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.";
|
||||
"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.";
|
||||
"Select a maximum of %zd photos" = "Bạn chỉ được chọn nhiều nhất %zd tấm hình";
|
||||
"Select a minimum of %zd photos" = "Chọn ít nhất %zd tấm hình";
|
||||
"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.";
|
||||
"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";
|
||||
"Selected for %ld seconds" = "Đã chọn cho %ld giây";
|
||||
396
Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZImagePickerController.h
generated
Normal file
@@ -0,0 +1,396 @@
|
||||
//
|
||||
// TZImagePickerController.h
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by 谭真 on 15/12/24.
|
||||
// Copyright © 2015年 谭真. All rights reserved.
|
||||
// version 3.8.8 - 2024.10.27
|
||||
// 更多信息,请前往项目的github地址:https://github.com/banchichen/TZImagePickerController
|
||||
|
||||
/*
|
||||
经过测试,比起xib的方式,把TZAssetCell改用纯代码的方式来写,滑动帧数明显提高了(约提高10帧左右)
|
||||
|
||||
最初发现这个问题并修复的是@小鱼周凌宇同学,她的博客地址: http://zhoulingyu.com/
|
||||
表示感谢~
|
||||
|
||||
原来xib确实会导致性能问题啊...大家也要注意了...
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "TZAssetModel.h"
|
||||
#import "NSBundle+TZImagePicker.h"
|
||||
#import "TZImageManager.h"
|
||||
#import "TZVideoPlayerController.h"
|
||||
#import "TZGifPhotoPreviewController.h"
|
||||
#import "TZPhotoPreviewController.h"
|
||||
#import "TZPhotoPreviewCell.h"
|
||||
|
||||
#if __has_include("TZLocationManager.h")
|
||||
#define TZ_HAVE_LOCATION_CODE 1
|
||||
#import "TZLocationManager.h"
|
||||
#else
|
||||
#undef TZ_HAVE_LOCATION_CODE
|
||||
#endif
|
||||
|
||||
#define CURRENT_SYSTEM_VERSION [[UIDevice currentDevice] systemVersion]
|
||||
#define SYSTEM_VERSION_GREATER_THAN_15 ([CURRENT_SYSTEM_VERSION floatValue] >= 15.0)
|
||||
|
||||
@class TZAlbumCell, TZAssetCell;
|
||||
@protocol TZImagePickerControllerDelegate;
|
||||
@interface TZImagePickerController : UINavigationController
|
||||
|
||||
#pragma mark -
|
||||
/// Use this init method / 用这个初始化方法
|
||||
- (instancetype)initWithMaxImagesCount:(NSInteger)maxImagesCount delegate:(id<TZImagePickerControllerDelegate>)delegate;
|
||||
- (instancetype)initWithMaxImagesCount:(NSInteger)maxImagesCount columnNumber:(NSInteger)columnNumber delegate:(id<TZImagePickerControllerDelegate>)delegate;
|
||||
- (instancetype)initWithMaxImagesCount:(NSInteger)maxImagesCount columnNumber:(NSInteger)columnNumber delegate:(id<TZImagePickerControllerDelegate>)delegate pushPhotoPickerVc:(BOOL)pushPhotoPickerVc;
|
||||
/// This init method just for previewing photos / 用这个初始化方法以预览图片
|
||||
- (instancetype)initWithSelectedAssets:(NSMutableArray *)selectedAssets selectedPhotos:(NSMutableArray *)selectedPhotos index:(NSInteger)index;
|
||||
/// This init method for crop photo / 用这个初始化方法以裁剪图片
|
||||
- (instancetype)initCropTypeWithAsset:(PHAsset *)asset photo:(UIImage *)photo completion:(void (^)(UIImage *cropImage,PHAsset *asset))completion;
|
||||
|
||||
#pragma mark -
|
||||
/// Default is 9 / 默认最大可选9张图片
|
||||
@property (nonatomic, assign) NSInteger maxImagesCount;
|
||||
|
||||
/// The minimum count photos user must pick, Default is 0
|
||||
/// 最小照片必选张数,默认是0
|
||||
@property (nonatomic, assign) NSInteger minImagesCount;
|
||||
|
||||
/// If the user does not select any pictures, the current picture is automatically selected when the Finish button is clicked, Default is YES
|
||||
/// 如果用户未选择任何图片,在点击完成按钮时自动选中当前图片,默认YES
|
||||
@property (nonatomic, assign) BOOL autoSelectCurrentWhenDone;
|
||||
|
||||
/// Always enale the done button, not require minimum 1 photo be picked
|
||||
/// 让完成按钮一直可以点击,无须最少选择一张图片
|
||||
@property (nonatomic, assign) BOOL alwaysEnableDoneBtn;
|
||||
|
||||
/// Sort photos ascending by modificationDate,Default is YES
|
||||
/// 对照片排序,按修改时间升序,默认是YES。如果设置为NO,最新的照片会显示在最前面,内部的拍照按钮会排在第一个
|
||||
@property (nonatomic, assign) BOOL sortAscendingByModificationDate;
|
||||
|
||||
/// The pixel width of output image, Default is 828px,you need to set photoPreviewMaxWidth at the same time
|
||||
/// 导出图片的宽度,默认828像素宽,你需要同时设置photoPreviewMaxWidth的值
|
||||
@property (nonatomic, assign) CGFloat photoWidth;
|
||||
|
||||
/// Default is 600px / 默认600像素宽
|
||||
@property (nonatomic, assign) CGFloat photoPreviewMaxWidth;
|
||||
|
||||
/// Default is 30, While fetching photo, HUD will dismiss automatic if timeout;
|
||||
/// 超时时间,默认为30秒,当取图片时间超过30秒还没有取成功时,会自动dismiss HUD;
|
||||
@property (nonatomic, assign) NSInteger timeout;
|
||||
|
||||
/// Default is YES, if set NO, the original photo button will hide. user can't picking original photo.
|
||||
/// 默认为YES,如果设置为NO,原图按钮将隐藏,用户不能选择发送原图
|
||||
@property (nonatomic, assign) BOOL allowPickingOriginalPhoto;
|
||||
|
||||
/// Default is YES, if set NO, user can't picking video.
|
||||
/// 默认为YES,如果设置为NO,用户将不能选择视频
|
||||
@property (nonatomic, assign) BOOL allowPickingVideo;
|
||||
|
||||
/// Default is NO, if set YES, user can edit video.
|
||||
/// 默认为NO,如果设置为YES, 用户能编辑视频
|
||||
@property (nonatomic, assign) BOOL allowEditVideo;
|
||||
|
||||
/// Export quality of cropped video, Default is AVAssetExportPresetMediumQuality
|
||||
/// 裁剪视频的导出质量,默认是 AVAssetExportPresetMediumQuality
|
||||
@property (nonatomic, copy) NSString *presetName;
|
||||
|
||||
/// Default is 30s. If it exceeds the video duration, it is the video duration.The minimum duration of video crop is 1s.
|
||||
/// 默认是30s,如果超过视频时长,则为视频时长,小于1s不裁剪
|
||||
@property (nonatomic, assign) NSInteger maxCropVideoDuration;
|
||||
|
||||
/// Default is NO, if set YES, The edited video will be automatically saved to the album.
|
||||
/// 默认为NO,如果设置为YES,编辑后的视频会自动保存到相册
|
||||
@property (nonatomic, assign) BOOL saveEditedVideoToAlbum;
|
||||
|
||||
/// Default is NO / 默认为NO,为YES时可以多选视频/gif/图片,和照片共享最大可选张数maxImagesCount的限制
|
||||
@property (nonatomic, assign) BOOL allowPickingMultipleVideo;
|
||||
|
||||
/// Default is NO, if set YES, user can picking gif image. When NO, gif will be treated as a regular image. If want not displayed, please refer to isAssetCanBeDisplayed
|
||||
/// 默认为NO,如果设置为YES,用户可以选择gif图片。为NO时gif会被当成普通图片,若要不显示,请参考isAssetCanBeDisplayed
|
||||
@property (nonatomic, assign) BOOL allowPickingGif;
|
||||
|
||||
/// Default is YES, if set NO, user can't picking image.
|
||||
/// 默认为YES,如果设置为NO,用户将不能选择发送图片
|
||||
@property (nonatomic, assign) BOOL allowPickingImage;
|
||||
|
||||
/// Default is YES, if set NO, user can't take picture.
|
||||
/// 默认为YES,如果设置为NO, 用户将不能拍摄照片
|
||||
@property (nonatomic, assign) BOOL allowTakePicture;
|
||||
|
||||
#ifdef TZ_HAVE_LOCATION_CODE
|
||||
@property (nonatomic, assign) BOOL allowCameraLocation;
|
||||
#endif
|
||||
|
||||
/// Default is YES, if set NO, user can't take video.
|
||||
/// 默认为YES,如果设置为NO, 用户将不能拍摄视频
|
||||
@property(nonatomic, assign) BOOL allowTakeVideo;
|
||||
/// Default value is 10 minutes / 视频最大拍摄时间,默认是10分钟,单位是秒
|
||||
@property (assign, nonatomic) NSTimeInterval videoMaximumDuration;
|
||||
/// Customizing UIImagePickerController's other properties, such as videoQuality / 定制UIImagePickerController的其它属性,比如视频拍摄质量videoQuality
|
||||
@property (nonatomic, copy) void(^uiImagePickerControllerSettingBlock)(UIImagePickerController *imagePickerController);
|
||||
|
||||
/// 首选语言,如果设置了就用该语言,不设则取当前系统语言。
|
||||
/// 支持zh-Hans、zh-Hant、en、vi等值,详见TZImagePickerController.bundle内的语言资源
|
||||
@property (copy, nonatomic) NSString *preferredLanguage;
|
||||
|
||||
/// 语言bundle,preferredLanguage变化时languageBundle会变化
|
||||
/// 可通过手动设置bundle,让选择器支持新的的语言(需要在设置preferredLanguage后设置languageBundle)。欢迎提交PR把语言文件提交上来~
|
||||
@property (strong, nonatomic) NSBundle *languageBundle;
|
||||
|
||||
/// Default is YES, if set NO, user can't preview photo.
|
||||
/// 默认为YES,如果设置为NO,预览按钮将隐藏,用户将不能去预览照片
|
||||
@property (nonatomic, assign) BOOL allowPreview;
|
||||
|
||||
/// Default is YES, if set NO, the picker don't dismiss itself.
|
||||
/// 默认为YES,如果设置为NO, 选择器将不会自己dismiss
|
||||
@property(nonatomic, assign) BOOL autoDismiss;
|
||||
|
||||
/// Default is NO, if set YES, in the delegate method the photos and infos will be nil, only assets hava value.
|
||||
/// 默认为NO,如果设置为YES,代理方法里photos和infos会是nil,只返回assets
|
||||
@property (assign, nonatomic) BOOL onlyReturnAsset;
|
||||
|
||||
/// Default is NO, if set YES, will show the image's selected index.
|
||||
/// 默认为NO,如果设置为YES,会显示照片的选中序号
|
||||
@property (assign, nonatomic) BOOL showSelectedIndex;
|
||||
|
||||
/// Default is NO, if set YES, when selected photos's count up to maxImagesCount, other photo will show float layer what's color is cannotSelectLayerColor.
|
||||
/// 默认是NO,如果设置为YES,当照片选择张数达到maxImagesCount时,其它照片会显示颜色为cannotSelectLayerColor的浮层
|
||||
@property (assign, nonatomic) BOOL showPhotoCannotSelectLayer;
|
||||
/// Default is white color with 0.8 alpha;
|
||||
@property (strong, nonatomic) UIColor *cannotSelectLayerColor;
|
||||
|
||||
/// Default is YES, if set NO, the result photo will be scaled to photoWidth pixel width. The photoWidth default is 828px
|
||||
/// 默认是YES,如果设置为NO,内部会缩放图片到photoWidth像素宽
|
||||
@property (assign, nonatomic) BOOL notScaleImage;
|
||||
|
||||
/// 默认是NO,如果设置为YES,导出视频时会修正转向(慎重设为YES,可能导致部分安卓下拍的视频导出失败)
|
||||
@property (assign, nonatomic) BOOL needFixComposition;
|
||||
|
||||
/// The photos user have selected
|
||||
/// 用户选中过的图片数组
|
||||
@property (nonatomic, strong) NSMutableArray *selectedAssets;
|
||||
@property (nonatomic, strong) NSMutableArray<TZAssetModel *> *selectedModels;
|
||||
@property (nonatomic, strong) NSMutableArray *selectedAssetIds;
|
||||
- (void)addSelectedModel:(TZAssetModel *)model;
|
||||
- (void)removeSelectedModel:(TZAssetModel *)model;
|
||||
|
||||
/// Minimum selectable photo width, Default is 0
|
||||
/// 最小可选中的图片宽度,默认是0,小于这个宽度的图片不可选中
|
||||
@property (nonatomic, assign) NSInteger minPhotoWidthSelectable;
|
||||
@property (nonatomic, assign) NSInteger minPhotoHeightSelectable;
|
||||
/// Hide the photo what can not be selected, Default is NO
|
||||
/// 隐藏不可以选中的图片,默认是NO,不推荐将其设置为YES
|
||||
@property (nonatomic, assign) BOOL hideWhenCanNotSelect;
|
||||
/// Deprecated, Use statusBarStyle (顶部statusBar 是否为系统默认的黑色,默认为NO)
|
||||
@property (nonatomic, assign) BOOL isStatusBarDefault __attribute__((deprecated("Use -statusBarStyle.")));
|
||||
/// statusBar的样式,默认为UIStatusBarStyleLightContent
|
||||
@property (assign, nonatomic) UIStatusBarStyle statusBarStyle;
|
||||
|
||||
#pragma mark -
|
||||
/// Single selection mode, valid when maxImagesCount = 1
|
||||
/// 单选模式,maxImagesCount为1时才生效
|
||||
@property (nonatomic, assign) BOOL showSelectBtn; ///< 在单选模式下,照片列表页中,显示选择按钮,默认为NO
|
||||
@property (nonatomic, assign) BOOL allowCrop; ///< 允许裁剪,默认为YES,showSelectBtn为NO才生效
|
||||
@property (nonatomic, assign) BOOL scaleAspectFillCrop; ///< 是否图片等比缩放填充cropRect区域,开启后预览页面无法左右滑动切换图片
|
||||
@property (nonatomic, assign) CGRect cropRect; ///< 裁剪框的尺寸
|
||||
@property (nonatomic, assign) CGRect cropRectPortrait; ///< 裁剪框的尺寸(竖屏)
|
||||
@property (nonatomic, assign) CGRect cropRectLandscape; ///< 裁剪框的尺寸(横屏)
|
||||
@property (nonatomic, assign) BOOL needCircleCrop; ///< 需要圆形裁剪框
|
||||
@property (nonatomic, assign) NSInteger circleCropRadius; ///< 圆形裁剪框半径大小
|
||||
@property (nonatomic, copy) void (^cropViewSettingBlock)(UIView *cropView); ///< 自定义裁剪框的其他属性
|
||||
@property (nonatomic, copy) void (^navLeftBarButtonSettingBlock)(UIButton *leftButton); ///< 自定义返回按钮样式及其属性
|
||||
|
||||
/// 【自定义各页面/组件的样式】在界面初始化/组件setModel完成后调用,允许外界修改样式等
|
||||
@property (nonatomic, copy) void (^photoPickerPageUIConfigBlock)(UICollectionView *collectionView, UIView *bottomToolBar, UIButton *previewButton, UIButton *originalPhotoButton, UILabel *originalPhotoLabel, UIButton *doneButton, UIImageView *numberImageView, UILabel *numberLabel, UIView *divideLine);
|
||||
@property (nonatomic, copy) void (^photoPreviewPageUIConfigBlock)(UICollectionView *collectionView, UIView *naviBar, UIButton *backButton, UIButton *selectButton, UILabel *indexLabel, UIView *toolBar, UIButton *originalPhotoButton, UILabel *originalPhotoLabel, UIButton *doneButton, UIImageView *numberImageView, UILabel *numberLabel);
|
||||
@property (nonatomic, copy) void (^videoPreviewPageUIConfigBlock)(UIButton *playButton, UIView *toolBar, UIButton *editBtn, UIButton *doneButton);
|
||||
@property (nonatomic, copy) void (^videoEditViewPageUIConfigBlock)(UIButton *playButton,UILabel *cropVideoDurationLabel, UIButton *editButton, UIButton *doneButton);
|
||||
@property (nonatomic, copy) void (^gifPreviewPageUIConfigBlock)(UIView *toolBar, UIButton *doneButton);
|
||||
@property (nonatomic, copy) void (^albumPickerPageUIConfigBlock)(UITableView *tableView);
|
||||
@property (nonatomic, copy) void (^assetCellDidSetModelBlock)(TZAssetCell *cell, UIImageView *imageView, UIImageView *selectImageView, UILabel *indexLabel, UIView *bottomView, UILabel *timeLength, UIImageView *videoImgView);
|
||||
@property (nonatomic, copy) void (^albumCellDidSetModelBlock)(TZAlbumCell *cell, UIImageView *posterImageView, UILabel *titleLabel);
|
||||
/// 【自定义各页面/组件的frame】在界面viewDidLayoutSubviews/组件layoutSubviews后调用,允许外界修改frame等
|
||||
@property (nonatomic, copy) void (^photoPickerPageDidLayoutSubviewsBlock)(UICollectionView *collectionView, UIView *bottomToolBar, UIButton *previewButton, UIButton *originalPhotoButton, UILabel *originalPhotoLabel, UIButton *doneButton, UIImageView *numberImageView, UILabel *numberLabel, UIView *divideLine);
|
||||
@property (nonatomic, copy) void (^photoPreviewPageDidLayoutSubviewsBlock)(UICollectionView *collectionView, UIView *naviBar, UIButton *backButton, UIButton *selectButton, UILabel *indexLabel, UIView *toolBar, UIButton *originalPhotoButton, UILabel *originalPhotoLabel, UIButton *doneButton, UIImageView *numberImageView, UILabel *numberLabel);
|
||||
@property (nonatomic, copy) void (^videoPreviewPageDidLayoutSubviewsBlock)(UIButton *playButton, UIView *toolBar, UIButton *editButton, UIButton *doneButton);
|
||||
@property (nonatomic, copy) void (^videoEditViewPageDidLayoutSubviewsBlock)(UIButton *playButton, UILabel *cropVideoDurationLabel, UIButton *cancelButton, UIButton *doneButton);
|
||||
@property (nonatomic, copy) void (^gifPreviewPageDidLayoutSubviewsBlock)(UIView *toolBar, UIButton *doneButton);
|
||||
@property (nonatomic, copy) void (^albumPickerPageDidLayoutSubviewsBlock)(UITableView *tableView);
|
||||
@property (nonatomic, copy) void (^assetCellDidLayoutSubviewsBlock)(TZAssetCell *cell, UIImageView *imageView, UIImageView *selectImageView, UILabel *indexLabel, UIView *bottomView, UILabel *timeLength, UIImageView *videoImgView);
|
||||
@property (nonatomic, copy) void (^albumCellDidLayoutSubviewsBlock)(TZAlbumCell *cell, UIImageView *posterImageView, UILabel *titleLabel);
|
||||
/// 自定义各页面/组件的frame】刷新底部状态(refreshNaviBarAndBottomBarState)使用的
|
||||
@property (nonatomic, copy) void (^photoPickerPageDidRefreshStateBlock)(UICollectionView *collectionView, UIView *bottomToolBar, UIButton *previewButton, UIButton *originalPhotoButton, UILabel *originalPhotoLabel, UIButton *doneButton, UIImageView *numberImageView, UILabel *numberLabel, UIView *divideLine);
|
||||
|
||||
@property (nonatomic, copy) void (^photoPreviewPageDidRefreshStateBlock)(UICollectionView *collectionView, UIView *naviBar, UIButton *backButton, UIButton *selectButton, UILabel *indexLabel, UIView *toolBar, UIButton *originalPhotoButton, UILabel *originalPhotoLabel, UIButton *doneButton, UIImageView *numberImageView, UILabel *numberLabel);
|
||||
|
||||
#pragma mark -
|
||||
- (UIAlertController *)showAlertWithTitle:(NSString *)title;
|
||||
- (void)showProgressHUD;
|
||||
- (void)hideProgressHUD;
|
||||
@property (nonatomic, assign) BOOL isSelectOriginalPhoto;
|
||||
@property (assign, nonatomic) BOOL needShowStatusBar;
|
||||
|
||||
#pragma mark -
|
||||
@property (nonatomic, copy) NSString *takePictureImageName __attribute__((deprecated("Use -takePictureImage.")));
|
||||
@property (nonatomic, copy) NSString *photoSelImageName __attribute__((deprecated("Use -photoSelImage.")));
|
||||
@property (nonatomic, copy) NSString *photoDefImageName __attribute__((deprecated("Use -photoDefImage.")));
|
||||
@property (nonatomic, copy) NSString *photoOriginSelImageName __attribute__((deprecated("Use -photoOriginSelImage.")));
|
||||
@property (nonatomic, copy) NSString *photoOriginDefImageName __attribute__((deprecated("Use -photoOriginDefImage.")));
|
||||
@property (nonatomic, copy) NSString *photoPreviewOriginDefImageName __attribute__((deprecated("Use -photoPreviewOriginDefImage.")));
|
||||
@property (nonatomic, copy) NSString *photoNumberIconImageName __attribute__((deprecated("Use -photoNumberIconImage.")));
|
||||
@property (nonatomic, strong) UIImage *takePictureImage;
|
||||
@property (nonatomic, strong) UIImage *addMorePhotoImage;
|
||||
@property (nonatomic, strong) UIImage *photoSelImage;
|
||||
@property (nonatomic, strong) UIImage *photoDefImage;
|
||||
@property (nonatomic, strong) UIImage *photoOriginSelImage;
|
||||
@property (nonatomic, strong) UIImage *photoOriginDefImage;
|
||||
@property (nonatomic, strong) UIImage *photoPreviewOriginDefImage;
|
||||
@property (nonatomic, strong) UIImage *photoNumberIconImage;
|
||||
|
||||
#pragma mark -
|
||||
/// Appearance / 外观颜色 + 按钮文字
|
||||
@property (nonatomic, strong) UIColor *oKButtonTitleColorNormal;
|
||||
@property (nonatomic, strong) UIColor *oKButtonTitleColorDisabled;
|
||||
@property (nonatomic, strong) UIColor *naviBgColor;
|
||||
@property (nonatomic, strong) UIColor *naviTitleColor;
|
||||
@property (nonatomic, strong) UIFont *naviTitleFont;
|
||||
@property (nonatomic, strong) UIColor *barItemTextColor;
|
||||
@property (nonatomic, strong) UIFont *barItemTextFont;
|
||||
|
||||
@property (nonatomic, copy) NSString *doneBtnTitleStr;
|
||||
@property (nonatomic, copy) NSString *cancelBtnTitleStr;
|
||||
@property (nonatomic, copy) NSString *previewBtnTitleStr;
|
||||
@property (nonatomic, copy) NSString *fullImageBtnTitleStr;
|
||||
@property (nonatomic, copy) NSString *settingBtnTitleStr;
|
||||
@property (nonatomic, copy) NSString *processHintStr;
|
||||
@property (nonatomic, copy) NSString *editBtnTitleStr;
|
||||
@property (nonatomic, copy) NSString *editViewCancelBtnTitleStr;
|
||||
|
||||
/// Icon theme color, default is green color like wechat, the value is r:31 g:185 b:34. Currently only support image selection icon when showSelectedIndex is YES. If you need it, please set it as soon as possible
|
||||
/// icon主题色,默认是微信的绿色,值是r:31 g:185 b:34。目前仅支持showSelectedIndex为YES时的图片选中icon。如需要,请尽早设置它。
|
||||
@property (strong, nonatomic) UIColor *iconThemeColor;
|
||||
|
||||
#pragma mark -
|
||||
- (void)cancelButtonClick;
|
||||
|
||||
// For method annotations, see the corresponding method in TZImagePickerControllerDelegate / 方法注释见TZImagePickerControllerDelegate中对应方法
|
||||
@property (nonatomic, copy) void (^didFinishPickingPhotosHandle)(NSArray<UIImage *> *photos,NSArray *assets,BOOL isSelectOriginalPhoto);
|
||||
@property (nonatomic, copy) void (^didFinishPickingPhotosWithInfosHandle)(NSArray<UIImage *> *photos,NSArray *assets,BOOL isSelectOriginalPhoto,NSArray<NSDictionary *> *infos);
|
||||
@property (nonatomic, copy) void (^imagePickerControllerDidCancelHandle)(void);
|
||||
@property (nonatomic, copy) void (^didFinishPickingVideoHandle)(UIImage *coverImage,PHAsset *asset);
|
||||
@property (nonatomic, copy) void (^didFinishPickingAndEditingVideoHandle)(UIImage *coverImage,NSString *outputPath,NSString *errorMsg);
|
||||
@property (nonatomic, copy) void (^didFinishPickingGifImageHandle)(UIImage *animatedImage,id sourceAssets);
|
||||
|
||||
@property (nonatomic, weak) id<TZImagePickerControllerDelegate> pickerDelegate;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@protocol TZImagePickerControllerDelegate <NSObject>
|
||||
@optional
|
||||
// The picker should dismiss itself; when it dismissed these callback will be called.
|
||||
// You can also set autoDismiss to NO, then the picker don't dismiss itself.
|
||||
// If isOriginalPhoto is YES, user picked the original photo.
|
||||
// You can get original photo with asset, by the method [[TZImageManager manager] getOriginalPhotoWithAsset:completion:].
|
||||
// The UIImage Object in photos default width is 828px, you can set it by photoWidth property.
|
||||
// 这个照片选择器会自己dismiss,当选择器dismiss的时候,会执行下面的代理方法
|
||||
// 你也可以设置autoDismiss属性为NO,选择器就不会自己dismis了
|
||||
// 如果isSelectOriginalPhoto为YES,表明用户选择了原图
|
||||
// 你可以通过一个asset获得原图,通过这个方法:[[TZImageManager manager] getOriginalPhotoWithAsset:completion:]
|
||||
// photos数组里的UIImage对象,默认是828像素宽,你可以通过设置photoWidth属性的值来改变它
|
||||
- (void)imagePickerController:(TZImagePickerController *)picker didFinishPickingPhotos:(NSArray<UIImage *> *)photos sourceAssets:(NSArray *)assets isSelectOriginalPhoto:(BOOL)isSelectOriginalPhoto;
|
||||
- (void)imagePickerController:(TZImagePickerController *)picker didFinishPickingPhotos:(NSArray<UIImage *> *)photos sourceAssets:(NSArray *)assets isSelectOriginalPhoto:(BOOL)isSelectOriginalPhoto infos:(NSArray<NSDictionary *> *)infos;
|
||||
- (void)tz_imagePickerControllerDidCancel:(TZImagePickerController *)picker;
|
||||
|
||||
/// 如果用户选择了某张照片下面的代理方法会被执行
|
||||
/// 如果isSelectOriginalPhoto为YES,表明用户选择了原图
|
||||
/// 你可以通过一个asset获得原图,通过这个方法:[[TZImageManager manager] getOriginalPhotoWithAsset:completion:]
|
||||
- (void)imagePickerController:(TZImagePickerController *)picker didSelectAsset:(PHAsset *)asset photo:(UIImage *)photo isSelectOriginalPhoto:(BOOL)isSelectOriginalPhoto;
|
||||
|
||||
/// 如果用户取消选择了某张照片下面的代理方法会被执行
|
||||
/// 如果isSelectOriginalPhoto为YES,表明用户选择了原图
|
||||
/// 你可以通过一个asset获得原图,通过这个方法:[[TZImageManager manager] getOriginalPhotoWithAsset:completion:]
|
||||
- (void)imagePickerController:(TZImagePickerController *)picker didDeselectAsset:(PHAsset *)asset photo:(UIImage *)photo isSelectOriginalPhoto:(BOOL)isSelectOriginalPhoto;
|
||||
|
||||
// If user picking a video and allowPickingMultipleVideo is NO, this callback will be called.
|
||||
// If allowPickingMultipleVideo is YES, will call imagePickerController:didFinishPickingPhotos:sourceAssets:isSelectOriginalPhoto:
|
||||
// 如果用户选择了一个视频且allowPickingMultipleVideo是NO,下面的代理方法会被执行
|
||||
// 如果allowPickingMultipleVideo是YES,将会调用imagePickerController:didFinishPickingPhotos:sourceAssets:isSelectOriginalPhoto:
|
||||
- (void)imagePickerController:(TZImagePickerController *)picker didFinishPickingVideo:(UIImage *)coverImage sourceAssets:(PHAsset *)asset;
|
||||
|
||||
// If allowEditVideo is YES and allowPickingMultipleVideo is NO, When user picking a video, this callback will be called.
|
||||
// If allowPickingMultipleVideo is YES, video editing is not supported, will call imagePickerController:didFinishPickingPhotos:sourceAssets:isSelectOriginalPhoto:
|
||||
// 当allowEditVideo是YES且allowPickingMultipleVideo是NO是,如果用户选择了一个视频,下面的代理方法会被执行
|
||||
// 如果allowPickingMultipleVideo是YES,则不支持编辑视频,将会调用imagePickerController:didFinishPickingPhotos:sourceAssets:isSelectOriginalPhoto:
|
||||
- (void)imagePickerController:(TZImagePickerController *)picker didFinishPickingAndEditingVideo:(UIImage *)coverImage outputPath:(NSString *)outputPath error:(NSString *)errorMsg;
|
||||
|
||||
// When saving the edited video to the album fails, this callback will be called.
|
||||
// 编辑后的视频自动保存到相册失败时,下面的代理方法会被执行
|
||||
- (void)imagePickerController:(TZImagePickerController *)picker didFailToSaveEditedVideoWithError:(NSError *)error;
|
||||
|
||||
// If user picking a gif image and allowPickingMultipleVideo is NO, this callback will be called.
|
||||
// If allowPickingMultipleVideo is YES, will call imagePickerController:didFinishPickingPhotos:sourceAssets:isSelectOriginalPhoto:
|
||||
// 如果用户选择了一个gif图片且allowPickingMultipleVideo是NO,下面的代理方法会被执行
|
||||
// 如果allowPickingMultipleVideo是YES,将会调用imagePickerController:didFinishPickingPhotos:sourceAssets:isSelectOriginalPhoto:
|
||||
- (void)imagePickerController:(TZImagePickerController *)picker didFinishPickingGifImage:(UIImage *)animatedImage sourceAssets:(PHAsset *)asset;
|
||||
|
||||
// Decide album show or not't
|
||||
// 决定相册显示与否 albumName:相册名字 result:相册原始数据
|
||||
- (BOOL)isAlbumCanSelect:(NSString *)albumName result:(PHFetchResult *)result;
|
||||
|
||||
// Decide asset show or not't
|
||||
// 决定照片显示与否
|
||||
- (BOOL)isAssetCanSelect:(PHAsset *)asset __attribute__((deprecated("Use -isAssetCanBeDisplayed:.")));
|
||||
- (BOOL)isAssetCanBeDisplayed:(PHAsset *)asset;
|
||||
|
||||
// Decide asset can be selected
|
||||
// 决定照片能否被选中
|
||||
- (BOOL)isAssetCanBeSelected:(PHAsset *)asset;
|
||||
@end
|
||||
|
||||
|
||||
@interface TZAlbumPickerController : UIViewController
|
||||
@property (nonatomic, assign) NSInteger columnNumber;
|
||||
@property (assign, nonatomic) BOOL isFirstAppear;
|
||||
- (void)configTableView;
|
||||
@end
|
||||
|
||||
|
||||
@interface UIImage (MyBundle)
|
||||
+ (UIImage *)tz_imageNamedFromMyBundle:(NSString *)name;
|
||||
@end
|
||||
|
||||
|
||||
@interface TZCommonTools : NSObject
|
||||
+ (UIEdgeInsets)tz_safeAreaInsets;
|
||||
+ (BOOL)tz_isIPhoneX;
|
||||
+ (BOOL)tz_isLandscape;
|
||||
+ (CGFloat)tz_statusBarHeight;
|
||||
// 获得Info.plist数据字典
|
||||
+ (NSDictionary *)tz_getInfoDictionary;
|
||||
+ (NSString *)tz_getAppName;
|
||||
+ (BOOL)tz_isRightToLeftLayout;
|
||||
+ (void)configBarButtonItem:(UIBarButtonItem *)item tzImagePickerVc:(TZImagePickerController *)tzImagePickerVc;
|
||||
+ (BOOL)isICloudSyncError:(NSError *)error;
|
||||
+ (BOOL)isAssetNotSelectable:(TZAssetModel *)model tzImagePickerVc:(TZImagePickerController *)tzImagePickerVc;
|
||||
@end
|
||||
|
||||
|
||||
@interface TZImagePickerConfig : NSObject
|
||||
+ (instancetype)sharedInstance;
|
||||
@property (copy, nonatomic) NSString *preferredLanguage;
|
||||
@property(nonatomic, assign) BOOL allowPickingImage;
|
||||
@property (nonatomic, assign) BOOL allowPickingVideo;
|
||||
@property (strong, nonatomic) NSBundle *languageBundle;
|
||||
@property (assign, nonatomic) BOOL showSelectedIndex;
|
||||
@property (assign, nonatomic) BOOL showPhotoCannotSelectLayer;
|
||||
@property (assign, nonatomic) BOOL notScaleImage;
|
||||
@property (assign, nonatomic) BOOL needFixComposition;
|
||||
|
||||
/// 默认是50,如果一个GIF过大,里面图片个数可能超过1000,会导致内存飙升而崩溃
|
||||
@property (assign, nonatomic) NSInteger gifPreviewMaxImagesCount;
|
||||
/// 【自定义GIF播放方案】为了避免内存过大,内部默认限制只播放50帧(平均取),可通过gifPreviewMaxImagesCount属性调整,若对GIF预览有更好的效果要求,可实现这个block采用FLAnimatedImage等三方库来播放,但注意FLAnimatedImage有播放速度较慢问题,自行取舍下。
|
||||
@property (nonatomic, copy) void (^gifImagePlayBlock)(TZPhotoPreviewView *view, UIImageView *imageView, NSData *gifData, NSDictionary *info);
|
||||
@end
|
||||
1102
Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZImagePickerController.m
generated
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// TZImageRequestOperation.h
|
||||
// TZImagePickerControllerFramework
|
||||
//
|
||||
// Created by 谭真 on 2018/12/20.
|
||||
// Copyright © 2018 谭真. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Photos/Photos.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TZImageRequestOperation : NSOperation
|
||||
|
||||
typedef void(^TZImageRequestCompletedBlock)(UIImage *photo, NSDictionary *info, BOOL isDegraded);
|
||||
typedef void(^TZImageRequestProgressBlock)(double progress, NSError *error, BOOL *stop, NSDictionary *info);
|
||||
|
||||
@property (nonatomic, copy, nullable) TZImageRequestCompletedBlock completedBlock;
|
||||
@property (nonatomic, copy, nullable) TZImageRequestProgressBlock progressBlock;
|
||||
@property (nonatomic, strong, nullable) PHAsset *asset;
|
||||
|
||||
@property (assign, nonatomic, getter = isExecuting) BOOL executing;
|
||||
@property (assign, nonatomic, getter = isFinished) BOOL finished;
|
||||
|
||||
- (instancetype)initWithAsset:(PHAsset *)asset completion:(TZImageRequestCompletedBlock)completionBlock progressHandler:(TZImageRequestProgressBlock)progressHandler;
|
||||
- (void)done;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,77 @@
|
||||
//
|
||||
// TZImageRequestOperation.m
|
||||
// TZImagePickerControllerFramework
|
||||
//
|
||||
// Created by 谭真 on 2018/12/20.
|
||||
// Copyright © 2018 谭真. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TZImageRequestOperation.h"
|
||||
#import "TZImageManager.h"
|
||||
|
||||
@implementation TZImageRequestOperation
|
||||
|
||||
@synthesize executing = _executing;
|
||||
@synthesize finished = _finished;
|
||||
|
||||
- (instancetype)initWithAsset:(PHAsset *)asset completion:(TZImageRequestCompletedBlock)completionBlock progressHandler:(TZImageRequestProgressBlock)progressHandler {
|
||||
self = [super init];
|
||||
self.asset = asset;
|
||||
self.completedBlock = completionBlock;
|
||||
self.progressBlock = progressHandler;
|
||||
_executing = NO;
|
||||
_finished = NO;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)start {
|
||||
self.executing = YES;
|
||||
[[TZImageManager manager] getPhotoWithAsset:self.asset completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (!isDegraded) {
|
||||
if (self.completedBlock) {
|
||||
self.completedBlock(photo, info, isDegraded);
|
||||
}
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[self done];
|
||||
});
|
||||
}
|
||||
});
|
||||
} progressHandler:^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (self.progressBlock) {
|
||||
self.progressBlock(progress, error, stop, info);
|
||||
}
|
||||
});
|
||||
} networkAccessAllowed:YES];
|
||||
}
|
||||
|
||||
- (void)done {
|
||||
self.finished = YES;
|
||||
self.executing = NO;
|
||||
[self reset];
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
self.asset = nil;
|
||||
self.completedBlock = nil;
|
||||
self.progressBlock = nil;
|
||||
}
|
||||
|
||||
- (void)setFinished:(BOOL)finished {
|
||||
[self willChangeValueForKey:@"isFinished"];
|
||||
_finished = finished;
|
||||
[self didChangeValueForKey:@"isFinished"];
|
||||
}
|
||||
|
||||
- (void)setExecuting:(BOOL)executing {
|
||||
[self willChangeValueForKey:@"isExecuting"];
|
||||
_executing = executing;
|
||||
[self didChangeValueForKey:@"isExecuting"];
|
||||
}
|
||||
|
||||
- (BOOL)isAsynchronous {
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// TZPhotoPickerController.h
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by 谭真 on 15/12/24.
|
||||
// Copyright © 2015年 谭真. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class TZAlbumModel;
|
||||
@interface TZPhotoPickerController : UIViewController
|
||||
|
||||
@property (nonatomic, assign) BOOL isFirstAppear;
|
||||
@property (nonatomic, assign) NSInteger columnNumber;
|
||||
@property (nonatomic, strong) TZAlbumModel *model;
|
||||
@end
|
||||
|
||||
|
||||
@interface TZCollectionView : UICollectionView
|
||||
|
||||
@end
|
||||
1274
Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZPhotoPickerController.m
generated
Executable file
76
Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZPhotoPreviewCell.h
generated
Normal file
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// TZPhotoPreviewCell.h
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by 谭真 on 15/12/24.
|
||||
// Copyright © 2015年 谭真. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class TZAssetModel;
|
||||
@interface TZAssetPreviewCell : UICollectionViewCell
|
||||
@property (nonatomic, strong) TZAssetModel *model;
|
||||
@property (nonatomic, copy) void (^singleTapGestureBlock)(void);
|
||||
- (void)configSubviews;
|
||||
- (void)photoPreviewCollectionViewDidScroll;
|
||||
@end
|
||||
|
||||
|
||||
@class TZAssetModel,TZProgressView,TZPhotoPreviewView;
|
||||
@interface TZPhotoPreviewCell : TZAssetPreviewCell
|
||||
|
||||
@property (nonatomic, copy) void (^imageProgressUpdateBlock)(double progress);
|
||||
|
||||
@property (nonatomic, strong) TZPhotoPreviewView *previewView;
|
||||
|
||||
@property (nonatomic, assign) BOOL allowCrop;
|
||||
@property (nonatomic, assign) CGRect cropRect;
|
||||
@property (nonatomic, assign) BOOL scaleAspectFillCrop;
|
||||
|
||||
- (void)recoverSubviews;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface TZPhotoPreviewView : UIView
|
||||
@property (nonatomic, strong) UIImageView *imageView;
|
||||
@property (nonatomic, strong) UIScrollView *scrollView;
|
||||
@property (nonatomic, strong) UIView *imageContainerView;
|
||||
@property (nonatomic, strong) TZProgressView *progressView;
|
||||
@property (nonatomic, strong) UIImageView *iCloudErrorIcon;
|
||||
@property (nonatomic, strong) UILabel *iCloudErrorLabel;
|
||||
@property (nonatomic, copy) void (^iCloudSyncFailedHandle)(id asset, BOOL isSyncFailed);
|
||||
|
||||
|
||||
@property (nonatomic, assign) BOOL allowCrop;
|
||||
@property (nonatomic, assign) CGRect cropRect;
|
||||
@property (nonatomic, assign) BOOL scaleAspectFillCrop;
|
||||
@property (nonatomic, strong) TZAssetModel *model;
|
||||
@property (nonatomic, strong) id asset;
|
||||
@property (nonatomic, copy) void (^singleTapGestureBlock)(void);
|
||||
@property (nonatomic, copy) void (^imageProgressUpdateBlock)(double progress);
|
||||
|
||||
@property (nonatomic, assign) int32_t imageRequestID;
|
||||
|
||||
- (void)recoverSubviews;
|
||||
@end
|
||||
|
||||
|
||||
@class AVPlayer, AVPlayerLayer;
|
||||
@interface TZVideoPreviewCell : TZAssetPreviewCell
|
||||
@property (strong, nonatomic) AVPlayer *player;
|
||||
@property (strong, nonatomic) AVPlayerLayer *playerLayer;
|
||||
@property (strong, nonatomic) UIButton *playButton;
|
||||
@property (strong, nonatomic) UIImage *cover;
|
||||
@property (nonatomic, strong) NSURL *videoURL;
|
||||
@property (nonatomic, strong) UIImageView *iCloudErrorIcon;
|
||||
@property (nonatomic, strong) UILabel *iCloudErrorLabel;
|
||||
@property (nonatomic, copy) void (^iCloudSyncFailedHandle)(id asset, BOOL isSyncFailed);
|
||||
- (void)pausePlayerAndShowNaviBar;
|
||||
@end
|
||||
|
||||
|
||||
@interface TZGifPreviewCell : TZAssetPreviewCell
|
||||
@property (strong, nonatomic) TZPhotoPreviewView *previewView;
|
||||
@end
|
||||
576
Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZPhotoPreviewCell.m
generated
Normal file
@@ -0,0 +1,576 @@
|
||||
//
|
||||
// TZPhotoPreviewCell.m
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by 谭真 on 15/12/24.
|
||||
// Copyright © 2015年 谭真. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TZPhotoPreviewCell.h"
|
||||
#import "TZAssetModel.h"
|
||||
#import "UIView+TZLayout.h"
|
||||
#import "TZImageManager.h"
|
||||
#import "TZProgressView.h"
|
||||
#import "TZImageCropManager.h"
|
||||
#import <MediaPlayer/MediaPlayer.h>
|
||||
#import "TZImagePickerController.h"
|
||||
|
||||
@implementation TZAssetPreviewCell
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.backgroundColor = [UIColor blackColor];
|
||||
[self configSubviews];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(photoPreviewCollectionViewDidScroll) name:@"photoPreviewCollectionViewDidScroll" object:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)configSubviews {
|
||||
|
||||
}
|
||||
|
||||
#pragma mark - Notification
|
||||
|
||||
- (void)photoPreviewCollectionViewDidScroll {
|
||||
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation TZPhotoPreviewCell
|
||||
|
||||
- (void)configSubviews {
|
||||
self.previewView = [[TZPhotoPreviewView alloc] initWithFrame:CGRectZero];
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[self.previewView setSingleTapGestureBlock:^{
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
if (strongSelf.singleTapGestureBlock) {
|
||||
strongSelf.singleTapGestureBlock();
|
||||
}
|
||||
}];
|
||||
[self.previewView setImageProgressUpdateBlock:^(double progress) {
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
if (strongSelf.imageProgressUpdateBlock) {
|
||||
strongSelf.imageProgressUpdateBlock(progress);
|
||||
}
|
||||
}];
|
||||
[self.contentView addSubview:self.previewView];
|
||||
}
|
||||
|
||||
- (void)setModel:(TZAssetModel *)model {
|
||||
[super setModel:model];
|
||||
_previewView.model = model;
|
||||
}
|
||||
|
||||
- (void)recoverSubviews {
|
||||
[_previewView recoverSubviews];
|
||||
}
|
||||
|
||||
- (void)setAllowCrop:(BOOL)allowCrop {
|
||||
_allowCrop = allowCrop;
|
||||
_previewView.allowCrop = allowCrop;
|
||||
}
|
||||
|
||||
- (void)setScaleAspectFillCrop:(BOOL)scaleAspectFillCrop {
|
||||
_scaleAspectFillCrop = scaleAspectFillCrop;
|
||||
_previewView.scaleAspectFillCrop = scaleAspectFillCrop;
|
||||
}
|
||||
|
||||
- (void)setCropRect:(CGRect)cropRect {
|
||||
_cropRect = cropRect;
|
||||
_previewView.cropRect = cropRect;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
self.previewView.frame = self.bounds;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface TZPhotoPreviewView ()<UIScrollViewDelegate>
|
||||
@property (assign, nonatomic) BOOL isRequestingGIF;
|
||||
@end
|
||||
|
||||
@implementation TZPhotoPreviewView
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
_scrollView = [[UIScrollView alloc] init];
|
||||
_scrollView.bouncesZoom = YES;
|
||||
_scrollView.maximumZoomScale = 4;
|
||||
_scrollView.minimumZoomScale = 1.0;
|
||||
_scrollView.multipleTouchEnabled = YES;
|
||||
_scrollView.delegate = self;
|
||||
_scrollView.scrollsToTop = NO;
|
||||
_scrollView.showsHorizontalScrollIndicator = NO;
|
||||
_scrollView.showsVerticalScrollIndicator = YES;
|
||||
_scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
_scrollView.delaysContentTouches = NO;
|
||||
_scrollView.canCancelContentTouches = YES;
|
||||
_scrollView.alwaysBounceVertical = NO;
|
||||
if (@available(iOS 11, *)) {
|
||||
_scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
||||
}
|
||||
[self addSubview:_scrollView];
|
||||
|
||||
_imageContainerView = [[UIView alloc] init];
|
||||
_imageContainerView.clipsToBounds = YES;
|
||||
_imageContainerView.contentMode = UIViewContentModeScaleAspectFill;
|
||||
[_scrollView addSubview:_imageContainerView];
|
||||
|
||||
_imageView = [[UIImageView alloc] init];
|
||||
_imageView.backgroundColor = [UIColor colorWithWhite:1.000 alpha:0.500];
|
||||
_imageView.contentMode = UIViewContentModeScaleAspectFill;
|
||||
_imageView.clipsToBounds = YES;
|
||||
[_imageContainerView addSubview:_imageView];
|
||||
|
||||
_iCloudErrorIcon = [[UIImageView alloc] init];
|
||||
_iCloudErrorIcon.image = [UIImage tz_imageNamedFromMyBundle:@"iCloudError"];
|
||||
_iCloudErrorIcon.hidden = YES;
|
||||
[self addSubview:_iCloudErrorIcon];
|
||||
_iCloudErrorLabel = [[UILabel alloc] init];
|
||||
_iCloudErrorLabel.font = [UIFont systemFontOfSize:10];
|
||||
_iCloudErrorLabel.textColor = [UIColor whiteColor];
|
||||
_iCloudErrorLabel.text = [NSBundle tz_localizedStringForKey:@"iCloud sync failed"];
|
||||
_iCloudErrorLabel.hidden = YES;
|
||||
[self addSubview:_iCloudErrorLabel];
|
||||
|
||||
UITapGestureRecognizer *tap1 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singleTap:)];
|
||||
[self addGestureRecognizer:tap1];
|
||||
UITapGestureRecognizer *tap2 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
|
||||
tap2.numberOfTapsRequired = 2;
|
||||
[tap1 requireGestureRecognizerToFail:tap2];
|
||||
[self addGestureRecognizer:tap2];
|
||||
|
||||
[self configProgressView];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)configProgressView {
|
||||
_progressView = [[TZProgressView alloc] init];
|
||||
_progressView.hidden = YES;
|
||||
[self addSubview:_progressView];
|
||||
}
|
||||
|
||||
- (void)setModel:(TZAssetModel *)model {
|
||||
_model = model;
|
||||
self.isRequestingGIF = NO;
|
||||
[_scrollView setZoomScale:1.0 animated:NO];
|
||||
if (model.type == TZAssetModelMediaTypePhotoGif) {
|
||||
// 先显示缩略图
|
||||
[[TZImageManager manager] getPhotoWithAsset:model.asset completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) {
|
||||
if (photo) {
|
||||
self.imageView.image = photo;
|
||||
}
|
||||
[self resizeSubviews];
|
||||
if (self.isRequestingGIF) {
|
||||
return;
|
||||
}
|
||||
// 再显示gif动图
|
||||
self.isRequestingGIF = YES;
|
||||
[[TZImageManager manager] getOriginalPhotoDataWithAsset:model.asset progressHandler:^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
|
||||
progress = progress > 0.02 ? progress : 0.02;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
BOOL iCloudSyncFailed = [TZCommonTools isICloudSyncError:error];
|
||||
self.iCloudErrorLabel.hidden = !iCloudSyncFailed;
|
||||
self.iCloudErrorIcon.hidden = !iCloudSyncFailed;
|
||||
if (self.iCloudSyncFailedHandle) {
|
||||
self.iCloudSyncFailedHandle(model.asset, iCloudSyncFailed);
|
||||
}
|
||||
|
||||
self.progressView.progress = progress;
|
||||
if (progress >= 1) {
|
||||
self.progressView.hidden = YES;
|
||||
} else {
|
||||
self.progressView.hidden = NO;
|
||||
}
|
||||
});
|
||||
#ifdef DEBUG
|
||||
NSLog(@"[TZImagePickerController] getOriginalPhotoDataWithAsset:%f error:%@", progress, error);
|
||||
#endif
|
||||
} completion:^(NSData *data, NSDictionary *info, BOOL isDegraded) {
|
||||
if (!isDegraded) {
|
||||
self.isRequestingGIF = NO;
|
||||
self.progressView.hidden = YES;
|
||||
if ([TZImagePickerConfig sharedInstance].gifImagePlayBlock) {
|
||||
[TZImagePickerConfig sharedInstance].gifImagePlayBlock(self, self.imageView, data, info);
|
||||
} else {
|
||||
self.imageView.image = [UIImage sd_tz_animatedGIFWithData:data];
|
||||
}
|
||||
[self resizeSubviews];
|
||||
}
|
||||
}];
|
||||
} progressHandler:nil networkAccessAllowed:NO];
|
||||
} else {
|
||||
self.asset = model.asset;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setAsset:(PHAsset *)asset {
|
||||
if (_asset && self.imageRequestID) {
|
||||
[[PHImageManager defaultManager] cancelImageRequest:self.imageRequestID];
|
||||
}
|
||||
|
||||
_asset = asset;
|
||||
self.imageRequestID = [[TZImageManager manager] getPhotoWithAsset:asset completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) {
|
||||
BOOL iCloudSyncFailed = !photo && [TZCommonTools isICloudSyncError:info[PHImageErrorKey]];
|
||||
self.iCloudErrorLabel.hidden = !iCloudSyncFailed;
|
||||
self.iCloudErrorIcon.hidden = !iCloudSyncFailed;
|
||||
if (self.iCloudSyncFailedHandle) {
|
||||
self.iCloudSyncFailedHandle(asset, iCloudSyncFailed);
|
||||
}
|
||||
if (![asset isEqual:self->_asset]) return;
|
||||
if (photo) {
|
||||
self.imageView.image = photo;
|
||||
}
|
||||
[self resizeSubviews];
|
||||
if (self.imageView.tz_height && self.allowCrop) {
|
||||
CGFloat scale = MAX(self.cropRect.size.width / self.imageView.tz_width, self.cropRect.size.height / self.imageView.tz_height);
|
||||
if (self.scaleAspectFillCrop && scale > 1) { // 如果设置图片缩放裁剪并且图片需要缩放
|
||||
CGFloat multiple = self.scrollView.maximumZoomScale / self.scrollView.minimumZoomScale;
|
||||
self.scrollView.minimumZoomScale = scale;
|
||||
self.scrollView.maximumZoomScale = scale * MAX(multiple, 2);
|
||||
[self.scrollView setZoomScale:scale animated:YES];
|
||||
}
|
||||
}
|
||||
|
||||
self->_progressView.hidden = YES;
|
||||
if (self.imageProgressUpdateBlock) {
|
||||
self.imageProgressUpdateBlock(1);
|
||||
}
|
||||
if (!isDegraded) {
|
||||
self.imageRequestID = 0;
|
||||
}
|
||||
} progressHandler:^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
|
||||
if (![asset isEqual:self->_asset]) return;
|
||||
self->_progressView.hidden = NO;
|
||||
[self bringSubviewToFront:self->_progressView];
|
||||
progress = progress > 0.02 ? progress : 0.02;
|
||||
self->_progressView.progress = progress;
|
||||
if (self.imageProgressUpdateBlock && progress < 1) {
|
||||
self.imageProgressUpdateBlock(progress);
|
||||
}
|
||||
|
||||
if (progress >= 1) {
|
||||
self->_progressView.hidden = YES;
|
||||
self.imageRequestID = 0;
|
||||
}
|
||||
} networkAccessAllowed:YES];
|
||||
|
||||
[self configMaximumZoomScale];
|
||||
}
|
||||
|
||||
- (void)recoverSubviews {
|
||||
[_scrollView setZoomScale:_scrollView.minimumZoomScale animated:NO];
|
||||
[self resizeSubviews];
|
||||
}
|
||||
|
||||
- (void)resizeSubviews {
|
||||
_imageContainerView.tz_origin = CGPointZero;
|
||||
_imageContainerView.tz_width = self.scrollView.tz_width;
|
||||
|
||||
UIImage *image = _imageView.image;
|
||||
if (image.size.height / image.size.width > self.tz_height / self.scrollView.tz_width) {
|
||||
CGFloat width = image.size.width / image.size.height * self.scrollView.tz_height;
|
||||
if (width < 1 || isnan(width)) width = self.tz_width;
|
||||
width = floor(width);
|
||||
|
||||
_imageContainerView.tz_width = width;
|
||||
_imageContainerView.tz_height = self.tz_height;
|
||||
_imageContainerView.tz_centerX = self.scrollView.tz_width / 2;
|
||||
} else {
|
||||
CGFloat height = image.size.height / image.size.width * self.scrollView.tz_width;
|
||||
if (height < 1 || isnan(height)) height = self.tz_height;
|
||||
height = floor(height);
|
||||
_imageContainerView.tz_height = height;
|
||||
_imageContainerView.tz_centerY = self.tz_height / 2;
|
||||
}
|
||||
if (_imageContainerView.tz_height > self.tz_height && _imageContainerView.tz_height - self.tz_height <= 1) {
|
||||
_imageContainerView.tz_height = self.tz_height;
|
||||
}
|
||||
CGFloat contentSizeH = MAX(_imageContainerView.tz_height, self.tz_height);
|
||||
_scrollView.contentSize = CGSizeMake(self.scrollView.tz_width, contentSizeH);
|
||||
[_scrollView scrollRectToVisible:self.bounds animated:NO];
|
||||
_scrollView.alwaysBounceVertical = _imageContainerView.tz_height <= self.tz_height ? NO : YES;
|
||||
_imageView.frame = _imageContainerView.bounds;
|
||||
|
||||
[self refreshScrollViewContentSize];
|
||||
}
|
||||
|
||||
- (void)configMaximumZoomScale {
|
||||
_scrollView.maximumZoomScale = _allowCrop ? 6.0 : 4.0;
|
||||
|
||||
if ([self.asset isKindOfClass:[PHAsset class]]) {
|
||||
PHAsset *phAsset = (PHAsset *)self.asset;
|
||||
CGFloat aspectRatio = phAsset.pixelWidth / (CGFloat)phAsset.pixelHeight;
|
||||
// 优化超宽图片的显示
|
||||
if (aspectRatio > 1.5) {
|
||||
self.scrollView.maximumZoomScale *= aspectRatio / 1.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)refreshScrollViewContentSize {
|
||||
if (_allowCrop) {
|
||||
// 1.7.2 如果允许裁剪,需要让图片的任意部分都能在裁剪框内,于是对_scrollView做了如下处理:
|
||||
// 1.让contentSize增大(裁剪框右下角的图片部分)
|
||||
CGFloat contentWidthAdd = (MIN(_imageContainerView.tz_width, self.scrollView.tz_width) - _cropRect.size.width) / 2;
|
||||
CGFloat contentHeightAdd = (MIN(_imageContainerView.tz_height, self.scrollView.tz_height) - _cropRect.size.height) / 2;
|
||||
CGFloat newSizeW = MAX(self.scrollView.contentSize.width, self.scrollView.tz_width) + contentWidthAdd;
|
||||
CGFloat newSizeH = MAX(self.scrollView.contentSize.height, self.scrollView.tz_height) + contentHeightAdd;
|
||||
_scrollView.contentSize = CGSizeMake(newSizeW, newSizeH);
|
||||
_scrollView.alwaysBounceVertical = YES;
|
||||
// 2.让scrollView新增滑动区域(裁剪框左上角的图片部分)
|
||||
if (contentHeightAdd > 0 || contentWidthAdd > 0) {
|
||||
_scrollView.contentInset = UIEdgeInsetsMake(MAX(contentHeightAdd, 0), MAX(contentWidthAdd, 0), 0, 0);
|
||||
} else {
|
||||
_scrollView.contentInset = UIEdgeInsetsZero;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
_scrollView.frame = CGRectMake(10, 0, self.tz_width - 20, self.tz_height);
|
||||
static CGFloat progressWH = 40;
|
||||
CGFloat progressX = (self.tz_width - progressWH) / 2;
|
||||
CGFloat progressY = (self.tz_height - progressWH) / 2;
|
||||
_progressView.frame = CGRectMake(progressX, progressY, progressWH, progressWH);
|
||||
[self recoverSubviews];
|
||||
_iCloudErrorIcon.frame = CGRectMake(20, [TZCommonTools tz_statusBarHeight] + 44 + 10, 28, 28);
|
||||
_iCloudErrorLabel.frame = CGRectMake(53, [TZCommonTools tz_statusBarHeight] + 44 + 10, self.tz_width - 63, 28);
|
||||
}
|
||||
|
||||
#pragma mark - UITapGestureRecognizer Event
|
||||
|
||||
- (void)doubleTap:(UITapGestureRecognizer *)tap {
|
||||
if (_scrollView.zoomScale > _scrollView.minimumZoomScale) {
|
||||
_scrollView.contentInset = UIEdgeInsetsZero;
|
||||
[_scrollView setZoomScale:_scrollView.minimumZoomScale animated:YES];
|
||||
} else {
|
||||
CGPoint touchPoint = [tap locationInView:self.imageView];
|
||||
CGFloat newZoomScale = MIN(_scrollView.maximumZoomScale, 2.5);
|
||||
CGFloat xsize = self.frame.size.width / newZoomScale;
|
||||
CGFloat ysize = self.frame.size.height / newZoomScale;
|
||||
[_scrollView zoomToRect:CGRectMake(touchPoint.x - xsize/2, touchPoint.y - ysize/2, xsize, ysize) animated:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)singleTap:(UITapGestureRecognizer *)tap {
|
||||
if (self.singleTapGestureBlock) {
|
||||
self.singleTapGestureBlock();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - UIScrollViewDelegate
|
||||
|
||||
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
|
||||
return _imageContainerView;
|
||||
}
|
||||
|
||||
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view {
|
||||
scrollView.contentInset = UIEdgeInsetsZero;
|
||||
}
|
||||
|
||||
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
|
||||
[self refreshImageContainerViewCenter];
|
||||
}
|
||||
|
||||
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale {
|
||||
[self refreshScrollViewContentSize];
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (void)refreshImageContainerViewCenter {
|
||||
CGFloat offsetX = (_scrollView.tz_width > _scrollView.contentSize.width) ? ((_scrollView.tz_width - _scrollView.contentSize.width) * 0.5) : 0.0;
|
||||
CGFloat offsetY = (_scrollView.tz_height > _scrollView.contentSize.height) ? ((_scrollView.tz_height - _scrollView.contentSize.height) * 0.5) : 0.0;
|
||||
self.imageContainerView.center = CGPointMake(_scrollView.contentSize.width * 0.5 + offsetX, _scrollView.contentSize.height * 0.5 + offsetY);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation TZVideoPreviewCell
|
||||
|
||||
- (void)configSubviews {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillResignActiveNotification) name:UIApplicationWillResignActiveNotification object:nil];
|
||||
_iCloudErrorIcon = [[UIImageView alloc] init];
|
||||
_iCloudErrorIcon.image = [UIImage tz_imageNamedFromMyBundle:@"iCloudError"];
|
||||
_iCloudErrorIcon.hidden = YES;
|
||||
_iCloudErrorLabel = [[UILabel alloc] init];
|
||||
_iCloudErrorLabel.font = [UIFont systemFontOfSize:10];
|
||||
_iCloudErrorLabel.textColor = [UIColor whiteColor];
|
||||
_iCloudErrorLabel.text = [NSBundle tz_localizedStringForKey:@"iCloud sync failed"];
|
||||
_iCloudErrorLabel.hidden = YES;
|
||||
}
|
||||
|
||||
- (void)configPlayButton {
|
||||
if (_playButton) {
|
||||
[_playButton removeFromSuperview];
|
||||
}
|
||||
_playButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlay"] forState:UIControlStateNormal];
|
||||
[_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlayHL"] forState:UIControlStateHighlighted];
|
||||
[_playButton addTarget:self action:@selector(playButtonClick) forControlEvents:UIControlEventTouchUpInside];
|
||||
_playButton.frame = CGRectMake(0, 64, self.tz_width, self.tz_height - 64 - 44);
|
||||
[self.contentView addSubview:_playButton];
|
||||
[self.contentView addSubview:_iCloudErrorIcon];
|
||||
[self.contentView addSubview:_iCloudErrorLabel];
|
||||
}
|
||||
|
||||
- (void)setModel:(TZAssetModel *)model {
|
||||
[super setModel:model];
|
||||
[self configMoviePlayer];
|
||||
}
|
||||
|
||||
- (void)setVideoURL:(NSURL *)videoURL {
|
||||
_videoURL = videoURL;
|
||||
[self configMoviePlayer];
|
||||
}
|
||||
|
||||
- (void)configMoviePlayer {
|
||||
if (_player) {
|
||||
[_playerLayer removeFromSuperlayer];
|
||||
_playerLayer = nil;
|
||||
[_player pause];
|
||||
_player = nil;
|
||||
}
|
||||
|
||||
if (self.model && self.model.asset) {
|
||||
[[TZImageManager manager] getPhotoWithAsset:self.model.asset completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) {
|
||||
BOOL iCloudSyncFailed = !photo && [TZCommonTools isICloudSyncError:info[PHImageErrorKey]];
|
||||
self.iCloudErrorLabel.hidden = !iCloudSyncFailed;
|
||||
self.iCloudErrorIcon.hidden = !iCloudSyncFailed;
|
||||
if (self.iCloudSyncFailedHandle) {
|
||||
self.iCloudSyncFailedHandle(self.model.asset, iCloudSyncFailed);
|
||||
}
|
||||
if (photo) {
|
||||
self.cover = photo;
|
||||
}
|
||||
}];
|
||||
[[TZImageManager manager] getVideoWithAsset:self.model.asset completion:^(AVPlayerItem *playerItem, NSDictionary *info) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
BOOL iCloudSyncFailed = !playerItem && [TZCommonTools isICloudSyncError:info[PHImageErrorKey]];
|
||||
self.iCloudErrorLabel.hidden = !iCloudSyncFailed;
|
||||
self.iCloudErrorIcon.hidden = !iCloudSyncFailed;
|
||||
if (self.iCloudSyncFailedHandle) {
|
||||
self.iCloudSyncFailedHandle(self.model.asset, iCloudSyncFailed);
|
||||
}
|
||||
[self configPlayerWithItem:playerItem];
|
||||
});
|
||||
}];
|
||||
} else {
|
||||
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:self.videoURL];
|
||||
[self configPlayerWithItem:playerItem];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)configPlayerWithItem:(AVPlayerItem *)playerItem {
|
||||
self.player = [AVPlayer playerWithPlayerItem:playerItem];
|
||||
self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
|
||||
self.playerLayer.backgroundColor = [UIColor blackColor].CGColor;
|
||||
self.playerLayer.frame = self.bounds;
|
||||
[self.contentView.layer addSublayer:self.playerLayer];
|
||||
[self configPlayButton];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pausePlayerAndShowNaviBar) name:AVPlayerItemDidPlayToEndTimeNotification object:self.player.currentItem];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
_playerLayer.frame = self.bounds;
|
||||
_playButton.frame = CGRectMake(0, 64, self.tz_width, self.tz_height - 64 - 44);
|
||||
_iCloudErrorIcon.frame = CGRectMake(20, [TZCommonTools tz_statusBarHeight] + 44 + 10, 28, 28);
|
||||
_iCloudErrorLabel.frame = CGRectMake(53, [TZCommonTools tz_statusBarHeight] + 44 + 10, self.tz_width - 63, 28);
|
||||
}
|
||||
|
||||
- (void)photoPreviewCollectionViewDidScroll {
|
||||
if (_player && _player.rate != 0.0) {
|
||||
[self pausePlayerAndShowNaviBar];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Notification
|
||||
|
||||
- (void)appWillResignActiveNotification {
|
||||
if (_player && _player.rate != 0.0) {
|
||||
[self pausePlayerAndShowNaviBar];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Click Event
|
||||
|
||||
- (void)playButtonClick {
|
||||
CMTime currentTime = _player.currentItem.currentTime;
|
||||
CMTime durationTime = _player.currentItem.duration;
|
||||
if (_player.rate == 0.0f) {
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"TZ_VIDEO_PLAY_NOTIFICATION" object:_player];
|
||||
if (currentTime.value == durationTime.value) [_player.currentItem seekToTime:CMTimeMake(0, 1)];
|
||||
[_player play];
|
||||
[_playButton setImage:nil forState:UIControlStateNormal];
|
||||
[UIApplication sharedApplication].statusBarHidden = YES;
|
||||
if (self.singleTapGestureBlock) {
|
||||
self.singleTapGestureBlock();
|
||||
}
|
||||
} else {
|
||||
[self pausePlayerAndShowNaviBar];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)pausePlayerAndShowNaviBar {
|
||||
[_player pause];
|
||||
[_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlay"] forState:UIControlStateNormal];
|
||||
if (self.singleTapGestureBlock) {
|
||||
self.singleTapGestureBlock();
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation TZGifPreviewCell
|
||||
|
||||
- (void)configSubviews {
|
||||
[self configPreviewView];
|
||||
}
|
||||
|
||||
- (void)configPreviewView {
|
||||
_previewView = [[TZPhotoPreviewView alloc] initWithFrame:CGRectZero];
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[_previewView setSingleTapGestureBlock:^{
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
[strongSelf signleTapAction];
|
||||
}];
|
||||
[self.contentView addSubview:_previewView];
|
||||
}
|
||||
|
||||
- (void)setModel:(TZAssetModel *)model {
|
||||
[super setModel:model];
|
||||
_previewView.model = self.model;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
_previewView.frame = self.bounds;
|
||||
}
|
||||
|
||||
#pragma mark - Click Event
|
||||
|
||||
- (void)signleTapAction {
|
||||
if (self.singleTapGestureBlock) {
|
||||
self.singleTapGestureBlock();
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// TZPhotoPreviewController.h
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by 谭真 on 15/12/24.
|
||||
// Copyright © 2015年 谭真. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface TZPhotoPreviewController : UIViewController
|
||||
|
||||
@property (nonatomic, strong) NSMutableArray *models; ///< All photo models / 所有图片模型数组
|
||||
@property (nonatomic, strong) NSMutableArray *photos; ///< All photos / 所有图片数组
|
||||
@property (nonatomic, assign) NSInteger currentIndex; ///< Index of the photo user click / 用户点击的图片的索引
|
||||
@property (nonatomic, assign) BOOL isSelectOriginalPhoto; ///< If YES,return original photo / 是否返回原图
|
||||
@property (nonatomic, assign) BOOL isCropImage;
|
||||
|
||||
/// Return the new selected photos / 返回最新的选中图片数组
|
||||
@property (nonatomic, copy) void (^backButtonClickBlock)(BOOL isSelectOriginalPhoto);
|
||||
@property (nonatomic, copy) void (^doneButtonClickBlock)(BOOL isSelectOriginalPhoto);
|
||||
@property (nonatomic, copy) void (^doneButtonClickBlockCropMode)(UIImage *cropedImage,id asset);
|
||||
@property (nonatomic, copy) void (^doneButtonClickBlockWithPreviewType)(NSArray<UIImage *> *photos,NSArray *assets,BOOL isSelectOriginalPhoto);
|
||||
|
||||
@end
|
||||
686
Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZPhotoPreviewController.m
generated
Normal file
@@ -0,0 +1,686 @@
|
||||
//
|
||||
// TZPhotoPreviewController.m
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by 谭真 on 15/12/24.
|
||||
// Copyright © 2015年 谭真. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TZPhotoPreviewController.h"
|
||||
#import "TZPhotoPreviewCell.h"
|
||||
#import "TZAssetModel.h"
|
||||
#import "UIView+TZLayout.h"
|
||||
#import "TZImagePickerController.h"
|
||||
#import "TZImageManager.h"
|
||||
#import "TZImageCropManager.h"
|
||||
|
||||
@interface TZPhotoPreviewController ()<UICollectionViewDataSource,UICollectionViewDelegate,UIScrollViewDelegate> {
|
||||
UICollectionView *_collectionView;
|
||||
UICollectionViewFlowLayout *_layout;
|
||||
NSArray *_photosTemp;
|
||||
NSArray *_assetsTemp;
|
||||
|
||||
UIView *_naviBar;
|
||||
UIButton *_backButton;
|
||||
UIButton *_selectButton;
|
||||
UILabel *_indexLabel;
|
||||
|
||||
UIView *_toolBar;
|
||||
UIButton *_doneButton;
|
||||
UIImageView *_numberImageView;
|
||||
UILabel *_numberLabel;
|
||||
UIButton *_originalPhotoButton;
|
||||
UILabel *_originalPhotoLabel;
|
||||
|
||||
CGFloat _offsetItemCount;
|
||||
|
||||
BOOL _didSetIsSelectOriginalPhoto;
|
||||
}
|
||||
@property (nonatomic, assign) BOOL isHideNaviBar;
|
||||
@property (nonatomic, strong) UIView *cropBgView;
|
||||
@property (nonatomic, strong) UIView *cropView;
|
||||
|
||||
@property (nonatomic, assign) double progress;
|
||||
@property (strong, nonatomic) UIAlertController *alertView;
|
||||
@property (nonatomic, strong) UIView *iCloudErrorView;
|
||||
@end
|
||||
|
||||
@implementation TZPhotoPreviewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
[TZImageManager manager].shouldFixOrientation = YES;
|
||||
TZImagePickerController *_tzImagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
if (!_didSetIsSelectOriginalPhoto) {
|
||||
_isSelectOriginalPhoto = _tzImagePickerVc.isSelectOriginalPhoto;
|
||||
}
|
||||
if (!self.models.count) {
|
||||
self.models = [NSMutableArray arrayWithArray:_tzImagePickerVc.selectedModels];
|
||||
_assetsTemp = [NSMutableArray arrayWithArray:_tzImagePickerVc.selectedAssets];
|
||||
}
|
||||
[self configCollectionView];
|
||||
[self configCustomNaviBar];
|
||||
[self configBottomToolBar];
|
||||
self.view.clipsToBounds = YES;
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didChangeStatusBarOrientationNotification:) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)setIsSelectOriginalPhoto:(BOOL)isSelectOriginalPhoto {
|
||||
_isSelectOriginalPhoto = isSelectOriginalPhoto;
|
||||
_didSetIsSelectOriginalPhoto = YES;
|
||||
}
|
||||
|
||||
- (void)setPhotos:(NSMutableArray *)photos {
|
||||
_photos = photos;
|
||||
_photosTemp = [NSArray arrayWithArray:photos];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
[self.navigationController setNavigationBarHidden:YES animated:YES];
|
||||
[UIApplication sharedApplication].statusBarHidden = YES;
|
||||
[_collectionView setContentOffset:CGPointMake((self.view.tz_width + 20) * self.currentIndex, 0) animated:NO];
|
||||
[self refreshNaviBarAndBottomBarState];
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
if (tzImagePickerVc.needShowStatusBar) {
|
||||
[UIApplication sharedApplication].statusBarHidden = NO;
|
||||
}
|
||||
[self.navigationController setNavigationBarHidden:NO animated:YES];
|
||||
[TZImageManager manager].shouldFixOrientation = NO;
|
||||
}
|
||||
|
||||
- (BOOL)prefersStatusBarHidden {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)configCustomNaviBar {
|
||||
TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
|
||||
_naviBar = [[UIView alloc] initWithFrame:CGRectZero];
|
||||
_naviBar.backgroundColor = [UIColor colorWithRed:(34/255.0) green:(34/255.0) blue:(34/255.0) alpha:0.7];
|
||||
|
||||
_backButton = [[UIButton alloc] initWithFrame:CGRectZero];
|
||||
[_backButton setImage:[UIImage tz_imageNamedFromMyBundle:@"navi_back"] forState:UIControlStateNormal];
|
||||
[_backButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
||||
[_backButton addTarget:self action:@selector(backButtonClick) forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
_selectButton = [[UIButton alloc] initWithFrame:CGRectZero];
|
||||
[_selectButton setImage:tzImagePickerVc.photoDefImage forState:UIControlStateNormal];
|
||||
[_selectButton setImage:tzImagePickerVc.photoSelImage forState:UIControlStateSelected];
|
||||
_selectButton.imageView.clipsToBounds = YES;
|
||||
_selectButton.imageEdgeInsets = UIEdgeInsetsMake(10, 0, 10, 0);
|
||||
_selectButton.imageView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
[_selectButton addTarget:self action:@selector(select:) forControlEvents:UIControlEventTouchUpInside];
|
||||
_selectButton.hidden = !tzImagePickerVc.showSelectBtn;
|
||||
|
||||
_indexLabel = [[UILabel alloc] init];
|
||||
_indexLabel.adjustsFontSizeToFitWidth = YES;
|
||||
_indexLabel.font = [UIFont systemFontOfSize:14];
|
||||
_indexLabel.textColor = [UIColor whiteColor];
|
||||
_indexLabel.textAlignment = NSTextAlignmentCenter;
|
||||
|
||||
[_naviBar addSubview:_selectButton];
|
||||
[_naviBar addSubview:_indexLabel];
|
||||
[_naviBar addSubview:_backButton];
|
||||
[self.view addSubview:_naviBar];
|
||||
}
|
||||
|
||||
- (void)configBottomToolBar {
|
||||
_toolBar = [[UIView alloc] initWithFrame:CGRectZero];
|
||||
static CGFloat rgb = 34 / 255.0;
|
||||
_toolBar.backgroundColor = [UIColor colorWithRed:rgb green:rgb blue:rgb alpha:0.7];
|
||||
|
||||
TZImagePickerController *_tzImagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
if (_tzImagePickerVc.allowPickingOriginalPhoto) {
|
||||
_originalPhotoButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
_originalPhotoButton.imageEdgeInsets = UIEdgeInsetsMake(0, [TZCommonTools tz_isRightToLeftLayout] ? 10 : -10, 0, 0);
|
||||
_originalPhotoButton.backgroundColor = [UIColor clearColor];
|
||||
[_originalPhotoButton addTarget:self action:@selector(originalPhotoButtonClick) forControlEvents:UIControlEventTouchUpInside];
|
||||
_originalPhotoButton.titleLabel.font = [UIFont systemFontOfSize:13];
|
||||
[_originalPhotoButton setTitle:_tzImagePickerVc.fullImageBtnTitleStr forState:UIControlStateNormal];
|
||||
[_originalPhotoButton setTitle:_tzImagePickerVc.fullImageBtnTitleStr forState:UIControlStateSelected];
|
||||
[_originalPhotoButton setTitleColor:[UIColor lightGrayColor] forState:UIControlStateNormal];
|
||||
[_originalPhotoButton setTitleColor:[UIColor whiteColor] forState:UIControlStateSelected];
|
||||
[_originalPhotoButton setImage:_tzImagePickerVc.photoPreviewOriginDefImage forState:UIControlStateNormal];
|
||||
[_originalPhotoButton setImage:_tzImagePickerVc.photoOriginSelImage forState:UIControlStateSelected];
|
||||
|
||||
_originalPhotoLabel = [[UILabel alloc] init];
|
||||
_originalPhotoLabel.textAlignment = NSTextAlignmentLeft;
|
||||
_originalPhotoLabel.font = [UIFont systemFontOfSize:13];
|
||||
_originalPhotoLabel.textColor = [UIColor whiteColor];
|
||||
_originalPhotoLabel.backgroundColor = [UIColor clearColor];
|
||||
if (_isSelectOriginalPhoto) [self showPhotoBytes];
|
||||
}
|
||||
|
||||
_doneButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
_doneButton.titleLabel.font = [UIFont systemFontOfSize:16];
|
||||
[_doneButton addTarget:self action:@selector(doneButtonClick) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_doneButton setTitle:_tzImagePickerVc.doneBtnTitleStr forState:UIControlStateNormal];
|
||||
[_doneButton setTitleColor:_tzImagePickerVc.oKButtonTitleColorNormal forState:UIControlStateNormal];
|
||||
|
||||
_numberImageView = [[UIImageView alloc] initWithImage:_tzImagePickerVc.photoNumberIconImage];
|
||||
_numberImageView.backgroundColor = [UIColor clearColor];
|
||||
_numberImageView.clipsToBounds = YES;
|
||||
_numberImageView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
_numberImageView.hidden = _tzImagePickerVc.selectedModels.count <= 0;
|
||||
|
||||
_numberLabel = [[UILabel alloc] init];
|
||||
_numberLabel.font = [UIFont systemFontOfSize:15];
|
||||
_numberLabel.adjustsFontSizeToFitWidth = YES;
|
||||
_numberLabel.textColor = [UIColor whiteColor];
|
||||
_numberLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_numberLabel.text = [NSString stringWithFormat:@"%zd",_tzImagePickerVc.selectedModels.count];
|
||||
_numberLabel.hidden = _tzImagePickerVc.selectedModels.count <= 0;
|
||||
_numberLabel.backgroundColor = [UIColor clearColor];
|
||||
_numberLabel.userInteractionEnabled = YES;
|
||||
|
||||
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doneButtonClick)];
|
||||
[_numberLabel addGestureRecognizer:tapGesture];
|
||||
|
||||
[_originalPhotoButton addSubview:_originalPhotoLabel];
|
||||
[_toolBar addSubview:_doneButton];
|
||||
[_toolBar addSubview:_originalPhotoButton];
|
||||
[_toolBar addSubview:_numberImageView];
|
||||
[_toolBar addSubview:_numberLabel];
|
||||
[self.view addSubview:_toolBar];
|
||||
|
||||
if (_tzImagePickerVc.photoPreviewPageUIConfigBlock) {
|
||||
_tzImagePickerVc.photoPreviewPageUIConfigBlock(_collectionView, _naviBar, _backButton, _selectButton, _indexLabel, _toolBar, _originalPhotoButton, _originalPhotoLabel, _doneButton, _numberImageView, _numberLabel);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)configCollectionView {
|
||||
_layout = [[UICollectionViewFlowLayout alloc] init];
|
||||
_layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
|
||||
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:_layout];
|
||||
_collectionView.backgroundColor = [UIColor blackColor];
|
||||
_collectionView.dataSource = self;
|
||||
_collectionView.delegate = self;
|
||||
_collectionView.pagingEnabled = YES;
|
||||
_collectionView.scrollsToTop = NO;
|
||||
_collectionView.showsHorizontalScrollIndicator = NO;
|
||||
_collectionView.contentOffset = CGPointMake(0, 0);
|
||||
_collectionView.contentSize = CGSizeMake(self.models.count * (self.view.tz_width + 20), 0);
|
||||
if (@available(iOS 11, *)) {
|
||||
_collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
||||
}
|
||||
[self.view addSubview:_collectionView];
|
||||
[_collectionView registerClass:[TZPhotoPreviewCell class] forCellWithReuseIdentifier:@"TZPhotoPreviewCell"];
|
||||
[_collectionView registerClass:[TZPhotoPreviewCell class] forCellWithReuseIdentifier:@"TZPhotoPreviewCellGIF"];
|
||||
[_collectionView registerClass:[TZVideoPreviewCell class] forCellWithReuseIdentifier:@"TZVideoPreviewCell"];
|
||||
[_collectionView registerClass:[TZGifPreviewCell class] forCellWithReuseIdentifier:@"TZGifPreviewCell"];
|
||||
|
||||
TZImagePickerController *_tzImagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
if (_tzImagePickerVc.scaleAspectFillCrop && _tzImagePickerVc.allowCrop) {
|
||||
_collectionView.scrollEnabled = NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)configCropView {
|
||||
TZImagePickerController *_tzImagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
if (_tzImagePickerVc.maxImagesCount <= 1 && _tzImagePickerVc.allowCrop && _tzImagePickerVc.allowPickingImage) {
|
||||
[_cropView removeFromSuperview];
|
||||
[_cropBgView removeFromSuperview];
|
||||
|
||||
_cropBgView = [UIView new];
|
||||
_cropBgView.userInteractionEnabled = NO;
|
||||
_cropBgView.frame = self.view.bounds;
|
||||
_cropBgView.backgroundColor = [UIColor clearColor];
|
||||
[self.view addSubview:_cropBgView];
|
||||
[TZImageCropManager overlayClippingWithView:_cropBgView cropRect:_tzImagePickerVc.cropRect containerView:self.view needCircleCrop:_tzImagePickerVc.needCircleCrop];
|
||||
|
||||
_cropView = [UIView new];
|
||||
_cropView.userInteractionEnabled = NO;
|
||||
_cropView.frame = _tzImagePickerVc.cropRect;
|
||||
_cropView.backgroundColor = [UIColor clearColor];
|
||||
_cropView.layer.borderColor = [UIColor whiteColor].CGColor;
|
||||
_cropView.layer.borderWidth = 1.0;
|
||||
if (_tzImagePickerVc.needCircleCrop) {
|
||||
_cropView.layer.cornerRadius = _tzImagePickerVc.cropRect.size.width / 2;
|
||||
_cropView.clipsToBounds = YES;
|
||||
}
|
||||
[self.view addSubview:_cropView];
|
||||
if (_tzImagePickerVc.cropViewSettingBlock) {
|
||||
_tzImagePickerVc.cropViewSettingBlock(_cropView);
|
||||
}
|
||||
|
||||
[self.view bringSubviewToFront:_naviBar];
|
||||
[self.view bringSubviewToFront:_toolBar];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Layout
|
||||
|
||||
- (void)viewDidLayoutSubviews {
|
||||
[super viewDidLayoutSubviews];
|
||||
TZImagePickerController *_tzImagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
|
||||
BOOL isFullScreen = self.view.tz_height == [UIScreen mainScreen].bounds.size.height;
|
||||
CGFloat statusBarHeight = isFullScreen ? [TZCommonTools tz_statusBarHeight] : 0;
|
||||
CGFloat statusBarHeightInterval = isFullScreen ? (statusBarHeight - 20) : 0;
|
||||
CGFloat naviBarHeight = statusBarHeight + _tzImagePickerVc.navigationBar.tz_height;
|
||||
_naviBar.frame = CGRectMake(0, 0, self.view.tz_width, naviBarHeight);
|
||||
_backButton.frame = CGRectMake(10, 10 + statusBarHeightInterval, 44, 44);
|
||||
_selectButton.frame = CGRectMake(self.view.tz_width - 56, 10 + statusBarHeightInterval, 44, 44);
|
||||
_indexLabel.frame = _selectButton.frame;
|
||||
|
||||
_layout.itemSize = CGSizeMake(self.view.tz_width + 20, self.view.tz_height);
|
||||
_layout.minimumInteritemSpacing = 0;
|
||||
_layout.minimumLineSpacing = 0;
|
||||
_collectionView.frame = CGRectMake(-10, 0, self.view.tz_width + 20, self.view.tz_height);
|
||||
[_collectionView setCollectionViewLayout:_layout];
|
||||
if (_offsetItemCount > 0) {
|
||||
CGFloat offsetX = _offsetItemCount * _layout.itemSize.width;
|
||||
[_collectionView setContentOffset:CGPointMake(offsetX, 0)];
|
||||
}
|
||||
if (_tzImagePickerVc.allowCrop) {
|
||||
[_collectionView reloadData];
|
||||
}
|
||||
|
||||
CGFloat toolBarHeight = 44 + [TZCommonTools tz_safeAreaInsets].bottom;
|
||||
CGFloat toolBarTop = self.view.tz_height - toolBarHeight;
|
||||
_toolBar.frame = CGRectMake(0, toolBarTop, self.view.tz_width, toolBarHeight);
|
||||
if (_tzImagePickerVc.allowPickingOriginalPhoto) {
|
||||
CGFloat fullImageWidth = [_tzImagePickerVc.fullImageBtnTitleStr boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:13]} context:nil].size.width;
|
||||
_originalPhotoButton.frame = CGRectMake(0, 0, fullImageWidth + 56, 44);
|
||||
_originalPhotoLabel.frame = CGRectMake(fullImageWidth + 42, 0, 80, 44);
|
||||
}
|
||||
[_doneButton sizeToFit];
|
||||
_doneButton.frame = CGRectMake(self.view.tz_width - _doneButton.tz_width - 12, 0, MAX(44, _doneButton.tz_width), 44);
|
||||
_numberImageView.frame = CGRectMake(_doneButton.tz_left - 24 - 5, 10, 24, 24);
|
||||
_numberLabel.frame = _numberImageView.frame;
|
||||
|
||||
[self configCropView];
|
||||
|
||||
if (_tzImagePickerVc.photoPreviewPageDidLayoutSubviewsBlock) {
|
||||
_tzImagePickerVc.photoPreviewPageDidLayoutSubviewsBlock(_collectionView, _naviBar, _backButton, _selectButton, _indexLabel, _toolBar, _originalPhotoButton, _originalPhotoLabel, _doneButton, _numberImageView, _numberLabel);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Notification
|
||||
|
||||
- (void)didChangeStatusBarOrientationNotification:(NSNotification *)noti {
|
||||
_offsetItemCount = _collectionView.contentOffset.x / _layout.itemSize.width;
|
||||
}
|
||||
|
||||
#pragma mark - Click Event
|
||||
|
||||
- (void)select:(UIButton *)selectButton {
|
||||
[self select:selectButton refreshCount:YES];
|
||||
}
|
||||
|
||||
- (void)select:(UIButton *)selectButton refreshCount:(BOOL)refreshCount {
|
||||
TZImagePickerController *_tzImagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
TZAssetModel *model = _models[self.currentIndex];
|
||||
if (!selectButton.isSelected) {
|
||||
// 1. select:check if over the maxImagesCount / 选择照片,检查是否超过了最大个数的限制
|
||||
if (_tzImagePickerVc.selectedModels.count >= _tzImagePickerVc.maxImagesCount) {
|
||||
NSString *title = [NSString stringWithFormat:[NSBundle tz_localizedStringForKey:@"Select a maximum of %zd photos"], _tzImagePickerVc.maxImagesCount];
|
||||
[_tzImagePickerVc showAlertWithTitle:title];
|
||||
return;
|
||||
// 2. if not over the maxImagesCount / 如果没有超过最大个数限制
|
||||
} else {
|
||||
if ([[TZImageManager manager] isAssetCannotBeSelected:model.asset]) {
|
||||
return;
|
||||
}
|
||||
[_tzImagePickerVc addSelectedModel:model];
|
||||
[self setAsset:model.asset isSelect:YES];
|
||||
if (self.photos) {
|
||||
[_tzImagePickerVc.selectedAssets addObject:_assetsTemp[self.currentIndex]];
|
||||
[self.photos addObject:_photosTemp[self.currentIndex]];
|
||||
}
|
||||
if (model.type == TZAssetModelMediaTypeVideo && !_tzImagePickerVc.allowPickingMultipleVideo) {
|
||||
[_tzImagePickerVc showAlertWithTitle:[NSBundle tz_localizedStringForKey:@"Select the video when in multi state, we will handle the video as a photo"]];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NSArray *selectedModels = [NSArray arrayWithArray:_tzImagePickerVc.selectedModels];
|
||||
for (TZAssetModel *model_item in selectedModels) {
|
||||
if ([model.asset.localIdentifier isEqualToString:model_item.asset.localIdentifier]) {
|
||||
// 1.6.7版本更新:防止有多个一样的model,一次性被移除了
|
||||
NSArray *selectedModelsTmp = [NSArray arrayWithArray:_tzImagePickerVc.selectedModels];
|
||||
for (NSInteger i = 0; i < selectedModelsTmp.count; i++) {
|
||||
TZAssetModel *model = selectedModelsTmp[i];
|
||||
if ([model isEqual:model_item]) {
|
||||
[_tzImagePickerVc removeSelectedModel:model];
|
||||
// [_tzImagePickerVc.selectedModels removeObjectAtIndex:i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (self.photos) {
|
||||
// 1.6.7版本更新:防止有多个一样的asset,一次性被移除了
|
||||
NSArray *selectedAssetsTmp = [NSArray arrayWithArray:_tzImagePickerVc.selectedAssets];
|
||||
for (NSInteger i = 0; i < selectedAssetsTmp.count; i++) {
|
||||
id asset = selectedAssetsTmp[i];
|
||||
if ([asset isEqual:_assetsTemp[self.currentIndex]]) {
|
||||
[_tzImagePickerVc.selectedAssets removeObjectAtIndex:i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
// [_tzImagePickerVc.selectedAssets removeObject:_assetsTemp[self.currentIndex]];
|
||||
[self.photos removeObject:_photosTemp[self.currentIndex]];
|
||||
}
|
||||
[self setAsset:model.asset isSelect:NO];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
model.isSelected = !selectButton.isSelected;
|
||||
if (refreshCount) {
|
||||
[self refreshNaviBarAndBottomBarState];
|
||||
}
|
||||
if (model.isSelected) {
|
||||
[UIView showOscillatoryAnimationWithLayer:selectButton.imageView.layer type:TZOscillatoryAnimationToBigger];
|
||||
}
|
||||
[UIView showOscillatoryAnimationWithLayer:_numberImageView.layer type:TZOscillatoryAnimationToSmaller];
|
||||
}
|
||||
|
||||
- (void)backButtonClick {
|
||||
if (self.navigationController.childViewControllers.count < 2) {
|
||||
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
|
||||
if ([self.navigationController isKindOfClass: [TZImagePickerController class]]) {
|
||||
TZImagePickerController *nav = (TZImagePickerController *)self.navigationController;
|
||||
if (nav.imagePickerControllerDidCancelHandle) {
|
||||
nav.imagePickerControllerDidCancelHandle();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
if (self.backButtonClickBlock) {
|
||||
self.backButtonClickBlock(_isSelectOriginalPhoto);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)doneButtonClick {
|
||||
TZImagePickerController *_tzImagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
// 如果图片正在从iCloud同步中,提醒用户
|
||||
if (_progress > 0 && _progress < 1 && (_selectButton.isSelected || !_tzImagePickerVc.selectedModels.count )) {
|
||||
_alertView = [_tzImagePickerVc showAlertWithTitle:[NSBundle tz_localizedStringForKey:@"Synchronizing photos from iCloud"]];
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果没有选中过照片 点击确定时选中当前预览的照片
|
||||
if (_tzImagePickerVc.selectedModels.count == 0 && _tzImagePickerVc.minImagesCount <= 0 && _tzImagePickerVc.autoSelectCurrentWhenDone) {
|
||||
TZAssetModel *model = _models[self.currentIndex];
|
||||
if ([[TZImageManager manager] isAssetCannotBeSelected:model.asset]) {
|
||||
return;
|
||||
}
|
||||
[self select:_selectButton refreshCount:NO];
|
||||
}
|
||||
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:self.currentIndex inSection:0];
|
||||
TZPhotoPreviewCell *cell = (TZPhotoPreviewCell *)[_collectionView cellForItemAtIndexPath:indexPath];
|
||||
if (_tzImagePickerVc.allowCrop && [cell isKindOfClass:[TZPhotoPreviewCell class]]) { // 裁剪状态
|
||||
_doneButton.enabled = NO;
|
||||
[_tzImagePickerVc showProgressHUD];
|
||||
UIImage *cropedImage = [TZImageCropManager cropImageView:cell.previewView.imageView toRect:_tzImagePickerVc.cropRect zoomScale:cell.previewView.scrollView.zoomScale containerView:self.view];
|
||||
if (_tzImagePickerVc.needCircleCrop) {
|
||||
cropedImage = [TZImageCropManager circularClipImage:cropedImage];
|
||||
}
|
||||
_doneButton.enabled = YES;
|
||||
[_tzImagePickerVc hideProgressHUD];
|
||||
if (self.doneButtonClickBlockCropMode) {
|
||||
TZAssetModel *model = _models[self.currentIndex];
|
||||
self.doneButtonClickBlockCropMode(cropedImage,model.asset);
|
||||
}
|
||||
} else if (self.doneButtonClickBlock) { // 非裁剪状态
|
||||
self.doneButtonClickBlock(_isSelectOriginalPhoto);
|
||||
}
|
||||
if (self.doneButtonClickBlockWithPreviewType) {
|
||||
self.doneButtonClickBlockWithPreviewType(self.photos,_tzImagePickerVc.selectedAssets,self.isSelectOriginalPhoto);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)originalPhotoButtonClick {
|
||||
TZAssetModel *model = _models[self.currentIndex];
|
||||
if ([[TZImageManager manager] isAssetCannotBeSelected:model.asset]) {
|
||||
return;
|
||||
}
|
||||
_originalPhotoButton.selected = !_originalPhotoButton.isSelected;
|
||||
_isSelectOriginalPhoto = _originalPhotoButton.isSelected;
|
||||
_originalPhotoLabel.hidden = !_originalPhotoButton.isSelected;
|
||||
if (_isSelectOriginalPhoto) {
|
||||
[self showPhotoBytes];
|
||||
if (!_selectButton.isSelected) {
|
||||
// 如果当前已选择照片张数 < 最大可选张数 && 最大可选张数大于1,就选中该张图
|
||||
TZImagePickerController *_tzImagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
if (_tzImagePickerVc.selectedModels.count < _tzImagePickerVc.maxImagesCount && _tzImagePickerVc.showSelectBtn) {
|
||||
[self select:_selectButton];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didTapPreviewCell {
|
||||
self.isHideNaviBar = !self.isHideNaviBar;
|
||||
_naviBar.hidden = self.isHideNaviBar;
|
||||
_toolBar.hidden = self.isHideNaviBar;
|
||||
}
|
||||
|
||||
#pragma mark - UIScrollViewDelegate
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
||||
CGFloat offSetWidth = scrollView.contentOffset.x;
|
||||
offSetWidth = offSetWidth + ((self.view.tz_width + 20) * 0.5);
|
||||
|
||||
NSInteger currentIndex = offSetWidth / (self.view.tz_width + 20);
|
||||
if (currentIndex < _models.count && _currentIndex != currentIndex) {
|
||||
_currentIndex = currentIndex;
|
||||
[self refreshNaviBarAndBottomBarState];
|
||||
}
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"photoPreviewCollectionViewDidScroll" object:nil];
|
||||
}
|
||||
|
||||
#pragma mark - UICollectionViewDataSource && Delegate
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
||||
return _models.count;
|
||||
}
|
||||
|
||||
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
TZImagePickerController *_tzImagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
TZAssetModel *model = _models[indexPath.item];
|
||||
|
||||
TZAssetPreviewCell *cell;
|
||||
__weak typeof(self) weakSelf = self;
|
||||
if (_tzImagePickerVc.allowPickingMultipleVideo && model.type == TZAssetModelMediaTypeVideo) {
|
||||
cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"TZVideoPreviewCell" forIndexPath:indexPath];
|
||||
TZVideoPreviewCell *currentCell = (TZVideoPreviewCell *)cell;
|
||||
currentCell.iCloudSyncFailedHandle = ^(id asset, BOOL isSyncFailed) {
|
||||
model.iCloudFailed = isSyncFailed;
|
||||
[weakSelf didICloudSyncStatusChanged:model];
|
||||
};
|
||||
} else if (_tzImagePickerVc.allowPickingMultipleVideo && model.type == TZAssetModelMediaTypePhotoGif && _tzImagePickerVc.allowPickingGif) {
|
||||
cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"TZGifPreviewCell" forIndexPath:indexPath];
|
||||
TZGifPreviewCell *currentCell = (TZGifPreviewCell *)cell;
|
||||
currentCell.previewView.iCloudSyncFailedHandle = ^(id asset, BOOL isSyncFailed) {
|
||||
model.iCloudFailed = isSyncFailed;
|
||||
[weakSelf didICloudSyncStatusChanged:model];
|
||||
};
|
||||
} else {
|
||||
NSString *reuseId = model.type == TZAssetModelMediaTypePhotoGif ? @"TZPhotoPreviewCellGIF" : @"TZPhotoPreviewCell";
|
||||
cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseId forIndexPath:indexPath];
|
||||
TZPhotoPreviewCell *photoPreviewCell = (TZPhotoPreviewCell *)cell;
|
||||
photoPreviewCell.cropRect = _tzImagePickerVc.cropRect;
|
||||
photoPreviewCell.allowCrop = _tzImagePickerVc.allowCrop;
|
||||
photoPreviewCell.scaleAspectFillCrop = _tzImagePickerVc.scaleAspectFillCrop;
|
||||
__weak typeof(_collectionView) weakCollectionView = _collectionView;
|
||||
__weak typeof(photoPreviewCell) weakCell = photoPreviewCell;
|
||||
[photoPreviewCell setImageProgressUpdateBlock:^(double progress) {
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
__strong typeof(weakCollectionView) strongCollectionView = weakCollectionView;
|
||||
__strong typeof(weakCell) strongCell = weakCell;
|
||||
strongSelf.progress = progress;
|
||||
if (progress >= 1) {
|
||||
if (strongSelf.isSelectOriginalPhoto) [strongSelf showPhotoBytes];
|
||||
if (strongSelf.alertView && [strongCollectionView.visibleCells containsObject:strongCell]) {
|
||||
[strongSelf.alertView dismissViewControllerAnimated:YES completion:^{
|
||||
strongSelf.alertView = nil;
|
||||
[strongSelf doneButtonClick];
|
||||
}];
|
||||
}
|
||||
}
|
||||
}];
|
||||
photoPreviewCell.previewView.iCloudSyncFailedHandle = ^(id asset, BOOL isSyncFailed) {
|
||||
model.iCloudFailed = isSyncFailed;
|
||||
[weakSelf didICloudSyncStatusChanged:model];
|
||||
};
|
||||
}
|
||||
|
||||
cell.model = model;
|
||||
[cell setSingleTapGestureBlock:^{
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
[strongSelf didTapPreviewCell];
|
||||
}];
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
if ([cell isKindOfClass:[TZPhotoPreviewCell class]]) {
|
||||
[(TZPhotoPreviewCell *)cell recoverSubviews];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
if ([cell isKindOfClass:[TZPhotoPreviewCell class]]) {
|
||||
[(TZPhotoPreviewCell *)cell recoverSubviews];
|
||||
} else if ([cell isKindOfClass:[TZVideoPreviewCell class]]) {
|
||||
TZVideoPreviewCell *videoCell = (TZVideoPreviewCell *)cell;
|
||||
if (videoCell.player && videoCell.player.rate != 0.0) {
|
||||
[videoCell pausePlayerAndShowNaviBar];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Private Method
|
||||
|
||||
- (void)dealloc {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
// NSLog(@"%@ dealloc",NSStringFromClass(self.class));
|
||||
}
|
||||
|
||||
- (void)refreshNaviBarAndBottomBarState {
|
||||
TZImagePickerController *_tzImagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
TZAssetModel *model = _models[self.currentIndex];
|
||||
_selectButton.selected = model.isSelected;
|
||||
[self refreshSelectButtonImageViewContentMode];
|
||||
if (_selectButton.isSelected && _tzImagePickerVc.showSelectedIndex && _tzImagePickerVc.showSelectBtn) {
|
||||
NSString *index = [NSString stringWithFormat:@"%d", (int)([_tzImagePickerVc.selectedAssetIds indexOfObject:model.asset.localIdentifier] + 1)];
|
||||
_indexLabel.text = index;
|
||||
_indexLabel.hidden = NO;
|
||||
} else {
|
||||
_indexLabel.hidden = YES;
|
||||
}
|
||||
_numberLabel.text = [NSString stringWithFormat:@"%zd",_tzImagePickerVc.selectedModels.count];
|
||||
_numberImageView.hidden = (_tzImagePickerVc.selectedModels.count <= 0 || _isHideNaviBar || _isCropImage);
|
||||
_numberLabel.hidden = (_tzImagePickerVc.selectedModels.count <= 0 || _isHideNaviBar || _isCropImage);
|
||||
|
||||
_originalPhotoButton.selected = _isSelectOriginalPhoto;
|
||||
_originalPhotoLabel.hidden = !_originalPhotoButton.isSelected;
|
||||
if (_isSelectOriginalPhoto) [self showPhotoBytes];
|
||||
|
||||
// If is previewing video, hide original photo button
|
||||
// 如果正在预览的是视频,隐藏原图按钮
|
||||
if (!_isHideNaviBar) {
|
||||
if (model.type == TZAssetModelMediaTypeVideo) {
|
||||
_originalPhotoButton.hidden = YES;
|
||||
_originalPhotoLabel.hidden = YES;
|
||||
} else {
|
||||
_originalPhotoButton.hidden = NO;
|
||||
if (_isSelectOriginalPhoto) _originalPhotoLabel.hidden = NO;
|
||||
}
|
||||
}
|
||||
|
||||
_doneButton.hidden = NO;
|
||||
_selectButton.hidden = !_tzImagePickerVc.showSelectBtn;
|
||||
// 让宽度/高度小于 最小可选照片尺寸 的图片不能选中
|
||||
if (![[TZImageManager manager] isPhotoSelectableWithAsset:model.asset]) {
|
||||
_numberLabel.hidden = YES;
|
||||
_numberImageView.hidden = YES;
|
||||
_selectButton.hidden = YES;
|
||||
_originalPhotoButton.hidden = YES;
|
||||
_originalPhotoLabel.hidden = YES;
|
||||
_doneButton.hidden = YES;
|
||||
}
|
||||
// iCloud同步失败的UI刷新
|
||||
[self didICloudSyncStatusChanged:model];
|
||||
if (_tzImagePickerVc.photoPreviewPageDidRefreshStateBlock) {
|
||||
_tzImagePickerVc.photoPreviewPageDidRefreshStateBlock(_collectionView, _naviBar, _backButton, _selectButton, _indexLabel, _toolBar, _originalPhotoButton, _originalPhotoLabel, _doneButton, _numberImageView, _numberLabel);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)refreshSelectButtonImageViewContentMode {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
if (self->_selectButton.imageView.image.size.width <= 27) {
|
||||
self->_selectButton.imageView.contentMode = UIViewContentModeCenter;
|
||||
} else {
|
||||
self->_selectButton.imageView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)didICloudSyncStatusChanged:(TZAssetModel *)model{
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
TZImagePickerController *_tzImagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
// onlyReturnAsset为NO时,依赖TZ返回大图,所以需要有iCloud同步失败的提示,并且不能选择,
|
||||
if (_tzImagePickerVc.onlyReturnAsset) {
|
||||
return;
|
||||
}
|
||||
TZAssetModel *currentModel = self.models[self.currentIndex];
|
||||
if (_tzImagePickerVc.selectedModels.count <= 0) {
|
||||
self->_doneButton.enabled = !currentModel.iCloudFailed;
|
||||
} else {
|
||||
self->_doneButton.enabled = YES;
|
||||
}
|
||||
self->_selectButton.hidden = currentModel.iCloudFailed || !_tzImagePickerVc.showSelectBtn;
|
||||
if (currentModel.iCloudFailed) {
|
||||
self->_originalPhotoButton.hidden = YES;
|
||||
self->_originalPhotoLabel.hidden = YES;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)showPhotoBytes {
|
||||
[[TZImageManager manager] getPhotosBytesWithArray:@[_models[self.currentIndex]] completion:^(NSString *totalBytes) {
|
||||
self->_originalPhotoLabel.text = [NSString stringWithFormat:@"(%@)",totalBytes];
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSInteger)currentIndex {
|
||||
return [TZCommonTools tz_isRightToLeftLayout] ? self.models.count - _currentIndex - 1 : _currentIndex;
|
||||
}
|
||||
|
||||
/// 选中/取消选中某张照片
|
||||
- (void)setAsset:(PHAsset *)asset isSelect:(BOOL)isSelect {
|
||||
TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
if (isSelect && [tzImagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didSelectAsset:photo:isSelectOriginalPhoto:)]) {
|
||||
[self callDelegate:asset isSelect:YES];
|
||||
}
|
||||
if (!isSelect && [tzImagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didDeselectAsset:photo:isSelectOriginalPhoto:)]) {
|
||||
[self callDelegate:asset isSelect:NO];
|
||||
}
|
||||
}
|
||||
|
||||
/// 调用选中/取消选中某张照片的代理方法
|
||||
- (void)callDelegate:(PHAsset *)asset isSelect:(BOOL)isSelect {
|
||||
TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
__weak typeof(self) weakSelf = self;
|
||||
__weak typeof(tzImagePickerVc) weakImagePickerVc= tzImagePickerVc;
|
||||
[[TZImageManager manager] getPhotoWithAsset:asset completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) {
|
||||
if (isDegraded) return;
|
||||
__strong typeof(weakSelf) strongSelf = weakSelf;
|
||||
__strong typeof(weakImagePickerVc) strongImagePickerVc = weakImagePickerVc;
|
||||
if (isSelect) {
|
||||
[strongImagePickerVc.pickerDelegate imagePickerController:strongImagePickerVc didSelectAsset:asset photo:photo isSelectOriginalPhoto:strongSelf.isSelectOriginalPhoto];
|
||||
} else {
|
||||
[strongImagePickerVc.pickerDelegate imagePickerController:strongImagePickerVc didDeselectAsset:asset photo:photo isSelectOriginalPhoto:strongSelf.isSelectOriginalPhoto];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
15
Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZProgressView.h
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// TZProgressView.h
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by ttouch on 2016/12/6.
|
||||
// Copyright © 2016年 谭真. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface TZProgressView : UIView
|
||||
|
||||
@property (nonatomic, assign) double progress;
|
||||
|
||||
@end
|
||||
55
Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZProgressView.m
generated
Normal file
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// TZProgressView.m
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by ttouch on 2016/12/6.
|
||||
// Copyright © 2016年 谭真. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TZProgressView.h"
|
||||
|
||||
@interface TZProgressView ()
|
||||
@property (nonatomic, strong) CAShapeLayer *progressLayer;
|
||||
@end
|
||||
|
||||
@implementation TZProgressView
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
|
||||
_progressLayer = [CAShapeLayer layer];
|
||||
_progressLayer.fillColor = [[UIColor clearColor] CGColor];
|
||||
_progressLayer.strokeColor = [[UIColor whiteColor] CGColor];
|
||||
_progressLayer.opacity = 1;
|
||||
_progressLayer.lineCap = kCALineCapRound;
|
||||
_progressLayer.lineWidth = 5;
|
||||
|
||||
[_progressLayer setShadowColor:[UIColor blackColor].CGColor];
|
||||
[_progressLayer setShadowOffset:CGSizeMake(1, 1)];
|
||||
[_progressLayer setShadowOpacity:0.5];
|
||||
[_progressLayer setShadowRadius:2];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect {
|
||||
CGPoint center = CGPointMake(rect.size.width / 2, rect.size.height / 2);
|
||||
CGFloat radius = rect.size.width / 2;
|
||||
CGFloat startA = - M_PI_2;
|
||||
CGFloat endA = - M_PI_2 + M_PI * 2 * _progress;
|
||||
_progressLayer.frame = self.bounds;
|
||||
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES];
|
||||
_progressLayer.path =[path CGPath];
|
||||
|
||||
[_progressLayer removeFromSuperlayer];
|
||||
[self.layer addSublayer:_progressLayer];
|
||||
}
|
||||
|
||||
- (void)setProgress:(double)progress {
|
||||
_progress = progress;
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
@end
|
||||
47
Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZVideoCropController.h
generated
Normal file
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// TZVideoCropController.h
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by 肖兰月 on 2021/5/27.
|
||||
// Copyright © 2021 谭真. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class TZAssetModel,TZImagePickerController;
|
||||
|
||||
@interface TZVideoCropController : UIViewController<UIViewControllerTransitioningDelegate>
|
||||
@property (nonatomic, strong) TZAssetModel *model;
|
||||
@property (nonatomic, weak) TZImagePickerController *imagePickerVc;
|
||||
@end
|
||||
|
||||
@protocol TZVideoEditViewDelegate <NSObject>
|
||||
- (void)editViewCropRectBeginChange;
|
||||
- (void)editViewCropRectEndChange;
|
||||
@end
|
||||
|
||||
@interface TZVideoEditView : UIView
|
||||
@property (strong, nonatomic) UIImageView *beginImgView;
|
||||
@property (strong, nonatomic) UIImageView *endImgView;
|
||||
@property (strong, nonatomic) UIView *indicatorLine;
|
||||
@property (assign, nonatomic) CGFloat videoDuration;
|
||||
@property (assign, nonatomic) NSInteger maxCropVideoDuration;
|
||||
@property (assign, nonatomic) CGRect cropRect;
|
||||
@property (assign, nonatomic) CGFloat allImgWidth;
|
||||
@property (assign, nonatomic) CGFloat minCropRectWidth;
|
||||
|
||||
@property (nonatomic, weak) id<TZVideoEditViewDelegate> delegate;
|
||||
|
||||
- (void)resetIndicatorLine;
|
||||
- (void)indicatorLineAnimateWithDuration:(NSTimeInterval)duration cropRect:(CGRect)cropRect;
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@interface TZVideoPictureCell : UICollectionViewCell
|
||||
@property (strong, nonatomic) UIImageView *imgView;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
673
Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZVideoCropController.m
generated
Normal file
@@ -0,0 +1,673 @@
|
||||
//
|
||||
// TZVideoCropController.m
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by 肖兰月 on 2021/5/27.
|
||||
// Copyright © 2021 谭真. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TZVideoCropController.h"
|
||||
#import <MediaPlayer/MediaPlayer.h>
|
||||
#import "UIView+TZLayout.h"
|
||||
#import "TZImageManager.h"
|
||||
#import "TZAssetModel.h"
|
||||
#import "TZImagePickerController.h"
|
||||
|
||||
@interface TZVideoCropController ()<TZVideoEditViewDelegate,UICollectionViewDelegate, UICollectionViewDataSource> {
|
||||
AVPlayer *_player;
|
||||
AVPlayerLayer *_playerLayer;
|
||||
UIButton *_playButton;
|
||||
UIImage *_cover;
|
||||
NSString *_outputPath;
|
||||
NSString *_errorMsg;
|
||||
|
||||
UIButton *_cancelButton;
|
||||
UIButton *_doneButton;
|
||||
UIProgressView *_progress;
|
||||
UILabel *_cropVideoDurationLabel;
|
||||
|
||||
AVAssetImageGenerator *_imageGenerator;
|
||||
AVAsset *_asset;
|
||||
|
||||
CGFloat _collectionViewBeginOffsetX;
|
||||
BOOL _isPlayed;
|
||||
CGFloat _itemW;
|
||||
BOOL _isDraging;
|
||||
|
||||
UIStatusBarStyle _originStatusBarStyle;
|
||||
}
|
||||
|
||||
// iCloud无法同步提示UI
|
||||
@property (nonatomic, strong) UIView *iCloudErrorView;
|
||||
@property (strong, nonatomic) UICollectionView *collectionView;
|
||||
@property (strong, nonatomic) TZVideoEditView *videoEditView;
|
||||
@property (strong, nonatomic) NSMutableArray *videoImgArray;
|
||||
@property (strong, nonatomic) NSArray *imageTimes;
|
||||
@property (strong, nonatomic) NSTimer *timer;
|
||||
|
||||
@end
|
||||
|
||||
@implementation TZVideoCropController
|
||||
|
||||
#define VideoEditLeftMargin 40
|
||||
#define PanImageWidth 10
|
||||
#define MinCropVideoDuration 1
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
self.view.backgroundColor = [UIColor blackColor];
|
||||
[self configMoviePlayer];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pausePlayer) name:UIApplicationWillResignActiveNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
[self stopTimer];
|
||||
}
|
||||
|
||||
- (void)configMoviePlayer {
|
||||
[[TZImageManager manager] getPhotoWithAsset:_model.asset completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) {
|
||||
BOOL iCloudSyncFailed = !photo && [TZCommonTools isICloudSyncError:info[PHImageErrorKey]];
|
||||
self.iCloudErrorView.hidden = !iCloudSyncFailed;
|
||||
self->_doneButton.enabled = !iCloudSyncFailed;
|
||||
}];
|
||||
[[TZImageManager manager] getVideoWithAsset:_model.asset completion:^(AVPlayerItem *playerItem, NSDictionary *info) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self->_asset = playerItem.asset;
|
||||
self->_player = [AVPlayer playerWithPlayerItem:playerItem];
|
||||
self->_playerLayer = [AVPlayerLayer playerLayerWithPlayer:self->_player];
|
||||
self->_playerLayer.frame = self.view.bounds;
|
||||
[self.view.layer addSublayer:self->_playerLayer];
|
||||
[self configPlayButton];
|
||||
[self configBottomToolBar];
|
||||
if (self.imagePickerVc.allowEditVideo) {
|
||||
[self configVideoImageCollectionView];
|
||||
[self configVideoEditView];
|
||||
[self generateVideoImage];
|
||||
}
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pausePlayer) name:AVPlayerItemDidPlayToEndTimeNotification object:self->_player.currentItem];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)configPlayButton {
|
||||
_playButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlay"] forState:UIControlStateNormal];
|
||||
[_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlayHL"] forState:UIControlStateHighlighted];
|
||||
[_playButton addTarget:self action:@selector(playButtonClick) forControlEvents:UIControlEventTouchUpInside];
|
||||
[self.view addSubview:_playButton];
|
||||
}
|
||||
|
||||
- (void)configBottomToolBar {
|
||||
_cropVideoDurationLabel = UILabel.new;
|
||||
_cropVideoDurationLabel.textAlignment = NSTextAlignmentCenter;
|
||||
_cropVideoDurationLabel.textColor = UIColor.whiteColor;
|
||||
_cropVideoDurationLabel.font = [UIFont systemFontOfSize:12];
|
||||
[self.view addSubview:_cropVideoDurationLabel];
|
||||
|
||||
_cancelButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
_cancelButton.titleLabel.font = [UIFont systemFontOfSize:16];
|
||||
[_cancelButton setTitle:[NSBundle tz_localizedStringForKey:@"Cancel"] forState:0];
|
||||
[_cancelButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
|
||||
[_cancelButton addTarget:self action:@selector(cancelButtonClick) forControlEvents:UIControlEventTouchUpInside];
|
||||
[self.view addSubview:_cancelButton];
|
||||
|
||||
_doneButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
_doneButton.titleLabel.font = [UIFont systemFontOfSize:16];
|
||||
[_doneButton addTarget:self action:@selector(doneButtonClick) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_doneButton setTitle:self.imagePickerVc.doneBtnTitleStr forState:UIControlStateNormal];
|
||||
[_doneButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
|
||||
[_doneButton setTitleColor:self.imagePickerVc.oKButtonTitleColorDisabled forState:UIControlStateDisabled];
|
||||
[self.view addSubview:_doneButton];
|
||||
|
||||
if (self.imagePickerVc.videoEditViewPageUIConfigBlock) {
|
||||
self.imagePickerVc.videoEditViewPageUIConfigBlock(_playButton, _cropVideoDurationLabel, _cancelButton, _doneButton);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)configVideoImageCollectionView {
|
||||
_itemW = (self.view.tz_width - VideoEditLeftMargin * 2 - 2 * PanImageWidth) / 10.0;
|
||||
UICollectionViewFlowLayout *layout = UICollectionViewFlowLayout.new;
|
||||
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
|
||||
layout.itemSize = CGSizeMake(_itemW, _itemW * 2);
|
||||
layout.minimumLineSpacing = 0;
|
||||
layout.minimumInteritemSpacing = 0;
|
||||
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
|
||||
_collectionView.dataSource = self;
|
||||
_collectionView.delegate = self;
|
||||
_collectionView.contentInset = UIEdgeInsetsMake(0, VideoEditLeftMargin + PanImageWidth, 0, VideoEditLeftMargin + PanImageWidth);
|
||||
_collectionView.clipsToBounds = NO;
|
||||
_collectionView.showsHorizontalScrollIndicator = NO;
|
||||
_collectionView.alwaysBounceHorizontal = YES;
|
||||
[_collectionView registerClass:TZVideoPictureCell.class forCellWithReuseIdentifier:@"TZVideoPictureCell"];
|
||||
[self.view addSubview:_collectionView];
|
||||
}
|
||||
|
||||
- (void)configVideoEditView {
|
||||
_videoEditView = TZVideoEditView.new;
|
||||
_videoEditView.backgroundColor = UIColor.clearColor;
|
||||
_videoEditView.delegate = self;
|
||||
_videoEditView.maxCropVideoDuration = self.imagePickerVc.maxCropVideoDuration;
|
||||
[self.view addSubview:_videoEditView];
|
||||
}
|
||||
|
||||
- (UIStatusBarStyle)preferredStatusBarStyle {
|
||||
if (self.imagePickerVc && [self.imagePickerVc isKindOfClass:[TZImagePickerController class]]) {
|
||||
return self.imagePickerVc.statusBarStyle;
|
||||
}
|
||||
return [super preferredStatusBarStyle];
|
||||
}
|
||||
|
||||
#pragma mark - Layout
|
||||
|
||||
- (void)viewDidLayoutSubviews {
|
||||
[super viewDidLayoutSubviews];
|
||||
|
||||
BOOL isFullScreen = self.view.tz_height == [UIScreen mainScreen].bounds.size.height;
|
||||
CGFloat statusBarHeight = isFullScreen ? [TZCommonTools tz_statusBarHeight] : 0;
|
||||
CGFloat statusBarAndNaviBarHeight = statusBarHeight + self.navigationController.navigationBar.tz_height;
|
||||
|
||||
CGFloat toolBarHeight = 44 + [TZCommonTools tz_safeAreaInsets].bottom;
|
||||
CGFloat doneButtonWidth = [_doneButton.currentTitle boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:16]} context:nil].size.width;
|
||||
doneButtonWidth = MAX(44, doneButtonWidth);
|
||||
_cancelButton.frame = CGRectMake(12, self.view.tz_height - toolBarHeight, 44, 44);
|
||||
[_cancelButton sizeToFit];
|
||||
_cancelButton.tz_height = 44;
|
||||
_doneButton.frame = CGRectMake(self.view.tz_width - doneButtonWidth - 12, self.view.tz_height - toolBarHeight, doneButtonWidth, 44);
|
||||
_playButton.frame = CGRectMake(0, statusBarAndNaviBarHeight, self.view.tz_width, self.view.tz_height - statusBarAndNaviBarHeight - toolBarHeight);
|
||||
|
||||
CGFloat collectionViewH = (self.view.tz_width - VideoEditLeftMargin * 2 - 2 * PanImageWidth) / 10.0 * 2;
|
||||
_collectionView.frame = CGRectMake(0, self.view.tz_height - collectionViewH - toolBarHeight - statusBarHeight, self.view.tz_width, collectionViewH);
|
||||
_videoEditView.frame = _collectionView.frame;
|
||||
_cropVideoDurationLabel.frame = CGRectMake(0, _videoEditView.tz_bottom, self.view.tz_width, 20);
|
||||
|
||||
CGFloat playerLayerHeight = CGRectGetMinY(_collectionView.frame) - statusBarHeight * 2;
|
||||
CGFloat playerLayerWidth = self.view.tz_width/self.view.tz_height * playerLayerHeight;
|
||||
CGFloat playerLayerLeft = (self.view.tz_width - playerLayerWidth) / 2.0;
|
||||
CGRect playerLayerFrame = CGRectMake(playerLayerLeft, statusBarHeight, playerLayerWidth, playerLayerHeight);
|
||||
_playerLayer.frame = playerLayerFrame;
|
||||
_playButton.frame = CGRectMake(0, statusBarAndNaviBarHeight, self.view.tz_width, playerLayerHeight - statusBarAndNaviBarHeight);
|
||||
|
||||
if (self.imagePickerVc.videoEditViewPageDidLayoutSubviewsBlock) {
|
||||
self.imagePickerVc.videoEditViewPageDidLayoutSubviewsBlock(_playButton, _cropVideoDurationLabel, _cancelButton, _doneButton);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)generateVideoImage {
|
||||
_imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:_asset];
|
||||
_imageGenerator.appliesPreferredTrackTransform = YES;
|
||||
_imageGenerator.requestedTimeToleranceBefore = kCMTimeZero;
|
||||
_imageGenerator.requestedTimeToleranceAfter = kCMTimeZero;
|
||||
_imageGenerator.maximumSize = CGSizeMake(100, 100);
|
||||
|
||||
NSTimeInterval durationSeconds = self.model.asset.duration;
|
||||
self.videoEditView.videoDuration = durationSeconds;
|
||||
|
||||
NSUInteger imageCount = 10;
|
||||
CGFloat maxCropWidth = self.view.tz_width - (VideoEditLeftMargin + PanImageWidth) * 2;
|
||||
if (durationSeconds <= MinCropVideoDuration) return;
|
||||
if (durationSeconds <= self.imagePickerVc.maxCropVideoDuration) {
|
||||
imageCount = 10;
|
||||
self.videoEditView.allImgWidth = maxCropWidth;
|
||||
_cropVideoDurationLabel.text = [NSString stringWithFormat:[NSBundle tz_localizedStringForKey:@"Selected for %ld seconds"], (NSInteger)durationSeconds];
|
||||
} else {
|
||||
CGFloat singleWidthSecond = maxCropWidth / self.imagePickerVc.maxCropVideoDuration;
|
||||
CGFloat allImgWidth = singleWidthSecond * durationSeconds;
|
||||
self.videoEditView.allImgWidth = allImgWidth;
|
||||
imageCount = allImgWidth / _itemW;
|
||||
_cropVideoDurationLabel.text = [NSString stringWithFormat:[NSBundle tz_localizedStringForKey:@"Selected for %ld seconds"],(long)self.imagePickerVc.maxCropVideoDuration];
|
||||
}
|
||||
NSArray *assetTracks = [_asset tracksWithMediaType:AVMediaTypeVideo];
|
||||
if (!assetTracks.count) {
|
||||
self.iCloudErrorView.hidden = NO;
|
||||
_doneButton.enabled = NO;
|
||||
_cropVideoDurationLabel.hidden = YES;
|
||||
return;
|
||||
};
|
||||
Float64 frameRate = [[_asset tracksWithMediaType:AVMediaTypeVideo][0] nominalFrameRate];;
|
||||
NSMutableArray *times = NSMutableArray.array;
|
||||
NSTimeInterval intervalSecond = durationSeconds/imageCount;
|
||||
CMTime timeFrame;
|
||||
for (NSInteger i = 0; i < imageCount; i++) {
|
||||
timeFrame = CMTimeMake(intervalSecond * i *frameRate, frameRate);
|
||||
NSValue *timeValue = [NSValue valueWithCMTime:timeFrame];
|
||||
[times addObject:timeValue];
|
||||
}
|
||||
self.videoImgArray = NSMutableArray.new;
|
||||
self.imageTimes = times;
|
||||
typeof(self) weakSelf = self;
|
||||
[_imageGenerator generateCGImagesAsynchronouslyForTimes:times completionHandler:^(CMTime requestedTime, CGImageRef _Nullable image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError * _Nullable error) {
|
||||
if (image) {
|
||||
UIImage *img = [[UIImage alloc] initWithCGImage:image];
|
||||
[weakSelf.videoImgArray addObject:img];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[weakSelf.collectionView reloadData];
|
||||
});
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - UICollectiobViewDataSource & UIcollectionViewDelegate
|
||||
|
||||
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
|
||||
return self.videoImgArray.count;
|
||||
}
|
||||
|
||||
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
|
||||
TZVideoPictureCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"TZVideoPictureCell" forIndexPath:indexPath];
|
||||
cell.imgView.image = self.videoImgArray[indexPath.item];
|
||||
return cell;
|
||||
}
|
||||
|
||||
#pragma mark - UIScrollViewDelegate
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
|
||||
if (!_isDraging) return;
|
||||
CGFloat offsetX = scrollView.contentOffset.x;
|
||||
if (offsetX - _collectionViewBeginOffsetX >= self.view.tz_width) {
|
||||
[self.collectionView setContentOffset:CGPointMake(self.view.tz_width + _collectionViewBeginOffsetX, 0) animated:NO];
|
||||
} else if (_collectionViewBeginOffsetX - offsetX >= self.view.tz_width) {
|
||||
[self.collectionView setContentOffset:CGPointMake(_collectionViewBeginOffsetX - self.view.tz_width, 0) animated:NO];
|
||||
}
|
||||
|
||||
[self editViewCropRectBeginChange];
|
||||
}
|
||||
|
||||
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
|
||||
_isDraging = YES;
|
||||
_collectionViewBeginOffsetX = scrollView.contentOffset.x;
|
||||
}
|
||||
|
||||
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
|
||||
_isDraging = NO;
|
||||
[self editViewCropRectEndChange];
|
||||
}
|
||||
|
||||
#pragma mark - TZVideoEditViewDelegate
|
||||
|
||||
- (void)editViewCropRectBeginChange {
|
||||
[self stopTimer];
|
||||
[_playerLayer.player seekToTime:[self getCropStartTime] toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
|
||||
|
||||
NSTimeInterval second = [self getCropVideoDuration];
|
||||
_cropVideoDurationLabel.text = [NSString stringWithFormat:[NSBundle tz_localizedStringForKey:@"Selected for %ld seconds"], (NSInteger)second];
|
||||
}
|
||||
|
||||
- (void)editViewCropRectEndChange {
|
||||
if (_isPlayed) {
|
||||
[self starTimer];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Click Event
|
||||
|
||||
- (void)playButtonClick {
|
||||
CMTime currentTime = _player.currentItem.currentTime;
|
||||
CMTime durationTime = _player.currentItem.duration;
|
||||
if (_player.rate == 0.0f) {
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"TZ_VIDEO_PLAY_NOTIFICATION" object:_player];
|
||||
if (currentTime.value == durationTime.value) [_player.currentItem seekToTime:CMTimeMake(0, 1)];
|
||||
_isPlayed = YES;
|
||||
[self starTimer];
|
||||
[_playButton setImage:nil forState:UIControlStateNormal];
|
||||
} else {
|
||||
_isPlayed = NO;
|
||||
[self stopTimer];
|
||||
[self pausePlayer];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)cancelButtonClick {
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
|
||||
- (void)doneButtonClick {
|
||||
if ([[TZImageManager manager] isAssetCannotBeSelected:_model.asset]) {
|
||||
return;
|
||||
}
|
||||
[self stopTimer];
|
||||
|
||||
TZImagePickerController *imagePickerVc = self.imagePickerVc;
|
||||
[imagePickerVc showProgressHUD];
|
||||
[[TZImageManager manager] getVideoOutputPathWithAsset:_model.asset presetName:imagePickerVc.presetName timeRange:[self getCropVideoTimeRange] success:^(NSString *outputPath) {
|
||||
[imagePickerVc hideProgressHUD];
|
||||
self->_outputPath = outputPath;
|
||||
[self dismissAndCallDelegateMethod];
|
||||
} failure:^(NSString *errorMessage, NSError *error) {
|
||||
[imagePickerVc hideProgressHUD];
|
||||
self->_errorMsg = errorMessage;
|
||||
[self dismissAndCallDelegateMethod];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)dismissAndCallDelegateMethod {
|
||||
[self dismissViewControllerAnimated:NO completion:^{
|
||||
[self callDelegateMethod];
|
||||
}];
|
||||
[self.imagePickerVc dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)callDelegateMethod {
|
||||
if (_outputPath) {
|
||||
NSURL *videoURL = [NSURL fileURLWithPath:_outputPath];
|
||||
if (self.imagePickerVc.saveEditedVideoToAlbum) {
|
||||
[[TZImageManager manager] saveVideoWithUrl:videoURL completion:^(PHAsset *asset, NSError *error) {
|
||||
if (error) { // 视频保存失败
|
||||
if ([self.imagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didFailToSaveEditedVideoWithError:)]) {
|
||||
[self.imagePickerVc.pickerDelegate imagePickerController:self.imagePickerVc didFailToSaveEditedVideoWithError:error];
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
UIImage *coverImage = [[TZImageManager manager] getImageWithVideoURL:videoURL];
|
||||
if ([self.imagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didFinishPickingAndEditingVideo:outputPath:error:)]) {
|
||||
[self.imagePickerVc.pickerDelegate imagePickerController:self.imagePickerVc didFinishPickingAndEditingVideo:coverImage outputPath:_outputPath error:nil];
|
||||
}
|
||||
if (self.imagePickerVc.didFinishPickingAndEditingVideoHandle) {
|
||||
self.imagePickerVc.didFinishPickingAndEditingVideoHandle(coverImage, _outputPath, nil);
|
||||
}
|
||||
} else {
|
||||
if ([self.imagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didFinishPickingAndEditingVideo:outputPath:error:)]) {
|
||||
[self.imagePickerVc.pickerDelegate imagePickerController:self.imagePickerVc didFinishPickingAndEditingVideo:nil outputPath:nil error:_errorMsg];
|
||||
}
|
||||
if (self.imagePickerVc.didFinishPickingAndEditingVideoHandle) {
|
||||
self.imagePickerVc.didFinishPickingAndEditingVideoHandle(nil, nil, _errorMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - private method
|
||||
|
||||
- (CMTime)getCropStartTime {
|
||||
NSTimeInterval second = [self getCropVideoStartSecond];
|
||||
if (second > self.model.asset.duration) {
|
||||
second = roundf(self.model.asset.duration);
|
||||
}
|
||||
return CMTimeMakeWithSeconds(second, _playerLayer.player.currentTime.timescale);
|
||||
}
|
||||
|
||||
- (CMTimeRange)getCropVideoTimeRange {
|
||||
NSTimeInterval startSecond = [self getCropVideoStartSecond];
|
||||
CMTime start = CMTimeMakeWithSeconds(startSecond, _playerLayer.player.currentTime.timescale);
|
||||
NSTimeInterval second = [self getCropVideoDuration];
|
||||
CMTime duration = CMTimeMakeWithSeconds(second, _playerLayer.player.currentTime.timescale);
|
||||
return CMTimeRangeMake(start, duration);
|
||||
}
|
||||
|
||||
- (NSTimeInterval)getCropVideoDuration {
|
||||
CGFloat rectW = self.videoEditView.cropRect.size.width;
|
||||
CGFloat contentW = self.videoEditView.allImgWidth;
|
||||
CGFloat second = rectW / contentW * roundf(self.model.asset.duration);
|
||||
return roundf(second);
|
||||
}
|
||||
|
||||
- (NSTimeInterval)getCropVideoStartSecond {
|
||||
CGFloat offsetX = self.collectionView.contentOffset.x;
|
||||
CGFloat contentW = self.videoEditView.allImgWidth;
|
||||
CGFloat cropRectX = self.videoEditView.cropRect.origin.x - VideoEditLeftMargin - PanImageWidth;
|
||||
NSTimeInterval second = (offsetX + cropRectX) / contentW * roundf(self.model.asset.duration);
|
||||
if (second < 0) second = 0;
|
||||
return roundf(second);
|
||||
}
|
||||
|
||||
- (CMTime)getTimeOfSeek {
|
||||
NSTimeInterval second = [self getCropVideoStartSecond];
|
||||
if (second > self.model.asset.duration) {
|
||||
second = roundf(self.model.asset.duration);
|
||||
}
|
||||
return CMTimeMakeWithSeconds(second, _playerLayer.player.currentTime.timescale);
|
||||
}
|
||||
|
||||
- (void)starTimer {
|
||||
[self stopTimer];
|
||||
NSTimeInterval timeInterval = [self getCropVideoDuration];
|
||||
self.timer = [NSTimer scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(playCropVideo) userInfo:nil repeats:YES];
|
||||
[self.timer fire];
|
||||
}
|
||||
|
||||
- (void)stopTimer {
|
||||
if (self.timer) {
|
||||
[self.videoEditView resetIndicatorLine];
|
||||
[_player pause];
|
||||
[self.timer invalidate];
|
||||
self.timer = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)playCropVideo {
|
||||
[_player seekToTime:[self getCropStartTime] toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
|
||||
[_player play];
|
||||
[self.videoEditView indicatorLineAnimateWithDuration:[self getCropVideoDuration] cropRect:self.videoEditView.cropRect];
|
||||
}
|
||||
|
||||
#pragma mark - Notification Method
|
||||
|
||||
- (void)pausePlayer {
|
||||
[_player pause];
|
||||
[_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlay"] forState:UIControlStateNormal];
|
||||
}
|
||||
|
||||
#pragma mark - lazy
|
||||
- (UIView *)iCloudErrorView{
|
||||
if (!_iCloudErrorView) {
|
||||
_iCloudErrorView = [[UIView alloc] initWithFrame:CGRectMake(0, [TZCommonTools tz_statusBarHeight] + 44 + 10, self.view.tz_width, 28)];
|
||||
UIImageView *icloud = [[UIImageView alloc] init];
|
||||
icloud.image = [UIImage tz_imageNamedFromMyBundle:@"iCloudError"];
|
||||
icloud.frame = CGRectMake(20, 0, 28, 28);
|
||||
[_iCloudErrorView addSubview:icloud];
|
||||
UILabel *label = [[UILabel alloc] init];
|
||||
label.frame = CGRectMake(53, 0, self.view.tz_width - 63, 28);
|
||||
label.font = [UIFont systemFontOfSize:10];
|
||||
label.textColor = [UIColor whiteColor];
|
||||
label.text = [NSBundle tz_localizedStringForKey:@"iCloud sync failed"];
|
||||
[_iCloudErrorView addSubview:label];
|
||||
[self.view addSubview:_iCloudErrorView];
|
||||
_iCloudErrorView.hidden = YES;
|
||||
}
|
||||
return _iCloudErrorView;
|
||||
}
|
||||
|
||||
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
|
||||
return UIInterfaceOrientationMaskPortrait;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
NSLog(@"%s",__func__);
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation TZVideoEditView {
|
||||
UILabel *_dragingLabel;
|
||||
CGFloat _itemWidth;
|
||||
CGFloat _beginOffsetX;
|
||||
CGFloat _endOffsetX;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self initSubViews];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initSubViews {
|
||||
_indicatorLine = UIView.new;
|
||||
_indicatorLine.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.7];
|
||||
[self addSubview:_indicatorLine];
|
||||
|
||||
_beginImgView = UIImageView.new;
|
||||
_beginImgView.image = [UIImage imageNamed:@"leftVideoEdit"];
|
||||
_beginImgView.userInteractionEnabled = YES;
|
||||
_beginImgView.tag = 0;
|
||||
UIPanGestureRecognizer *beginPanGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureAction:)];
|
||||
[_beginImgView addGestureRecognizer:beginPanGesture];
|
||||
[self addSubview:_beginImgView];
|
||||
|
||||
_endImgView = UIImageView.new;
|
||||
_endImgView.image = [UIImage imageNamed:@"rightVideoEdit"];
|
||||
_endImgView.userInteractionEnabled = YES;
|
||||
_endImgView.tag = 1;
|
||||
UIPanGestureRecognizer *endPanGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureAction:)];
|
||||
[_endImgView addGestureRecognizer:endPanGesture];
|
||||
[self addSubview:_endImgView];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
_beginImgView.frame = CGRectMake(VideoEditLeftMargin, 0, PanImageWidth, self.tz_height);
|
||||
_indicatorLine.frame = CGRectMake(_beginImgView.tz_right - 2, 2, 2, self.tz_height - 4);
|
||||
_endImgView.frame = CGRectMake(self.tz_width - PanImageWidth - VideoEditLeftMargin, 0, PanImageWidth, self.tz_height);
|
||||
|
||||
self.cropRect = CGRectMake(VideoEditLeftMargin + PanImageWidth, 0, self.tz_width - VideoEditLeftMargin * 2 - PanImageWidth * 2, self.tz_height);
|
||||
}
|
||||
|
||||
- (void)setAllImgWidth:(CGFloat)allImgWidth {
|
||||
_allImgWidth = allImgWidth;
|
||||
if ((NSInteger)roundf(self.videoDuration) <= 0) {
|
||||
self.minCropRectWidth = allImgWidth;
|
||||
return;
|
||||
}
|
||||
|
||||
CGFloat scale = MinCropVideoDuration / self.videoDuration;
|
||||
self.minCropRectWidth = scale * allImgWidth;
|
||||
}
|
||||
|
||||
- (void)setCropRect:(CGRect)cropRect {
|
||||
_cropRect = cropRect;
|
||||
self.beginImgView.tz_left = cropRect.origin.x - PanImageWidth;
|
||||
self.indicatorLine.tz_left = cropRect.origin.x - self.indicatorLine.tz_width;
|
||||
self.endImgView.tz_left = CGRectGetMaxX(cropRect);
|
||||
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect {
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
CGContextClearRect(context, rect);
|
||||
|
||||
CGPoint topPoints[] = {
|
||||
CGPointMake(self.cropRect.origin.x, 0),
|
||||
CGPointMake(CGRectGetMaxX(self.cropRect), 0)
|
||||
};
|
||||
CGPoint bottomPoints[] = {
|
||||
CGPointMake(self.cropRect.origin.x, self.tz_height),
|
||||
CGPointMake(CGRectGetMaxX(self.cropRect), self.tz_height)
|
||||
};
|
||||
|
||||
CGContextAddLines(context, topPoints, 2);
|
||||
CGContextAddLines(context, bottomPoints, 2);
|
||||
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
|
||||
CGContextSetLineWidth(context, 4.0);
|
||||
CGContextDrawPath(context, kCGPathStroke);
|
||||
}
|
||||
|
||||
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
|
||||
CGRect beginImgViewFrame = self.beginImgView.frame;
|
||||
beginImgViewFrame.origin.x -= PanImageWidth;
|
||||
beginImgViewFrame.size.width += PanImageWidth * 2;
|
||||
if (CGRectContainsPoint(beginImgViewFrame, point)) return self.beginImgView;
|
||||
|
||||
CGRect endImgViewFrame = self.endImgView.frame;
|
||||
endImgViewFrame.origin.x -= PanImageWidth;
|
||||
endImgViewFrame.size.width += PanImageWidth * 2;
|
||||
if (CGRectContainsPoint(endImgViewFrame, point)) return self.endImgView;
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark - private
|
||||
|
||||
- (void)indicatorLineAnimateWithDuration:(NSTimeInterval)duration cropRect:(CGRect)cropRect {
|
||||
[self resetIndicatorLine];
|
||||
[UIView animateWithDuration:duration delay:.0 options:UIViewAnimationOptionCurveLinear animations:^{
|
||||
self.indicatorLine.tz_left = CGRectGetMaxX(cropRect);
|
||||
} completion:nil];
|
||||
}
|
||||
|
||||
- (void)resetIndicatorLine {
|
||||
[self.indicatorLine.layer removeAllAnimations];
|
||||
self.indicatorLine.tz_left = CGRectGetMinX(self.cropRect) - self.indicatorLine.tz_width;
|
||||
}
|
||||
|
||||
- (void)panGestureAction:(UIGestureRecognizer *)gesture {
|
||||
CGPoint point = [gesture locationInView:self];
|
||||
CGRect rect = self.cropRect;
|
||||
CGFloat minCropRectLeft = VideoEditLeftMargin + PanImageWidth;
|
||||
|
||||
switch (gesture.view.tag) {
|
||||
case 0: { // 左边拖拽
|
||||
CGFloat maxX = self.endImgView.tz_left - self.minCropRectWidth;
|
||||
point.x = MAX(minCropRectLeft, MIN(point.x, maxX));
|
||||
point.y = 0;
|
||||
|
||||
rect.size.width = CGRectGetMaxX(rect) - point.x;
|
||||
rect.origin.x = point.x;
|
||||
} break;
|
||||
case 1: { // 右边拖拽
|
||||
minCropRectLeft = CGRectGetMaxX(self.beginImgView.frame) + self.minCropRectWidth;
|
||||
CGFloat maxX = self.tz_width - VideoEditLeftMargin - PanImageWidth;
|
||||
|
||||
point.x = MAX(minCropRectLeft, MIN(point.x, maxX));
|
||||
point.y = 0;
|
||||
|
||||
rect.size.width = (point.x - rect.origin.x);
|
||||
} break;
|
||||
default:break;
|
||||
}
|
||||
|
||||
self.cropRect = rect;
|
||||
|
||||
switch (gesture.state) {
|
||||
case UIGestureRecognizerStateBegan:
|
||||
case UIGestureRecognizerStateChanged: {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(editViewCropRectBeginChange)]) {
|
||||
[self.delegate editViewCropRectBeginChange];
|
||||
}
|
||||
} break;
|
||||
case UIGestureRecognizerStateEnded:
|
||||
case UIGestureRecognizerStateCancelled: {
|
||||
if (self.delegate && [self.delegate respondsToSelector:@selector(editViewCropRectEndChange)]) {
|
||||
[self.delegate editViewCropRectEndChange];
|
||||
}
|
||||
} break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@implementation TZVideoPictureCell
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
[self initSubViews];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)initSubViews {
|
||||
_imgView = [[UIImageView alloc] initWithFrame:self.bounds];
|
||||
_imgView.contentMode = UIViewContentModeScaleAspectFill;
|
||||
_imgView.clipsToBounds = YES;
|
||||
[self.contentView addSubview:_imgView];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// TZVideoEditedPreviewController.h
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by 肖兰月 on 2021/5/29.
|
||||
// Copyright © 2021 谭真. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TZVideoEditedPreviewController : UIViewController
|
||||
@property (nonatomic, copy) NSURL *videoURL;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,129 @@
|
||||
//
|
||||
// TZVideoEditedPreviewController.m
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by 肖兰月 on 2021/5/29.
|
||||
// Copyright © 2021 谭真. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TZVideoEditedPreviewController.h"
|
||||
#import <MediaPlayer/MediaPlayer.h>
|
||||
#import "TZImageManager.h"
|
||||
#import "TZImagePickerController.h"
|
||||
#import "UIView+TZLayout.h"
|
||||
|
||||
@interface TZVideoEditedPreviewController () {
|
||||
AVPlayer *_player;
|
||||
AVPlayerLayer *_playerLayer;
|
||||
UIButton *_playButton;
|
||||
UIImage *_cover;
|
||||
|
||||
UIView *_toolBar;
|
||||
UIButton *_doneButton;
|
||||
UIProgressView *_progress;
|
||||
|
||||
UIStatusBarStyle _originStatusBarStyle;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TZVideoEditedPreviewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
self.view.backgroundColor = [UIColor blackColor];
|
||||
[self configMoviePlayer];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pausePlayerAndShowNaviBar) name:UIApplicationWillResignActiveNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)configMoviePlayer {
|
||||
_player = [AVPlayer playerWithURL:self.videoURL];
|
||||
_playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
|
||||
[self.view.layer addSublayer:_playerLayer];
|
||||
|
||||
[self configPlayButton];
|
||||
[self configBottomToolBar];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pausePlayerAndShowNaviBar) name:AVPlayerItemDidPlayToEndTimeNotification object:self->_player.currentItem];
|
||||
}
|
||||
|
||||
- (void)configPlayButton {
|
||||
_playButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlay"] forState:UIControlStateNormal];
|
||||
[_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlayHL"] forState:UIControlStateHighlighted];
|
||||
[_playButton addTarget:self action:@selector(playButtonClick) forControlEvents:UIControlEventTouchUpInside];
|
||||
[self.view addSubview:_playButton];
|
||||
}
|
||||
|
||||
- (void)configBottomToolBar {
|
||||
_toolBar = [[UIView alloc] initWithFrame:CGRectZero];
|
||||
CGFloat rgb = 34 / 255.0;
|
||||
_toolBar.backgroundColor = [UIColor colorWithRed:rgb green:rgb blue:rgb alpha:0.7];
|
||||
|
||||
_doneButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
_doneButton.titleLabel.font = [UIFont systemFontOfSize:16];
|
||||
[_doneButton addTarget:self action:@selector(doneButtonClick) forControlEvents:UIControlEventTouchUpInside];
|
||||
TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
if (tzImagePickerVc) {
|
||||
[_doneButton setTitle:tzImagePickerVc.doneBtnTitleStr forState:UIControlStateNormal];
|
||||
[_doneButton setTitleColor:tzImagePickerVc.oKButtonTitleColorNormal forState:UIControlStateNormal];
|
||||
} else {
|
||||
[_doneButton setTitle:[NSBundle tz_localizedStringForKey:@"Done"] forState:UIControlStateNormal];
|
||||
[_doneButton setTitleColor:[UIColor colorWithRed:(83/255.0) green:(179/255.0) blue:(17/255.0) alpha:1.0] forState:UIControlStateNormal];
|
||||
}
|
||||
[_doneButton setTitleColor:tzImagePickerVc.oKButtonTitleColorDisabled forState:UIControlStateDisabled];
|
||||
[_toolBar addSubview:_doneButton];
|
||||
[self.view addSubview:_toolBar];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Layout
|
||||
|
||||
- (void)viewDidLayoutSubviews {
|
||||
[super viewDidLayoutSubviews];
|
||||
|
||||
BOOL isFullScreen = self.view.tz_height == [UIScreen mainScreen].bounds.size.height;
|
||||
CGFloat statusBarHeight = isFullScreen ? [TZCommonTools tz_statusBarHeight] : 0;
|
||||
CGFloat statusBarAndNaviBarHeight = statusBarHeight + self.navigationController.navigationBar.tz_height;
|
||||
|
||||
CGFloat toolBarHeight = 44 + [TZCommonTools tz_safeAreaInsets].bottom;
|
||||
_toolBar.frame = CGRectMake(0, self.view.tz_height - toolBarHeight, self.view.tz_width, toolBarHeight);
|
||||
[_doneButton sizeToFit];
|
||||
_doneButton.frame = CGRectMake(self.view.tz_width - _doneButton.tz_width - 12, 0, MAX(44, _doneButton.tz_width), 44);
|
||||
_playButton.frame = CGRectMake(0, statusBarAndNaviBarHeight, self.view.tz_width, self.view.tz_height - statusBarAndNaviBarHeight - toolBarHeight);
|
||||
_playerLayer.frame = self.view.bounds;
|
||||
}
|
||||
|
||||
#pragma mark - Click Event
|
||||
|
||||
- (void)playButtonClick {
|
||||
CMTime currentTime = _player.currentItem.currentTime;
|
||||
CMTime durationTime = _player.currentItem.duration;
|
||||
if (_player.rate == 0.0f) {
|
||||
if (currentTime.value == durationTime.value) [_player.currentItem seekToTime:CMTimeMake(0, 1)];
|
||||
[_player play];
|
||||
_toolBar.hidden = YES;
|
||||
[_playButton setImage:nil forState:UIControlStateNormal];
|
||||
} else {
|
||||
[self pausePlayerAndShowNaviBar];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)doneButtonClick {
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
#pragma mark - Notification Method
|
||||
|
||||
- (void)pausePlayerAndShowNaviBar {
|
||||
[_player pause];
|
||||
_toolBar.hidden = NO;
|
||||
[_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlay"] forState:UIControlStateNormal];
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// TZVideoPlayerController.h
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by 谭真 on 16/1/5.
|
||||
// Copyright © 2016年 谭真. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class TZAssetModel;
|
||||
@interface TZVideoPlayerController : UIViewController
|
||||
|
||||
@property (nonatomic, strong) TZAssetModel *model;
|
||||
|
||||
@end
|
||||
|
||||
325
Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZVideoPlayerController.m
generated
Normal file
@@ -0,0 +1,325 @@
|
||||
//
|
||||
// TZVideoPlayerController.m
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by 谭真 on 16/1/5.
|
||||
// Copyright © 2016年 谭真. All rights reserved.
|
||||
//
|
||||
|
||||
#import "TZVideoPlayerController.h"
|
||||
#import <MediaPlayer/MediaPlayer.h>
|
||||
#import "UIView+TZLayout.h"
|
||||
#import "TZImageManager.h"
|
||||
#import "TZAssetModel.h"
|
||||
#import "TZImagePickerController.h"
|
||||
#import "TZPhotoPreviewController.h"
|
||||
#import "TZVideoCropController.h"
|
||||
|
||||
@interface TZVideoPlayerController () {
|
||||
AVPlayer *_player;
|
||||
AVPlayerLayer *_playerLayer;
|
||||
UIButton *_playButton;
|
||||
UIImage *_playButtonNormalImage;
|
||||
UIImage *_cover;
|
||||
NSString *_outputPath;
|
||||
NSString *_errorMsg;
|
||||
|
||||
UIView *_toolBar;
|
||||
UIButton *_doneButton;
|
||||
UIButton *_editButton;
|
||||
UIProgressView *_progress;
|
||||
|
||||
UIStatusBarStyle _originStatusBarStyle;
|
||||
}
|
||||
@property (assign, nonatomic) BOOL needShowStatusBar;
|
||||
|
||||
// iCloud无法同步提示UI
|
||||
@property (nonatomic, strong) UIView *iCloudErrorView;
|
||||
|
||||
@end
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
|
||||
@implementation TZVideoPlayerController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
self.needShowStatusBar = ![UIApplication sharedApplication].statusBarHidden;
|
||||
self.view.backgroundColor = [UIColor blackColor];
|
||||
TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
if (tzImagePickerVc) {
|
||||
self.navigationItem.title = tzImagePickerVc.previewBtnTitleStr;
|
||||
}
|
||||
[self configMoviePlayer];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pausePlayerAndShowNaviBar) name:UIApplicationWillResignActiveNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated {
|
||||
[super viewWillAppear:animated];
|
||||
_originStatusBarStyle = [UIApplication sharedApplication].statusBarStyle;
|
||||
[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
if (self.needShowStatusBar) {
|
||||
[UIApplication sharedApplication].statusBarHidden = NO;
|
||||
}
|
||||
[UIApplication sharedApplication].statusBarStyle = _originStatusBarStyle;
|
||||
}
|
||||
|
||||
- (void)configMoviePlayer {
|
||||
[[TZImageManager manager] getPhotoWithAsset:_model.asset completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) {
|
||||
BOOL iCloudSyncFailed = !photo && [TZCommonTools isICloudSyncError:info[PHImageErrorKey]];
|
||||
self.iCloudErrorView.hidden = !iCloudSyncFailed;
|
||||
if (!isDegraded && photo) {
|
||||
self->_cover = photo;
|
||||
self->_doneButton.enabled = YES;
|
||||
self->_editButton.enabled = YES;
|
||||
}
|
||||
}];
|
||||
[[TZImageManager manager] getVideoWithAsset:_model.asset completion:^(AVPlayerItem *playerItem, NSDictionary *info) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self->_player = [AVPlayer playerWithPlayerItem:playerItem];
|
||||
self->_playerLayer = [AVPlayerLayer playerLayerWithPlayer:self->_player];
|
||||
self->_playerLayer.frame = self.view.bounds;
|
||||
[self.view.layer addSublayer:self->_playerLayer];
|
||||
[self addProgressObserver];
|
||||
[self configPlayButton];
|
||||
[self configBottomToolBar];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pausePlayerAndShowNaviBar) name:AVPlayerItemDidPlayToEndTimeNotification object:self->_player.currentItem];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
/// Show progress,do it next time / 给播放器添加进度更新,下次加上
|
||||
- (void)addProgressObserver{
|
||||
AVPlayerItem *playerItem = _player.currentItem;
|
||||
UIProgressView *progress = _progress;
|
||||
[_player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
|
||||
float current = CMTimeGetSeconds(time);
|
||||
float total = CMTimeGetSeconds([playerItem duration]);
|
||||
if (current) {
|
||||
[progress setProgress:(current/total) animated:YES];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)configPlayButton {
|
||||
_playButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
[_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlay"] forState:UIControlStateNormal];
|
||||
[_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlayHL"] forState:UIControlStateHighlighted];
|
||||
[_playButton addTarget:self action:@selector(playButtonClick) forControlEvents:UIControlEventTouchUpInside];
|
||||
[self.view addSubview:_playButton];
|
||||
}
|
||||
|
||||
- (void)configBottomToolBar {
|
||||
_toolBar = [[UIView alloc] initWithFrame:CGRectZero];
|
||||
CGFloat rgb = 34 / 255.0;
|
||||
_toolBar.backgroundColor = [UIColor colorWithRed:rgb green:rgb blue:rgb alpha:0.7];
|
||||
|
||||
_doneButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
_doneButton.titleLabel.font = [UIFont systemFontOfSize:16];
|
||||
if (!_cover) {
|
||||
_doneButton.enabled = NO;
|
||||
}
|
||||
[_doneButton addTarget:self action:@selector(doneButtonClick) forControlEvents:UIControlEventTouchUpInside];
|
||||
TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
if (tzImagePickerVc) {
|
||||
[_doneButton setTitle:tzImagePickerVc.doneBtnTitleStr forState:UIControlStateNormal];
|
||||
[_doneButton setTitleColor:tzImagePickerVc.oKButtonTitleColorNormal forState:UIControlStateNormal];
|
||||
} else {
|
||||
[_doneButton setTitle:[NSBundle tz_localizedStringForKey:@"Done"] forState:UIControlStateNormal];
|
||||
[_doneButton setTitleColor:[UIColor colorWithRed:(83/255.0) green:(179/255.0) blue:(17/255.0) alpha:1.0] forState:UIControlStateNormal];
|
||||
}
|
||||
[_doneButton setTitleColor:tzImagePickerVc.oKButtonTitleColorDisabled forState:UIControlStateDisabled];
|
||||
[_toolBar addSubview:_doneButton];
|
||||
[self.view addSubview:_toolBar];
|
||||
|
||||
if (tzImagePickerVc && tzImagePickerVc.allowEditVideo && roundf(self.model.asset.duration) > 1) {
|
||||
_editButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
_editButton.titleLabel.font = [UIFont systemFontOfSize:16];
|
||||
if (!_cover) {
|
||||
_editButton.enabled = NO;
|
||||
}
|
||||
[_editButton addTarget:self action:@selector(editButtonClick) forControlEvents:UIControlEventTouchUpInside];
|
||||
[_editButton setTitle:tzImagePickerVc.editBtnTitleStr forState:UIControlStateNormal];
|
||||
[_editButton setTitleColor:tzImagePickerVc.oKButtonTitleColorNormal forState:UIControlStateNormal];
|
||||
[_editButton setTitleColor:tzImagePickerVc.oKButtonTitleColorDisabled forState:UIControlStateDisabled];
|
||||
[_toolBar addSubview:_editButton];
|
||||
}
|
||||
|
||||
if (tzImagePickerVc.videoPreviewPageUIConfigBlock) {
|
||||
tzImagePickerVc.videoPreviewPageUIConfigBlock(_playButton, _toolBar, _editButton, _doneButton);
|
||||
}
|
||||
}
|
||||
|
||||
- (UIStatusBarStyle)preferredStatusBarStyle {
|
||||
TZImagePickerController *tzImagePicker = (TZImagePickerController *)self.navigationController;
|
||||
if (tzImagePicker && [tzImagePicker isKindOfClass:[TZImagePickerController class]]) {
|
||||
return tzImagePicker.statusBarStyle;
|
||||
}
|
||||
return [super preferredStatusBarStyle];
|
||||
}
|
||||
|
||||
#pragma mark - Layout
|
||||
|
||||
- (void)viewDidLayoutSubviews {
|
||||
[super viewDidLayoutSubviews];
|
||||
TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
|
||||
BOOL isFullScreen = self.view.tz_height == [UIScreen mainScreen].bounds.size.height;
|
||||
CGFloat statusBarHeight = isFullScreen ? [TZCommonTools tz_statusBarHeight] : 0;
|
||||
CGFloat statusBarAndNaviBarHeight = statusBarHeight + self.navigationController.navigationBar.tz_height;
|
||||
_playerLayer.frame = self.view.bounds;
|
||||
CGFloat toolBarHeight = 44 + [TZCommonTools tz_safeAreaInsets].bottom;
|
||||
_toolBar.frame = CGRectMake(0, self.view.tz_height - toolBarHeight, self.view.tz_width, toolBarHeight);
|
||||
[_doneButton sizeToFit];
|
||||
_doneButton.frame = CGRectMake(self.view.tz_width - _doneButton.tz_width - 12, 0, MAX(44, _doneButton.tz_width), 44);
|
||||
_playButton.frame = CGRectMake(0, statusBarAndNaviBarHeight, self.view.tz_width, self.view.tz_height - statusBarAndNaviBarHeight - toolBarHeight);
|
||||
if (tzImagePickerVc.allowEditVideo) {
|
||||
_editButton.frame = CGRectMake(12, 0, 44, 44);
|
||||
[_editButton sizeToFit];
|
||||
_editButton.tz_height = 44;
|
||||
}
|
||||
if (tzImagePickerVc.videoPreviewPageDidLayoutSubviewsBlock) {
|
||||
tzImagePickerVc.videoPreviewPageDidLayoutSubviewsBlock(_playButton, _toolBar, _editButton, _doneButton);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Click Event
|
||||
|
||||
- (void)playButtonClick {
|
||||
CMTime currentTime = _player.currentItem.currentTime;
|
||||
CMTime durationTime = _player.currentItem.duration;
|
||||
if (_player.rate == 0.0f) {
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"TZ_VIDEO_PLAY_NOTIFICATION" object:_player];
|
||||
if (currentTime.value == durationTime.value) [_player.currentItem seekToTime:CMTimeMake(0, 1)];
|
||||
[_player play];
|
||||
[self.navigationController setNavigationBarHidden:YES];
|
||||
_toolBar.hidden = YES;
|
||||
_playButtonNormalImage = [_playButton imageForState:UIControlStateNormal];
|
||||
[_playButton setImage:nil forState:UIControlStateNormal];
|
||||
[UIApplication sharedApplication].statusBarHidden = YES;
|
||||
} else {
|
||||
[self pausePlayerAndShowNaviBar];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)editButtonClick {
|
||||
TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
TZVideoCropController *videoCropVc = [[TZVideoCropController alloc] init];
|
||||
videoCropVc.model = self.model;
|
||||
videoCropVc.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
|
||||
videoCropVc.modalPresentationStyle = UIModalPresentationFullScreen;
|
||||
videoCropVc.modalPresentationCapturesStatusBarAppearance = YES;
|
||||
videoCropVc.imagePickerVc = imagePickerVc;
|
||||
[self presentViewController:videoCropVc animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)doneButtonClick {
|
||||
if ([[TZImageManager manager] isAssetCannotBeSelected:_model.asset]) {
|
||||
return;
|
||||
}
|
||||
TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
if (imagePickerVc.allowEditVideo) {
|
||||
[imagePickerVc showProgressHUD];
|
||||
[[TZImageManager manager] getVideoOutputPathWithAsset:_model.asset presetName:imagePickerVc.presetName success:^(NSString *outputPath) {
|
||||
[imagePickerVc hideProgressHUD];
|
||||
self->_outputPath = outputPath;
|
||||
[self dismissAndCallDelegateMethod];
|
||||
} failure:^(NSString *errorMessage, NSError *error) {
|
||||
[imagePickerVc hideProgressHUD];
|
||||
self->_errorMsg = errorMessage;
|
||||
[self dismissAndCallDelegateMethod];
|
||||
}];
|
||||
} else {
|
||||
[self dismissAndCallDelegateMethod];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dismissAndCallDelegateMethod {
|
||||
TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
if (!imagePickerVc) {
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
return;
|
||||
}
|
||||
if (imagePickerVc.autoDismiss) {
|
||||
[imagePickerVc dismissViewControllerAnimated:YES completion:^{
|
||||
[self callDelegateMethod];
|
||||
}];
|
||||
} else {
|
||||
[self callDelegateMethod];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)callDelegateMethod {
|
||||
TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController;
|
||||
if (imagePickerVc.allowEditVideo) {
|
||||
if (_outputPath) {
|
||||
if ([imagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didFinishPickingAndEditingVideo:outputPath:error:)]) {
|
||||
[imagePickerVc.pickerDelegate imagePickerController:imagePickerVc didFinishPickingAndEditingVideo:self->_cover outputPath:self->_outputPath error:nil];
|
||||
}
|
||||
if (imagePickerVc.didFinishPickingAndEditingVideoHandle) {
|
||||
imagePickerVc.didFinishPickingAndEditingVideoHandle(self->_cover, self->_outputPath, nil);
|
||||
}
|
||||
} else {
|
||||
if ([imagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didFinishPickingAndEditingVideo:outputPath:error:)]) {
|
||||
[imagePickerVc.pickerDelegate imagePickerController:imagePickerVc didFinishPickingAndEditingVideo:nil outputPath:nil error:self->_errorMsg];
|
||||
}
|
||||
if (imagePickerVc.didFinishPickingAndEditingVideoHandle) {
|
||||
imagePickerVc.didFinishPickingAndEditingVideoHandle(nil, nil, self->_errorMsg);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ([imagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didFinishPickingVideo:sourceAssets:)]) {
|
||||
[imagePickerVc.pickerDelegate imagePickerController:imagePickerVc didFinishPickingVideo:_cover sourceAssets:_model.asset];
|
||||
}
|
||||
if (imagePickerVc.didFinishPickingVideoHandle) {
|
||||
imagePickerVc.didFinishPickingVideoHandle(_cover,_model.asset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Notification Method
|
||||
|
||||
- (void)pausePlayerAndShowNaviBar {
|
||||
[_player pause];
|
||||
_toolBar.hidden = NO;
|
||||
[self.navigationController setNavigationBarHidden:NO];
|
||||
UIImage *normalImage = _playButtonNormalImage ?: [UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlay"];
|
||||
[_playButton setImage:normalImage forState:UIControlStateNormal];
|
||||
|
||||
if (self.needShowStatusBar) {
|
||||
[UIApplication sharedApplication].statusBarHidden = NO;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - lazy
|
||||
- (UIView *)iCloudErrorView{
|
||||
if (!_iCloudErrorView) {
|
||||
_iCloudErrorView = [[UIView alloc] initWithFrame:CGRectMake(0, [TZCommonTools tz_statusBarHeight] + 44 + 10, self.view.tz_width, 28)];
|
||||
UIImageView *icloud = [[UIImageView alloc] init];
|
||||
icloud.image = [UIImage tz_imageNamedFromMyBundle:@"iCloudError"];
|
||||
icloud.frame = CGRectMake(20, 0, 28, 28);
|
||||
[_iCloudErrorView addSubview:icloud];
|
||||
UILabel *label = [[UILabel alloc] init];
|
||||
label.frame = CGRectMake(53, 0, self.view.tz_width - 63, 28);
|
||||
label.font = [UIFont systemFontOfSize:10];
|
||||
label.textColor = [UIColor whiteColor];
|
||||
label.text = [NSBundle tz_localizedStringForKey:@"iCloud sync failed"];
|
||||
[_iCloudErrorView addSubview:label];
|
||||
[self.view addSubview:_iCloudErrorView];
|
||||
_iCloudErrorView.hidden = YES;
|
||||
}
|
||||
return _iCloudErrorView;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
@end
|
||||
31
Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/UIView+TZLayout.h
generated
Normal file
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// UIView+TZLayout.h
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by 谭真 on 15/2/24.
|
||||
// Copyright © 2015年 谭真. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
typedef enum : NSUInteger {
|
||||
TZOscillatoryAnimationToBigger,
|
||||
TZOscillatoryAnimationToSmaller,
|
||||
} TZOscillatoryAnimationType;
|
||||
|
||||
@interface UIView (TZLayout)
|
||||
|
||||
@property (nonatomic) CGFloat tz_left; ///< Shortcut for frame.origin.x.
|
||||
@property (nonatomic) CGFloat tz_top; ///< Shortcut for frame.origin.y
|
||||
@property (nonatomic) CGFloat tz_right; ///< Shortcut for frame.origin.x + frame.size.width
|
||||
@property (nonatomic) CGFloat tz_bottom; ///< Shortcut for frame.origin.y + frame.size.height
|
||||
@property (nonatomic) CGFloat tz_width; ///< Shortcut for frame.size.width.
|
||||
@property (nonatomic) CGFloat tz_height; ///< Shortcut for frame.size.height.
|
||||
@property (nonatomic) CGFloat tz_centerX; ///< Shortcut for center.x
|
||||
@property (nonatomic) CGFloat tz_centerY; ///< Shortcut for center.y
|
||||
@property (nonatomic) CGPoint tz_origin; ///< Shortcut for frame.origin.
|
||||
@property (nonatomic) CGSize tz_size; ///< Shortcut for frame.size.
|
||||
|
||||
+ (void)showOscillatoryAnimationWithLayer:(CALayer *)layer type:(TZOscillatoryAnimationType)type;
|
||||
|
||||
@end
|
||||
126
Pods/TZImagePickerController/TZImagePickerController/TZImagePickerController/UIView+TZLayout.m
generated
Normal file
@@ -0,0 +1,126 @@
|
||||
//
|
||||
// UIView+TZLayout.m
|
||||
// TZImagePickerController
|
||||
//
|
||||
// Created by 谭真 on 15/2/24.
|
||||
// Copyright © 2015年 谭真. All rights reserved.
|
||||
//
|
||||
|
||||
#import "UIView+TZLayout.h"
|
||||
|
||||
@implementation UIView (TZLayout)
|
||||
|
||||
- (CGFloat)tz_left {
|
||||
return self.frame.origin.x;
|
||||
}
|
||||
|
||||
- (void)setTz_left:(CGFloat)x {
|
||||
CGRect frame = self.frame;
|
||||
frame.origin.x = x;
|
||||
self.frame = frame;
|
||||
}
|
||||
|
||||
- (CGFloat)tz_top {
|
||||
return self.frame.origin.y;
|
||||
}
|
||||
|
||||
- (void)setTz_top:(CGFloat)y {
|
||||
CGRect frame = self.frame;
|
||||
frame.origin.y = y;
|
||||
self.frame = frame;
|
||||
}
|
||||
|
||||
- (CGFloat)tz_right {
|
||||
return self.frame.origin.x + self.frame.size.width;
|
||||
}
|
||||
|
||||
- (void)setTz_right:(CGFloat)right {
|
||||
CGRect frame = self.frame;
|
||||
frame.origin.x = right - frame.size.width;
|
||||
self.frame = frame;
|
||||
}
|
||||
|
||||
- (CGFloat)tz_bottom {
|
||||
return self.frame.origin.y + self.frame.size.height;
|
||||
}
|
||||
|
||||
- (void)setTz_bottom:(CGFloat)bottom {
|
||||
CGRect frame = self.frame;
|
||||
frame.origin.y = bottom - frame.size.height;
|
||||
self.frame = frame;
|
||||
}
|
||||
|
||||
- (CGFloat)tz_width {
|
||||
return self.frame.size.width;
|
||||
}
|
||||
|
||||
- (void)setTz_width:(CGFloat)width {
|
||||
CGRect frame = self.frame;
|
||||
frame.size.width = width;
|
||||
self.frame = frame;
|
||||
}
|
||||
|
||||
- (CGFloat)tz_height {
|
||||
return self.frame.size.height;
|
||||
}
|
||||
|
||||
- (void)setTz_height:(CGFloat)height {
|
||||
CGRect frame = self.frame;
|
||||
frame.size.height = height;
|
||||
self.frame = frame;
|
||||
}
|
||||
|
||||
- (CGFloat)tz_centerX {
|
||||
return self.center.x;
|
||||
}
|
||||
|
||||
- (void)setTz_centerX:(CGFloat)centerX {
|
||||
self.center = CGPointMake(centerX, self.center.y);
|
||||
}
|
||||
|
||||
- (CGFloat)tz_centerY {
|
||||
return self.center.y;
|
||||
}
|
||||
|
||||
- (void)setTz_centerY:(CGFloat)centerY {
|
||||
self.center = CGPointMake(self.center.x, centerY);
|
||||
}
|
||||
|
||||
- (CGPoint)tz_origin {
|
||||
return self.frame.origin;
|
||||
}
|
||||
|
||||
- (void)setTz_origin:(CGPoint)origin {
|
||||
CGRect frame = self.frame;
|
||||
frame.origin = origin;
|
||||
self.frame = frame;
|
||||
}
|
||||
|
||||
- (CGSize)tz_size {
|
||||
return self.frame.size;
|
||||
}
|
||||
|
||||
- (void)setTz_size:(CGSize)size {
|
||||
CGRect frame = self.frame;
|
||||
frame.size = size;
|
||||
self.frame = frame;
|
||||
}
|
||||
|
||||
+ (void)showOscillatoryAnimationWithLayer:(CALayer *)layer type:(TZOscillatoryAnimationType)type{
|
||||
NSNumber *animationScale1 = type == TZOscillatoryAnimationToBigger ? @(1.15) : @(0.5);
|
||||
NSNumber *animationScale2 = type == TZOscillatoryAnimationToBigger ? @(0.92) : @(1.15);
|
||||
|
||||
[UIView animateWithDuration:0.15 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut animations:^{
|
||||
[layer setValue:animationScale1 forKeyPath:@"transform.scale"];
|
||||
} completion:^(BOOL finished) {
|
||||
[UIView animateWithDuration:0.15 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut animations:^{
|
||||
[layer setValue:animationScale2 forKeyPath:@"transform.scale"];
|
||||
} completion:^(BOOL finished) {
|
||||
[UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut animations:^{
|
||||
[layer setValue:@(1.0) forKeyPath:@"transform.scale"];
|
||||
} completion:nil];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||