TZPhotoPreviewController实现:iOS图片选择器的预览核心组件解析

TZPhotoPreviewController实现:iOS图片选择器的预览核心组件解析

【免费下载链接】TZImagePickerController 一个支持多选、选原图和视频的图片选择器,同时有预览、裁剪功能,支持iOS6+。 A clone of UIImagePickerController, support picking multiple photos、original photo、video, also allow preview photo and video, support iOS6+ 【免费下载链接】TZImagePickerController 项目地址: https://gitcode.com/gh_mirrors/tz/TZImagePickerController

引言:为什么需要独立的预览控制器?

在iOS开发中,图片选择功能是许多应用的基础模块。无论是社交分享、头像设置还是内容发布,用户都需要一个流畅直观的图片选择体验。你是否还在为系统UIImagePickerController的功能局限性而困扰?希望实现多选、原图预览、视频播放等高级功能?TZImagePickerController作为一个功能全面的第三方图片选择框架,其核心就在于TZPhotoPreviewController——这个为用户提供沉浸式媒体预览体验的组件。

读完本文,你将能够:

  • 理解TZPhotoPreviewController的架构设计与核心功能
  • 掌握复杂媒体预览界面的实现技巧
  • 学会处理图片选择、预览、裁剪的完整流程
  • 解决iCloud同步、原图选择等实际开发痛点

一、TZPhotoPreviewController架构概览

1.1 类定义与核心属性

TZPhotoPreviewController继承自UIViewController,主要负责媒体文件的全屏预览功能,支持图片、GIF和视频的预览,并提供选择、裁剪等交互操作。

@interface TZPhotoPreviewController : UIViewController

@property (nonatomic, strong) NSMutableArray *models;                  ///< 所有图片模型数组
@property (nonatomic, strong) NSMutableArray *photos;                  ///< 所有图片数组
@property (nonatomic, assign) NSInteger currentIndex;           ///< 用户点击的图片索引
@property (nonatomic, assign) BOOL isSelectOriginalPhoto;       ///< 是否返回原图
@property (nonatomic, assign) BOOL isCropImage;

/// 回调Block定义
@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

1.2 核心组件与层次结构

TZPhotoPreviewController的视图结构采用分层设计,主要包含以下组件:

mermaid

主要视图层次结构如下:

├── 主视图UIView
│   ├── UICollectionView (媒体预览区域)
│   ├── _naviBar (自定义导航栏)
│   │   ├── 返回按钮(_backButton)
│   │   ├── 选择按钮(_selectButton)
│   │   └── 索引标签(_indexLabel)
│   ├── _toolBar (底部工具栏)
│   │   ├── 完成按钮(_doneButton)
│   │   ├── 原图选择按钮(_originalPhotoButton)
│   │   └── 数量指示器(_numberImageView, _numberLabel)
│   └── 裁剪相关视图
│       ├── _cropBgView (裁剪背景)
│       └── _cropView (裁剪框)

二、初始化与配置流程

2.1 生命周期方法解析

TZPhotoPreviewController的初始化流程遵循标准的UIViewController生命周期,并在此基础上添加了自定义配置:

- (void)viewDidLoad {
    [super viewDidLoad];
    [TZImageManager manager].shouldFixOrientation = YES;
    // 获取导航控制器(TZImagePickerController)实例
    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];
}

2.2 关键组件配置

2.2.1 集合视图配置(UICollectionView)

UICollectionView是预览控制器的核心,用于展示多个媒体项并支持滑动切换:

- (void)configCollectionView {
    _layout = [TZCommonTools tz_rtlFlowLayout];
    _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"];
}
2.2.2 自定义导航栏与工具栏

由于系统导航栏无法满足沉浸式预览需求,TZPhotoPreviewController实现了自定义导航栏和工具栏:

- (void)configCustomNaviBar {
    _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];
    
    // 配置索引标签
    _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];
}

底部工具栏包含完成按钮、原图选择按钮等关键交互元素,其配置过程与导航栏类似。

三、媒体预览核心实现

3.1 集合视图数据源与代理

UICollectionView的数据源方法实现了媒体项的展示逻辑:

#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) {
        // GIF预览单元格
        cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"TZGifPreviewCell" forIndexPath:indexPath];
        TZGifPreviewCell *currentCell = (TZGifPreviewCell *)cell;
        currentCell.previewView.iCloudSyncFailedHandle = ^(id asset, BOOL isSyncFailed) {
            model.iCloudFailed = isSyncFailed;
            [weakSelf didICloudSyncStatusChanged:model];
        };
    } else {
        // 图片/GIF预览单元格
        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];
                    }];
                }
            }
        }];
        
        // iCloud同步失败处理
        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;
}

上述代码根据媒体类型(图片、GIF、视频)返回不同的预览单元格,实现了多类型媒体的统一预览接口。

3.2 滑动切换与索引更新

通过UIScrollViewDelegate实现滑动切换时的索引更新:

#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];
}

refreshNaviBarAndBottomBarState方法会根据当前索引更新导航栏和工具栏的状态,如选择按钮状态、索引标签文本等。

四、交互功能实现

4.1 选择功能

选择按钮的点击事件处理是预览控制器的核心交互之一:

- (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) {
        // 检查是否超过最大选择数量
        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;
        } 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]) {
                // 移除选中模型
                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];
                        break;
                    }
                }
                // 更新选中资产数组
                if (self.photos) {
                    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;
                        }
                    }
                    [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];
}

4.2 原图选择功能

原图选择按钮的实现考虑了文件大小计算、状态切换等细节:

- (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) {
            TZImagePickerController *_tzImagePickerVc = (TZImagePickerController *)self.navigationController;
            if (_tzImagePickerVc.selectedModels.count < _tzImagePickerVc.maxImagesCount && _tzImagePickerVc.showSelectBtn) {
                [self select:_selectButton];
            }
        }
    }
}

// 计算并显示原图大小
- (void)showPhotoBytes {
    TZAssetModel *model = _models[self.currentIndex];
    [[TZImageManager manager] getAssetSize:model.asset completion:^(NSString *sizeStr) {
        self->_originalPhotoLabel.text = sizeStr;
    }];
}

4.3 裁剪功能

裁剪功能的实现涉及裁剪框绘制、图片处理等复杂逻辑:

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

完成按钮点击时的裁剪处理:

- (void)doneButtonClick {
    // ... 其他逻辑 ...
    
    // 裁剪模式处理
    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];
        // 通过Block返回裁剪结果
        if (self.doneButtonClickBlockCropMode) {
            TZAssetModel *model = _models[self.currentIndex];
            self.doneButtonClickBlockCropMode(cropedImage,model.asset);
        }
    } 
    // ... 其他逻辑 ...
}

五、高级功能与优化

5.1 iCloud同步处理

针对iCloud照片的同步问题,实现了进度反馈和错误处理:

// 在单元格配置中设置iCloud同步失败处理
currentCell.previewView.iCloudSyncFailedHandle = ^(id asset, BOOL isSyncFailed) {
    model.iCloudFailed = isSyncFailed;
    [weakSelf didICloudSyncStatusChanged:model];
};

// 图片加载进度更新Block
[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];
            }];
        }
    }
}];

5.2 横竖屏适配

通过监听状态栏旋转通知实现布局自适应:

- (void)didChangeStatusBarOrientationNotification:(NSNotification *)noti {
    _offsetItemCount = _collectionView.contentOffset.x / _layout.itemSize.width;
}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    
    // 处理导航栏布局
    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);
    
    // 处理集合视图布局
    _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];
    
    // 处理工具栏布局
    CGFloat toolBarHeight = 44 + [TZCommonTools tz_safeAreaInsets].bottom;
    CGFloat toolBarTop = self.view.tz_height - toolBarHeight;
    _toolBar.frame = CGRectMake(0, toolBarTop, self.view.tz_width, toolBarHeight);
    
    // 重新配置裁剪视图
    [self configCropView];
    
    // 自定义布局回调
    if (_tzImagePickerVc.photoPreviewPageDidLayoutSubviewsBlock) {
        _tzImagePickerVc.photoPreviewPageDidLayoutSubviewsBlock(_collectionView, _naviBar, _backButton, _selectButton, _indexLabel, _toolBar, _originalPhotoButton, _originalPhotoLabel, _doneButton, _numberImageView, _numberLabel);
    }
}

六、总结与最佳实践

6.1 TZPhotoPreviewController的设计亮点

  1. 分层架构:将导航栏、预览区、工具栏等拆分为独立模块,职责清晰
  2. 多类型支持:统一接口处理图片、GIF、视频等多种媒体类型
  3. 交互体验优化:添加选择动画、平滑过渡等细节处理
  4. 可扩展性设计:通过Block回调和配置Block支持自定义需求
  5. 性能优化:实现图片懒加载、iCloud同步状态管理等

6.2 实际开发中的注意事项

  1. 内存管理:注意大图片预览时的内存占用,及时释放不再需要的资源
  2. 错误处理:完善iCloud同步失败、权限不足等异常场景的用户提示
  3. 性能优化:对于大量图片预览,考虑实现预加载和回收机制
  4. 用户体验:保持交互一致性,添加适当的加载状态和反馈

6.3 未来改进方向

  1. Swift重构:目前代码为Objective-C实现,可考虑迁移到Swift
  2. 暗黑模式支持:增加对iOS暗黑模式的完整支持
  3. 视频编辑功能:扩展视频预览为完整的视频编辑功能
  4. 性能监控:添加性能指标收集,优化卡顿问题

TZPhotoPreviewController作为TZImagePickerController的核心组件,展示了如何构建一个功能完善、用户体验优秀的媒体预览系统。通过分层设计、接口抽象和细致的交互处理,实现了复杂媒体预览场景的优雅解决方案。开发者可以借鉴其设计思想,构建更强大的媒体处理功能。

要开始使用TZImagePickerController,请克隆仓库:git clone https://gitcode.com/gh_mirrors/tz/TZImagePickerController

【免费下载链接】TZImagePickerController 一个支持多选、选原图和视频的图片选择器,同时有预览、裁剪功能,支持iOS6+。 A clone of UIImagePickerController, support picking multiple photos、original photo、video, also allow preview photo and video, support iOS6+ 【免费下载链接】TZImagePickerController 项目地址: https://gitcode.com/gh_mirrors/tz/TZImagePickerController

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值