本开源库基于iOS8中PhotoKit框架制做,因此暂不支持iOS8如下版本,请谅解。另,在iOS10中,使用photoKit框架的应用可能会出现crash,但这个问题相信很快会被官方修复,请无需担忧。git
效果图github
实际截图spring
如下是原Idea做者的Dribble:Photo Picker Interaction 地址和本人的GitHub:CBImagePicker 地址,但愿你们点击支持,再次感谢。数组
下面咱们来仔细的分析,作这个库的完整过程。浏览器
<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的方式来进行,更详细代码请点击查看。框架
这个部分咱们分小节来说。fetch
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:目标视图属性和参照视图属性差别值
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中设置了collectionView的originUp,sizeHeight和contentOffset,做为最终状态,而上面那个方法则会自动完成中间状态的计算和设置。
在cell里面,一样使用了约束和UIView动画,在动画中使用了View的transform属性,点击时设置cell的transform为CGAffineTransformMakeScale(0.9, 0.9),即缩小状态,取消点击时设置cell的transform为CGAffineTransformMakeScale(1, 1),即正常大小,造成一种强烈的选中效果。
这里自定义了一个下拉列表,整个下拉列表的制做并无困难的地方,一样使用了UIView动画来进行frame上的位移设置。使用了requestImageForAsset:的方法进行缩略图请求。
这个浏览器,我写了这样的一个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,第一层是用来盛放第二层的ScrollView,起到了滑动选择不一样图片的效果,第一层的ScrollView的contentSize经过所传入的图片数组动态改变。
第二层的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,谢谢。