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

iOS · cbangchen · 于 发布 · 1634 次阅读
96

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

效果图

实际截图

以下是原Idea作者的Dribble:Photo Picker Interaction 地址和本人的GitHub:CBImagePicker 地址,希望大家点击支持,再次感谢。

下面我们来仔细的分析,做这个库的完整过程。

第一部分 Category

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

// 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(图片选择)

这个部分我们分小节来讲。

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;

参数说明:

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,谢谢。

本帖已被设为精华帖!
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册