iOS_“图片浏览选择”功能的编写思路

最近重新开始练习iOS开发,找感觉。

先做个简单的图片浏览选择功能。不用管是难还是简单,先实现一下。


一、步骤概述

包含三个步骤:

  • 创建页面:图片选择页(collectionView),图片预览页
  • 处理数据:在页面间传递图片数据集或者单张图片,用系统的Photo库API获取图片
  • 实现动画:各个手势(GestureRecognizer)结合数值的计算

练习重点:

  • 各种手势(GestureRecognizer)的运用

二、具体实现

创建页面

首先是创建相应的页面。这个作为最基础的部分还是在此略过。总共有三个页面:

  • 入口页

  • 选择页

  • 预览页

  • 选择图片后的入口页

处理数据

然后是处理图片数据,在页面间传递图片数据。

给图片加上点击事件,由于UIImageView默认不能交互,所以需要把交互打开:

在入口页导入系统的库,在图片的点击事件响应方法里,调用Photo库“查看是否有权限”的API:

if (status == PHAuthorizationStatusNotDetermined) {
    [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
        if (status == PHAuthorizationStatusAuthorized) {     
            // 有权限读取照片
            [self performLoadPhotoAssets]; // 自定义方法
        }
    }];
} else if (status == PHAuthorizationStatusAuthorized) {
    // 有权限读取照片
    [self performLoadPhotoAssets]; // 自定义方法
}
复制代码

当判断到有权限查看相册时,则执行获取图片的方法[self performLoadPhotoAssets]

  • 先从系统相册获取图片assetsArray
NSMutableArray *assetsArray = [NSMutableArray array];

PHFetchOptions *options = [[PHFetchOptions alloc] init];
options.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];

PHFetchResult *fetchResult = [PHAsset fetchAssetsWithOptions:options];

[fetchResult enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    [assetsArray addObject:obj];
}];
复制代码
  • 然后传递到下一个UICollectionView界面
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];

layout.itemSize = CGSizeMake(100, 100);

CGFloat space = (self.view.bounds.size.width - (4 * 100)) / 5;
if (space < 5) space = 5;
layout.minimumInteritemSpacing = space;

layout.minimumLineSpacing = 15;

layout.sectionInset = UIEdgeInsetsMake(15, space, 15, space);
    
YLPhotoSelectionViewController *photoVC = [[YLPhotoSelectionViewController alloc] initWithCollectionViewLayout:layout];
photoVC.assetsArray = [assetsArray copy];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:photoVC];
[self presentViewController:nav animated:YES completion:nil];
复制代码

在这个UICollectionView界面,也就是图片选择页,在接口处自然是定义好属性:

@interface YLPhotoSelectionViewController : UICollectionViewController

@property(nonatomic, copy)  NSArray * _Nullable assetsArray;

@end
复制代码

这个collectionView没什么好多说的,实现collectionView的dataSource和delegate即可,唯一要注意的是在CollectionViewCell里继续引入并调用Photo库解码PHAseet:

  • cell里接受数据的属性
@interface YLPhotoCollectionViewCell : UICollectionViewCell

@property(nonatomic, strong) PHAsset *asset;

@end
复制代码
  • asset属性的set里解码PHAsset
- (void)setAsset:(PHAsset *)asset {
    
    __weak __typeof(self) weakSelf = self;
    [[PHImageManager defaultManager] requestImageForAsset:asset 
        targetSize:self.photoImageView.bounds.size 
        contentMode:PHImageContentModeAspectFill 
        options:NULL 
        resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {

        if (result) {
            // 给cell里的图片视图ImageView赋值
            weakSelf.photoImageView.image = result;
        }
        
    }];
    
}
复制代码

然后接着在collectionView的点击代理里,把整个图片数组和被选择图片的索引值传递给图片预览页:

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
    NSInteger row = indexPath.row;
    // 图片预览页
    YLPhotoPreviewViewController *previewVC = [[YLPhotoPreviewViewController alloc] init];
    // 传递图片和图片的索引值
    previewVC.photoAssetsArray = self.assetsArray;
    previewVC.photoIndex = row;
    [self.navigationController pushViewController:previewVC animated:YES];
}
复制代码

传递整个图片数组和被选择图片的索引值,而不是只传递被选择的图片,这样做目的是给轻扫手势切换图片提供相应的数据,通过对索引值的加和减来实现。

同样地,在图片预览页面的接口定义接收数据的属性:

@interface YLPhotoPreviewViewController : UIViewController

@property(nonatomic, copy) NSArray  * _Nullable photoAssetsArray;
@property(nonatomic, assign) NSInteger photoIndex;

@end
复制代码

图片的呈现很简单,一张铺满屏幕的UIImageView,同样用在cell里的Photo库的解码方法,把数据赋值给UIImageView,注意这里的图片是通过传递过来的两个属性组合取出:

PHAsset *asset = self.photoAssetsArray[self.photoIndex];
复制代码

实现动画

最后是这次的重点,实现各种手势操作:

  • 长按,UILongPressGestureRecognizer
  • 轻扫,UISwipeGestureRecognizer
  • 缩放,UIPinchGestureRecognizer
  • 旋转,UIRotationGestureRecognizer
  • 滑动,UIPanGestureRecognizer
  • 屏幕边缘滑动,UIScreenEdgePanGestureRecognizer

这里重点记录一下缩放、旋转和平移这三个手势实现时,都需要有处理的数据,全部代码在GitHub里。

首先它们都需要记录一个最终数值:

// 最终照片的比例
@property (assign, nonatomic) CGFloat finalPinchScale; 
// 最终旋转角度
@property (assign, nonatomic) CGFloat finalRotaionAngle; 
// 最终平移的位置
@property (assign, nonatomic) CGPoint finalPanTranslastion; 
复制代码

其次都需要记录上次缩放、旋转或平移的值:

// 上次缩放的比例
static CGFloat _lastPinchScale = 1; 

// 上一次旋转的角度
static CGFloat _lastRotaionAngle = 0; 

// 上次平移的位置
static CGPoint _lastTranslation = {.x = 0, .y = 0}; 
复制代码

然后是根据操作的状态state来计算数值,以缩放的代码为例:

if (sender.state == UIGestureRecognizerStateChanged) {
        // 改变照片的缩放比例
        self.finalPinchScale = _lastPinchScale * currentPinchScale;
        // 将finalPinchScaley应用在照片上
        [self transformForImage]; // 自定义方法
    } else if (sender.state == UIGestureRecognizerStateEnded) {
        // 更新lastPinchScale的数值到本次缩放的比例
        _lastPinchScale = _lastPinchScale *currentPinchScale;
    }
复制代码

然后就是执行关键的自定义方法[self transformForImage]

- (void)transformForImage {
    // 把最终值传入相应的transform类型
    CGAffineTransform scaleTransform = CGAffineTransformMakeScale(self.finalPinchScale, self.finalPinchScale);
    CGAffineTransform rotationTransform = CGAffineTransformRotate(scaleTransform, self.finalRotaionAngle);
    CGAffineTransform translationTransform = CGAffineTransformTranslate(rotationTransform, _finalPanTranslastion.x, _finalPanTranslastion.y);
    self.previewImageView.transform = translationTransform;
}
复制代码

这里是最终的自定义方法,可以看到不同类型的transform是需要叠加在一起,然后最后一个transform类型赋值给视图的transform属性。

最后要主要,当有多个容易冲突的手势操作时,这些手势需要实现相应的代理方法:

// 缩放、旋转、平移手势各自签署协议
pinchGesture.delegate = self;
rotationGesture.delegate = self;
panGesture.delegate = self;

// “同时生效”的代理方法
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    return YES;
}
复制代码

然后就可以操作手势来实现缩放、旋转等功能了:

三、总结

iOS开发很大一部分工作是熟悉使用系统提供的强大API,然后做适度的封装。

至于编程的另一部分——算法,数据结构,编程范式等,不分后端还是前端,都是编程者需要结合理论和实践不断提高的东西。

你可能感兴趣的:(iOS_“图片浏览选择”功能的编写思路)