CBImagePicker 图片多选框架的另外一个选择

本开源库基于iOS8中PhotoKit框架制做,因此暂不支持iOS8如下版本,请谅解。另,在iOS10中,使用photoKit框架的应用可能会出现crash,但这个问题相信很快会被官方修复,请无需担忧。git

效果图github

实际截图spring

如下是原Idea做者的Dribble:Photo Picker Interaction 地址和本人的GitHub:CBImagePicker 地址,但愿你们点击支持,再次感谢。数组

下面咱们来仔细的分析,作这个库的完整过程。浏览器

第一部分 Category

<span id="jump_1">整个库的制做用到了大量的坐标计算,因此咱们颇有必要写一个category来简化这一部分操做,我针对UIView的<u>UIView+CBAddition</u>类库和针对UIImage的<u>UIImage+CBAddition</u>都是为了简化这一部份内容而作的工做。</span>app

// Getter
- (CGFloat)originLeft {
    return self.frame.origin.x;
}
// Setter
- (void)setOriginLeft:(CGFloat)originLeft {
    if (!isnan(originLeft)) {
        self.frame = CGRectMake(originLeft, self.originUp, self.sizeWidth, self.sizeHeight);
    }
}

大都以设置属性后自定义Getter和Setter的方式来进行,更详细代码请点击查看。框架

第二部分 ImagePicker(图片选择)

这个部分咱们分小节来说。fetch

TitleView

TitleView分为两个View,第一个是Label,第二个则是UIImageView,这里使用了苹果原生NSLayoutConstraint来添加约束去限制两个View之间的位置和尺寸的关系,使用方法以下:动画

+(instancetype)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1 relatedBy:(NSLayoutRelation)relation toItem:(nullable id)view2 attribute:(NSLayoutAttribute)attr2 multiplier:(CGFloat)multiplier constant:(CGFloat)c;

参数说明:ui

view1:设置的目标视图 attr1:设置的目标视图的属性 relation:目的视图和参照视图之间的关系 view2:设置的参照视图 attr2:参照视图的参照属性 multiplier:目标视图属性和参照视图属性倍值 c:目标视图属性和参照视图属性差别值

CollctionView

相册获取

collection这个部分,主要是图片数据的申请,这里首先使用了PhotoKit来申请全部的相册列表,代码以下:

PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
    
    PHFetchResult *topLevelUserCollections = [PHCollectionList fetchTopLevelUserCollectionsWithOptions:nil];
    
    [smartAlbums enumerateObjectsUsingBlock:^(PHAssetCollection  * _Nonnull collection, NSUInteger idx, BOOL *stop) {
        PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:collection options:nil];

        if (fetchResult.count > 0) {
            [self.assetsGroupArray addObject:collection];
        }
    }];
    
    [topLevelUserCollections enumerateObjectsUsingBlock:^(PHAssetCollection  * _Nonnull collection, NSUInteger idx, BOOL *stop) {
        PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:collection options:nil];

        if (fetchResult.count > 0) {
            [self.assetsGroupArray addObject:collection];
        }
    }];

这里使用了一个数组存储所取到的全部相册列表,相册信息存储对象为PHAssetCollection

相册图片获取

而当咱们取到相册信息以后,要从对应相册中取到咱们所须要显示的图片,那么这里咱们根据index从上面的数组中取出咱们所须要的对应相册,遍历改相册,再利用方法requestImageForAsset来请求图片信息,方法以下:

- (PHImageRequestID)requestImageForAsset:(PHAsset *)asset targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(nullable PHImageRequestOptions *)options resultHandler:(void (^)(UIImage *__nullable result, NSDictionary *__nullable info))resultHandler;

参数说明:
asset:单个的图片数据 targetSize:请求的图片尺寸 cotentMode:尺寸拉伸方式 options:附加信息 resultHandler:结果回调

此处自定义了一个头部视图,添加方法以下:

注册registerClass

[_imageCollectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"UICollectionReusableView"];

设置dataSource

- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
    UICollectionReusableView *collectionHeardView;
    
    if (kind == UICollectionElementKindSectionHeader){
        collectionHeardView = (UICollectionReusableView *)[collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"UICollectionReusableView" forIndexPath:indexPath];
        
        collectionHeardView.backgroundColor = [UIColor clearColor];
        
        [collectionHeardView addSubview:_horizontalScrollView];
    }
     return collectionHeardView;
}

当进行图片选择时,头部视图会向下滑动,而当开始取消选择图片,且选择图片为空时,头部视图向上滑动,这一部分操做,我使用了UIView动画来制做,方法内容以下:

+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(7_0);

参数说明:

duration:持续时间 delay:延时时间 dampingRatio:spring系数,从0-1,数值越小,效果越强 velocity:初始速度 options:动画选项 animations:动画block completion:动画结束后的回调

animations block中设置了collectionVieworiginUpsizeHeightcontentOffset,做为最终状态,而上面那个方法则会自动完成中间状态的计算和设置。

CollectionViewCell

在cell里面,一样使用了约束和UIView动画,在动画中使用了View的transform属性,点击时设置cell的transform为CGAffineTransformMakeScale(0.9, 0.9),即缩小状态,取消点击时设置cell的transform为CGAffineTransformMakeScale(1, 1),即正常大小,造成一种强烈的选中效果。

AlbumTableView

这里自定义了一个下拉列表,整个下拉列表的制做并无困难的地方,一样使用了UIView动画来进行frame上的位移设置。使用了requestImageForAsset:的方法进行缩略图请求。

第三部分 ImageBrowser(图片浏览)

Simulator Screen Shot 2016年8月7日 下午5.08.56.png

动画显示

这个浏览器,我写了这样的一个present的方法。咱们从这个方法入手。

/**
 *  Present the browser.
 *
 *  @param fromView    the fromeView.
 *  @param toContainer the view which will contain the browser.
 *  @param animated    animated bool.
 *  @param completion  completion block.
 */
- (void)presentFromImageView:(UIView *)fromView
                 toContainer:(UIView *)toContainer
                    animated:(BOOL)animated
                  completion:(void (^)(void))completion;

参数设置:

fromView:经过该View的绝对位置,咱们取到初始变化的Bounds toContainer:浏览器所add上去的视图 animated:动画选择 completion完成回调

下面的两个动画方法,分别用来作浏览器的显示和隐藏动效,结合fromView的bounds值,能够作到视图从所点击处的View变化而来的效果。点击查看更多代码。

显示动画:

- (void)showWithAnimated:(BOOL)animated
                    cell:(CBImageScrollViewCell *)cell
              completion:(void (^)(void))completion;

隐藏动画:

- (void)dismissAnimated:(BOOL)animated
             completion:(void (^)(void))completion;

第一层ScrollView

Simulator Screen Shot 2016年8月7日 下午5.10.35.png

这个浏览器使用了两层的ScrollView,第一层是用来盛放第二层的ScrollView,起到了滑动选择不一样图片的效果,第一层的ScrollView的contentSize经过所传入的图片数组动态改变。

第二层ScrollView

第二层的ScrollView起到了做为相似cell的做用,咱们简单的经过官方的一个方法:

- (nullable UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView;

传入一个ImageView便可自动完成二指缩放的效果,很是方便。但咱们仍是须要本身处理单击,双击,长按和双击事件,下面咱们来看这一部份内容。

单击,双击,长按和双击手势的处理

咱们先依次添加手势,记得要添加[singleTap requireGestureRecognizerToFail:doubleTap];保证双击手势和单击手势之间的手势冲突获得解决。

UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
    
    doubleTap.delegate = self;
    
    doubleTap.numberOfTapsRequired = 2;
    
    [doubleTap requireGestureRecognizerToFail:doubleTap];
    
    [self addGestureRecognizer:doubleTap];
    
    UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismiss:)];
    
    singleTap.delegate = self;
    
    [singleTap requireGestureRecognizerToFail:doubleTap];
    
    [self addGestureRecognizer:singleTap];
    
    UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
    
    longPress.delegate = self;
    
    [self addGestureRecognizer:longPress];
    
    UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGesture:)];
    
    [self addGestureRecognizer:panGesture];

单击手势咱们直接使用block来执行dismiss方法便可,这里不表。双击手势呢,咱们用下面的方法来进行缩放,主要是计算rect和scale。

CGPoint touchPoint = [sender locationInView:imageCell.imageView];
        
        CGFloat newZoomScale = imageCell.maximumZoomScale;
        
        CGFloat xsize = imageCell.sizeWidth / newZoomScale;
        
        CGFloat ysize = imageCell.sizeHeight / newZoomScale;
        
        [imageCell zoomToRect:CGRectMake(touchPoint.x - xsize / 2, touchPoint.y - ysize / 2, xsize, ysize) animated:YES];

长按手势我是直接调用系统的popoverPresentationController:方法,能够实现分享和保存等功能。

UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:@[imageCell.imageView.image] applicationActivities:nil];
    
    if ([activityViewController respondsToSelector:@selector(popoverPresentationController)]) {
        activityViewController.popoverPresentationController.sourceView = self;
    }
    
    [self.viewController presentViewController:activityViewController animated:YES completion:nil];

Pan手势相对复杂,利用velocityInView:来取得滑动速度,利用locationInView:来取得滑动的初始位置和最终位置。主要的处理逻辑是进行初始位置和最终位置进行对比,若是数值较大,就执行动画并进行dismiss,而若是数值较小就取消滑动,恢复初始位置。而一旦初始速度很大的时候,就直接执行动画并dismiss,判断用户的行为,并做出操做。
这一部份内容较多,请直接点击查看

- (void)panGesture:(UIPanGestureRecognizer *)sender;

第四部分 总结

这个库的制做过程基本如上所述,但仍有不少细节难以细讲,请谅解,请有意向的朋友下载demo查看,若有疑问或者BUG,欢迎留言或者提出issue,谢谢。

相关文章
相关标签/搜索