本開源庫基於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,謝謝。