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

相關文章
相關標籤/搜索