TZImagePickerController核心功能解析:如何优雅实现图片/视频多选功能
引言:移动端媒体选择的痛点与解决方案
在iOS应用开发中,图片/视频选择功能是社交、电商、内容创作类App的核心需求。然而,系统原生的UIImagePickerController存在诸多限制:不支持多选、缺乏预览功能、无法同时选择图片和视频、UI定制困难等问题。这些痛点直接影响用户体验和开发效率。
TZImagePickerController作为一款功能全面的媒体选择框架,彻底解决了这些问题。它支持图片/视频多选、原图选择、预览、裁剪等核心功能,同时提供高度自定义的UI接口,最低支持iOS10+系统。本文将深入剖析其核心功能实现原理,帮助开发者快速掌握这一强大工具。
读完本文,你将能够:
- 掌握TZImagePickerController的完整集成流程
- 实现图片/视频的高效多选功能
- 理解框架的核心类设计与数据流转机制
- 定制符合App风格的媒体选择界面
- 解决性能优化、权限处理等关键问题
一、框架架构与核心类解析
1.1 整体架构设计
TZImagePickerController采用分层设计,主要包含四大模块:资源管理、UI控制器、数据模型和工具类。各模块职责清晰,通过协议通信,保证了代码的可维护性和扩展性。
1.2 核心类功能详解
TZImageManager:资源管理中心
- 功能:封装
Photos框架操作,统一管理图片/视频资源的获取、加载和处理 - 关键方法:
getAssetFromPHAsset::将系统PHAsset转换为框架内部TZAssetModelrequestImageForAsset:size:completion::异步请求指定尺寸的图片getVideoOutputPathWithAsset:completion::获取视频文件路径
- 性能优化:内部实现请求队列管理,避免并发请求过多导致内存峰值
TZAssetModel:媒体资源模型
- 属性:
@property (nonatomic, strong) PHAsset *asset; // 系统资源对象 @property (nonatomic, assign) TZAssetModelMediaType type; // 资源类型(图片/视频/GIF) @property (nonatomic, assign) NSTimeInterval duration; // 视频时长 @property (nonatomic, assign) BOOL isSelected; // 是否被选中 - 作用:抽象媒体资源,隔离系统API依赖,便于扩展额外属性
TZImagePickerController:主控制器
- 核心属性:
@property (nonatomic, assign) NSInteger maxImagesCount; // 最大可选数量 @property (nonatomic, assign) BOOL allowPickingVideo; // 是否允许选视频 @property (nonatomic, assign) BOOL allowPickingOriginalPhoto; // 是否允许选原图 @property (nonatomic, copy) void (^didFinishPickingPhotosHandle)(NSArray<UIImage *>, NSArray *, BOOL); - 职责:协调各子控制器,管理选中状态,触发结果回调
二、图片/视频多选功能实现
2.1 多选核心机制
TZImagePickerController的多选功能通过以下机制实现:
- 选中状态管理:使用
TZAssetModel的isSelected属性跟踪选择状态 - 数量控制:通过
maxImagesCount限制最大选择数量 - UI反馈:选中时显示序号标记,达到上限时禁用未选中项
- 数据同步:在选择器和预览控制器间共享选中数据
核心实现代码:
// TZAssetCell.m
- (void)setModel:(TZAssetModel *)model {
_model = model;
self.selectButton.selected = model.isSelected;
// 更新选中序号
if (model.isSelected) {
NSInteger index = [self.tzImagePickerVc.selectedModels indexOfObject:model];
self.selectIndexButton.hidden = NO;
self.selectIndexButton.titleLabel.text = [NSString stringWithFormat:@"%zd", index + 1];
} else {
self.selectIndexButton.hidden = YES;
}
// 处理达到最大选择数量的情况
if (self.tzImagePickerVc.selectedModels.count >= self.tzImagePickerVc.maxImagesCount && !model.isSelected) {
self.cannotSelectLayer.hidden = NO;
} else {
self.cannotSelectLayer.hidden = YES;
}
}
// TZPhotoPickerController.m
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
TZAssetModel *model = self.assets[indexPath.item];
// 检查是否可以选择
if (![self.tzImagePickerVc isAssetCanBeSelected:model.asset]) {
return;
}
// 切换选择状态
if (model.isSelected) {
[self.tzImagePickerVc removeSelectedModel:model];
} else {
if (self.tzImagePickerVc.selectedModels.count >= self.tzImagePickerVc.maxImagesCount) {
[self showMaxTip];
return;
}
[self.tzImagePickerVc addSelectedModel:model];
}
// 更新UI
TZAssetCell *cell = (TZAssetCell *)[collectionView cellForItemAtIndexPath:indexPath];
cell.model = model;
[self refreshBottomToolBar];
}
2.2 图片与视频混合选择
TZImagePickerController支持同时选择图片和视频,通过TZAssetModelMediaType枚举区分资源类型:
typedef NS_ENUM(NSInteger, TZAssetModelMediaType) {
TZAssetModelMediaTypePhoto, // 图片
TZAssetModelMediaTypeLivePhoto,// Live Photo
TZAssetModelMediaTypeVideo, // 视频
TZAssetModelMediaTypeAudio // 音频
};
实现关键点:
- 在
TZImageManager中根据PHAsset的mediaType属性初始化TZAssetModel - 在UI层面通过不同图标区分视频和图片(视频显示时长)
- 在选择逻辑中统一计数,不区分类型(除非特殊配置)
视频特殊处理:
- 显示时长格式化:
01:23(分:秒) - 预览时使用专用的视频播放器
TZVideoPlayerController - 支持视频裁剪功能(通过
allowEditVideo属性开启)
2.3 原图选择功能
原图选择是一个重要功能,尤其在需要高质量图片的场景。实现机制如下:
- UI层面:底部工具栏提供"原图"开关按钮
- 状态管理:通过
isSelectOriginalPhoto属性记录选择状态 - 数据处理:根据选择状态决定返回压缩图还是原图
// TZImagePickerController.m
- (void)originalPhotoButtonClick:(UIButton *)button {
button.selected = !button.selected;
self.isSelectOriginalPhoto = button.selected;
self.originalPhotoLabel.textColor = button.selected ? self.oKButtonTitleColorNormal : self.barItemTextColor;
}
// 获取图片时根据状态决定质量
- (void)requestImageForAsset:(PHAsset *)asset completion:(void (^)(UIImage *))completion {
CGSize size = self.isSelectOriginalPhoto ? PHImageManagerMaximumSize : CGSizeMake(828, 828);
[self.imageManager requestImageForAsset:asset size:size completion:completion];
}
原图选择会显著增加内存占用和传输大小,框架内部通过NSOperationQueue控制并发数,避免内存峰值过高。
三、完整集成与使用指南
3.1 环境配置与依赖
系统要求
- iOS 10.0+
- Xcode 10.0+
权限配置
在Info.plist中添加必要的权限描述:
<key>NSPhotoLibraryUsageDescription</key>
<string>需要访问您的相册以选择图片</string>
<key>NSCameraUsageDescription</key>
<string>需要访问相机以拍摄照片</string>
<key>NSMicrophoneUsageDescription</key>
<string>需要访问麦克风以录制视频</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>需要访问位置信息以记录照片位置</string>
3.2 安装方法
CocoaPods集成
# Podfile
pod 'TZImagePickerController' # 完整版本
# 或
pod 'TZImagePickerController/Basic' # 无定位功能的精简版本
执行安装命令:
pod install
手动集成
- 克隆仓库:
git clone https://gitcode.com/gh_mirrors/tz/TZImagePickerController.git
- 将
TZImagePickerController文件夹拖拽到项目中 - 添加依赖框架:
Photos.framework、AVFoundation.framework
3.3 基础使用示例
1. 简单多选功能
#import "TZImagePickerController.h"
// 在需要调用的地方
- (void)showImagePicker {
// 初始化选择器,最多选择9张图片
TZImagePickerController *imagePicker = [[TZImagePickerController alloc] initWithMaxImagesCount:9 delegate:self];
// 设置代理回调
[imagePicker setDidFinishPickingPhotosHandle:^(NSArray<UIImage *> *photos, NSArray *assets, BOOL isSelectOriginalPhoto) {
// 处理选中的图片
self.selectedPhotos = photos;
[self.collectionView reloadData];
}];
// 模态弹出
[self presentViewController:imagePicker animated:YES completion:nil];
}
// 实现代理方法(可选)
#pragma mark - TZImagePickerControllerDelegate
- (void)imagePickerController:(TZImagePickerController *)picker didFinishPickingPhotos:(NSArray<UIImage *> *)photos sourceAssets:(NSArray *)assets isSelectOriginalPhoto:(BOOL)isSelectOriginalPhoto {
NSLog(@"选中了%zd张图片,是否原图:%@", photos.count, isSelectOriginalPhoto ? @"是" : @"否");
[picker dismissViewControllerAnimated:YES completion:nil];
}
2. 高级配置示例
// 配置同时选择图片和视频
TZImagePickerController *imagePicker = [[TZImagePickerController alloc] initWithMaxImagesCount:9 delegate:self];
imagePicker.allowPickingVideo = YES; // 允许选择视频
imagePicker.allowPickingImage = YES; // 允许选择图片
imagePicker.allowPickingGif = YES; // 允许选择GIF
imagePicker.allowEditVideo = YES; // 允许编辑视频
imagePicker.maxCropVideoDuration = 15; // 视频最大裁剪时长15秒
imagePicker.allowPreview = YES; // 允许预览
imagePicker.showSelectedIndex = YES; // 显示选中序号
imagePicker.photoWidth = 1200; // 导出图片宽度
imagePicker.oKButtonTitleColorNormal = [UIColor redColor]; // 自定义按钮颜色
3. 单选裁剪功能
// 初始化裁剪模式的选择器
TZImagePickerController *imagePicker = [[TZImagePickerController alloc] initWithMaxImagesCount:1 delegate:self];
imagePicker.allowCrop = YES; // 允许裁剪
imagePicker.cropRect = CGRectMake(0, 100, 300, 300); // 设置裁剪框
imagePicker.needCircleCrop = YES; // 圆形裁剪
imagePicker.circleCropRadius = 150; // 圆形裁剪半径
// 裁剪完成回调
imagePicker.didFinishPickingPhotosHandle = ^(NSArray<UIImage *> *photos, NSArray *assets, BOOL isSelectOriginalPhoto) {
self.avatarImageView.image = photos.firstObject;
};
3.4 自定义UI样式
TZImagePickerController提供丰富的UI自定义接口,几乎所有可见元素都可以定制:
// 自定义导航栏样式
imagePicker.naviBgColor = [UIColor whiteColor];
imagePicker.naviTitleColor = [UIColor blackColor];
imagePicker.naviTitleFont = [UIFont boldSystemFontOfSize:18];
// 自定义底部工具栏
imagePicker.photoPickerPageUIConfigBlock = ^(UICollectionView *collectionView, UIView *bottomToolBar, UIButton *previewButton, UIButton *originalPhotoButton, UILabel *originalPhotoLabel, UIButton *doneButton, UIImageView *numberImageView, UILabel *numberLabel, UIView *divideLine) {
// 修改完成按钮样式
doneButton.backgroundColor = [UIColor blueColor];
doneButton.layer.cornerRadius = 22;
doneButton.clipsToBounds = YES;
// 修改分割线
divideLine.backgroundColor = [UIColor lightGrayColor];
};
// 自定义选中图标
imagePicker.photoSelImage = [UIImage imageNamed:@"custom_selected"];
imagePicker.photoDefImage = [UIImage imageNamed:@"custom_unselected"];
四、性能优化与最佳实践
4.1 性能优化策略
TZImagePickerController在设计时充分考虑了性能问题,采用多种优化策略:
图片加载优化
- 按需加载:只加载当前可见区域的图片
- 尺寸适配:根据显示需求请求合适尺寸的图片,避免大图片占用过多内存
- 缓存机制:利用
Photos框架的缓存机制,避免重复请求
// 高效图片加载
- (void)requestImageForAsset:(PHAsset *)asset size:(CGSize)size completion:(void (^)(UIImage *image))completion {
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
options.resizeMode = PHImageRequestOptionsResizeModeFast;
options.deliveryMode = PHImageRequestOptionsDeliveryModeOpportunistic;
options.synchronous = NO;
[self.imageManager requestImageForAsset:asset targetSize:size contentMode:PHImageContentModeAspectFill options:options resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
BOOL downloadFinined = ![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey];
if (result && downloadFinined) {
completion(result);
}
}];
}
内存管理
- 自动释放:及时释放不可见的图片资源
- 并发控制:限制同时请求的图片数量
- 大列表优化:采用
UICollectionView的重用机制
视频处理优化
- 异步处理:视频导出和处理在后台线程进行
- 进度反馈:提供导出进度提示,提升用户体验
- 临时文件管理:及时清理不再需要的临时视频文件
4.2 常见问题解决方案
问题1:多选大量图片时内存飙升
解决方案:
- 限制最大可选数量(建议不超过20张)
- 使用
onlyReturnAsset模式,只返回PHAsset对象,需要时再加载图片 - 实现图片加载队列控制
// 优化大量图片选择
imagePicker.onlyReturnAsset = YES; // 只返回PHAsset,不立即加载图片
// 需要显示图片时再单独请求
- (void)loadImageForAsset:(PHAsset *)asset {
[[TZImageManager sharedManager] requestImageForAsset:asset size:CGSizeMake(200, 200) completion:^(UIImage *image) {
// 更新UI
}];
}
问题2:iOS14+权限适配
解决方案: iOS14引入了"Limited Photos Library"权限,需要特殊处理:
// 检查并请求完整相册访问权限
- (void)checkPhotoLibraryPermission {
if (@available(iOS 14, *)) {
[PHPhotoLibrary requestAuthorizationForAccessLevel:PHAccessLevelRead handler:^(PHAuthorizationStatus status) {
if (status == PHAuthorizationStatusLimited) {
// 提示用户授予完整访问权限
dispatch_async(dispatch_get_main_queue(), ^{
[self showLimitedAccessAlert];
});
}
}];
}
}
// 提示用户切换到完整访问权限
- (void)showLimitedAccessAlert {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:@"当前仅能访问部分照片,请到设置中授予完整相册访问权限" preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil]];
[alert addAction:[UIAlertAction actionWithTitle:@"去设置" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil];
}]];
[self presentViewController:alert animated:YES completion:nil];
}
问题3:自定义UI后布局错乱
解决方案:
- 使用框架提供的布局回调进行自定义
- 注意适配不同屏幕尺寸和横竖屏切换
- 避免直接修改框架内部视图的frame
// 正确的布局自定义方式
imagePicker.photoPickerPageDidLayoutSubviewsBlock = ^(UICollectionView *collectionView, UIView *bottomToolBar, UIButton *previewButton, UIButton *originalPhotoButton, UILabel *originalPhotoLabel, UIButton *doneButton, UIImageView *numberImageView, UILabel *numberLabel, UIView *divideLine) {
// 适配安全区域
CGFloat bottomInset = [TZCommonTools tz_safeAreaInsets].bottom;
bottomToolBar.frame = CGRectMake(0, [UIScreen mainScreen].bounds.size.height - 49 - bottomInset, [UIScreen mainScreen].bounds.size.width, 49 + bottomInset);
// 重新布局按钮位置
doneButton.frame = CGRectMake([UIScreen mainScreen].bounds.size.width - 15 - 80, 7, 80, 35);
};
4.3 最佳实践总结
-
权限处理:
- 提前请求权限,避免使用时才弹窗
- 处理权限被拒绝的情况,提供友好提示
-
性能优化:
- 根据实际需求设置合理的
maxImagesCount - 列表滑动时暂停图片加载,停止滑动后恢复
- 大图预览时注意内存释放
- 根据实际需求设置合理的
-
用户体验:
- 提供清晰的选择状态反馈
- 操作耗时较长时显示加载指示器
- 支持横屏模式,提升iPad体验
-
兼容性处理:
- 适配不同iOS版本的特性差异
- 测试各种权限组合场景
- 处理特殊资源(如GIF、Live Photo、iCloud照片)
五、总结与展望
TZImagePickerController作为一款成熟的媒体选择框架,凭借其全面的功能、良好的性能和高度的可定制性,成为iOS开发中媒体选择功能的首选解决方案。它不仅解决了原生组件的功能限制,还提供了丰富的扩展接口,满足各种定制需求。
主要优势回顾
- 功能全面:支持图片/视频多选、预览、裁剪、原图选择等核心功能
- 易于集成:提供多种集成方式,API设计简洁直观
- 高度可定制:几乎所有UI元素都可以自定义,满足不同App风格需求
- 性能优化:针对图片加载、内存管理等关键问题进行专项优化
- 持续维护:活跃的社区支持和版本更新,及时适配新系统
未来发展方向
- Swift版本迁移:目前框架主要使用Objective-C编写,未来可能提供Swift版本
- SwiftUI支持:提供SwiftUI组件,适应iOS开发新趋势
- 功能扩展:增加图片编辑、滤镜等高级功能
- ARKit集成:结合AR技术提供更丰富的媒体交互体验
通过本文的介绍,相信开发者已经对TZImagePickerController有了深入了解。无论是快速集成基本功能,还是深度定制满足特殊需求,这款框架都能提供有力支持。建议开发者结合实际项目需求,充分利用框架提供的各项功能和扩展点,打造出色的媒体选择体验。
最后,感谢框架作者谭真(banchichen)的辛勤付出,以及社区贡献者的持续优化,让iOS开发者拥有如此强大的媒体选择工具。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



