首次提交

This commit is contained in:
启星
2025-08-08 11:05:33 +08:00
parent 1b3bb91b4a
commit adc1a2a25d
8803 changed files with 708874 additions and 0 deletions

22
Pods/TZImagePickerController/LICENSE generated Normal file
View 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
View File

@@ -0,0 +1,176 @@
# TZImagePickerController
[![CocoaPods](https://img.shields.io/cocoapods/v/TZImagePickerController.svg?style=flat)](https://github.com/banchichen/TZImagePickerController)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](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正常说明你可以升级下新版试试。
## 重要提示23.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 Libraryyou 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 常见问题
**Qpod search TZImagePickerController 搜索出来的不是最新版本**
A需要在终端执行cd转换文件路径命令退回到Desktop然后执行pod setup命令更新本地spec缓存可能需要几分钟,然后再搜索就可以了
**Q拍照后照片保存失败**
A请参考issue481https://github.com/banchichen/TZImagePickerController/issues/481 的信息排查若还有问题请直接在issue内评论
**Qphotos数组图片不是原图如何获取原图**
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对象**
A3.0.1版本已支持,需新接一个库:[TZImagePreviewController](https://github.com/banchichen/TZImagePreviewController)请参考里面的Demo使用。
**Q设置可选视频的最大/最小时长?照片的最小/最大尺寸?不符合要求的不显示**
A可以的参照Demo的isAssetCanBeDisplayed方法实现。我会返回asset出来显示与否你来决定注意这个是一个同步方法对于需要根据asset去异步获取的信息如视频的大小、视频是否存在iCloud里来过滤的无法做到。如果真要这样做相册打开速度会变慢你需要改我源码。
如果需要显示选择时才提醒用户不可选则实现isAssetCanBeSelected用户选择时会调用它
**Q预览页面出现了导航栏**
Ahttps://github.com/banchichen/TZImagePickerController/issues/652
**Q可否增加微信编辑图片的功能**
A考虑下优先级低
**Q是否有QQ/微信群/钉钉群?**
A有「钉钉群33192786」和「QQ群859033147」推荐加钉钉群答疑响应更快
**Q想提交一个Pull Request**
A请先加钉钉群(33192786)说下方案,和我确认下,避免同时改动同一处内容。**一个PR请只修复1个问题变动内容越少越好**。
**Qdemo在真机上跑不起来**
A1、team选你自己的2、bundleId也改成你自己的或改成一个不会和别人重复的。可参考[简书的这篇博客](https://www.jianshu.com/p/cbe59138fca6)
**Q3.6.4以上版本设置导航栏颜色无效?**
A参考Demo里的代码加上imagePickerVc.navigationBar.standardAppearance的相关设置
**Q设置导航栏颜色无效导航栏颜色总是白色**
A是否有集成WRNavigationBar如有参考其readme调一下它的wr_setBlackList把TZImagePickerController相关的控制器放到黑名单里使得不受WRNavigationBar的影响。如果没有集成可在issues列表里搜一下看看类似的issue参考下如实在没头绪可加群提供个能复现该问题的demo0~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但有几率导致安卓拍的视频导出失败。此外也可参考这个issuehttps://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/

View 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

View 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

View 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

View 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

View 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

View 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];
}
}
/// allowPreviewNO
- (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

View 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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View 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 {
// imagescount
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

View 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 modificationDateDefault 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

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

View File

@@ -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 ثانية";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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 lorsquelle 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";

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -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 秒間選択されました";

View File

@@ -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 초 동안 선택됨";

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 B

View File

@@ -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";

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -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 секунд";

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

View File

@@ -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";

View 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 modificationDateDefault is YES
/// 对照片排序按修改时间升序默认是YES。如果设置为NO,最新的照片会显示在最前面,内部的拍照按钮会排在第一个
@property (nonatomic, assign) BOOL sortAscendingByModificationDate;
/// The pixel width of output image, Default is 828pxyou 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;
/// 语言bundlepreferredLanguage变化时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; ///< 允许裁剪,默认为YESshowSelectBtn为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

View 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

View File

@@ -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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View 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

View 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

View File

@@ -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

View 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;
}
// iCloudUI
[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;
// onlyReturnAssetNO,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

View 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

View 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

View 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

View 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;
}
// iCloudUI
@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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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;
// iCloudUI
@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 progressdo 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

View 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

View 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