1. 程式人生 > >iOS 瀏覽相簿功能實現 —— HERO部落格

iOS 瀏覽相簿功能實現 —— HERO部落格

iOS 瀏覽相簿功能實現,可縮放,畫筆標記,快取圖片,記錄下載進度。

首先看一下效果圖:

   

  

下面簡述下主要思路及相關程式碼:

HWPhotoVC(控制器,用collcetView展示縮圖,點選cell展示大圖片):

#import <UIKit/UIKit.h>

@interface HWPhotoVC : UIViewController

@end

/*** ---------------分割線--------------- ***/

#import "HWPhotoVC.h"
#import "HWPhotoView.h"
#import "HWNavBar.h"
#import "HWLoadingView.h"
#import "HWPhotoManger.h"

#define KItemW 111
#define KItemH 111
#define KMargin 10

@interface HWPhotoVC ()<UICollectionViewDelegate, UICollectionViewDataSource>

@property (nonatomic, strong) NSArray *photos;
@property (nonatomic, strong) NSArray *thumPhotos;
@property (nonatomic, strong) UICollectionView *collectionView;
@property (nonatomic, weak) HWPhotoView *photoView;
@property (nonatomic, weak) HWLoadingView *loadingView;

@end

@implementation HWPhotoVC

static NSString *const reuseIdentifier = @"HWPhotosCell";

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor whiteColor];
    
    //建立控制元件
    [self creatControl];
}

- (void)creatControl
{
    CGFloat navBarH = 64.0f;
    
    //導航條
    HWNavBar *navBar = [[HWNavBar alloc] initWithFrame:CGRectMake(0, 0, KMainW, navBarH)];
    navBar.title = @"相簿";
    [navBar addBackButtonWithAction:@selector(photoVCBackAction)];
    [self.view addSubview:navBar];
    
    //展示檢視佈局
    UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
    flowLayout.sectionInset = UIEdgeInsetsMake(KMargin, KMargin, KMargin, KMargin);
    
    //展示檢視
    _collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, navBarH, KMainW, KMainH - navBarH) collectionViewLayout:flowLayout];
    _collectionView.backgroundColor = [UIColor clearColor];
    _collectionView.showsVerticalScrollIndicator = NO;
    _collectionView.dataSource = self;
    _collectionView.delegate = self;
    [self.view addSubview:_collectionView];
    
    //載入檢視
    HWLoadingView *loadingView = [[HWLoadingView alloc] initWithFrame:CGRectMake(0, 0, KMainW, KMainH)];
    loadingView.progress = 0.0f;
    weakify(self);
    loadingView.back = ^() {
        strongify(weakSelf);
        [strongSelf photoVCBackAction];
    };
    [self.view addSubview:loadingView];
    self.loadingView = loadingView;
    
    //註冊標識
    [_collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:reuseIdentifier];
    
    //獲取資訊
    [self getInfo];
}

- (HWPhotoView *)photoView
{
    if (!_photoView) {
         HWPhotoView *photoView = [[HWPhotoView alloc] initWithFrame:CGRectMake(0, 0, KMainW, KMainH)];
        photoView.photos = _photos;
        photoView.thumPhotos = _thumPhotos;
        weakify(self);
        photoView.back = ^(CGRect imageViewFrame, NSInteger item) {
            strongify(weakSelf);
            [strongSelf photoDismissAnimationWithFrame:imageViewFrame item:item];
        };
        [self.view addSubview:photoView];
        _photoView = photoView;
    }
    
    return _photoView;
}

- (void)photoDismissAnimationWithFrame:(CGRect)frame item:(NSInteger)item
{
    //根據item寬度計算每行可顯示的個數
    int colCount = (int)((KMainW - KMargin) / (KItemW + KMargin));
    
    //建立圖片並設定初始frame
    UIImageView __block *imageView = [[UIImageView alloc] initWithFrame:frame];
    
    imageView.image = [UIImage imageWithContentsOfFile:[HWPhotoManger getImagePathWithImageName:_photos[item]]];
    [self.view addSubview:imageView];
    
    //點選圖片時左上第一張是否能完整顯示
    BOOL isIntact = (int)_collectionView.contentOffset.y % (KMargin + KItemH) <= KMargin;
    
    //點選圖片時左下第一張是否能完整顯示
    BOOL isIntactBottom = (int)(_collectionView.contentOffset.y + _collectionView.bounds.size.height) % (KMargin + KItemH) <= KMargin;
    
    //點選圖片時左上第一張圖片item
    NSInteger currentFirstItem = (int)_collectionView.contentOffset.y / (KMargin + KItemH) * colCount;
    
    //點選圖片時左下第一張圖片item
    NSInteger currentBottomFirstItem = (int)(_collectionView.contentOffset.y + _collectionView.bounds.size.height) / (KMargin + KItemH) * colCount;
    if (isIntactBottom) currentBottomFirstItem = currentBottomFirstItem - colCount;
    
    //檢視可以展示的最大item數量
    NSInteger showMaxItemCount = currentBottomFirstItem - currentFirstItem + colCount;
    
    //當結束點選圖片在最上一行,並且圖片不能完整顯示時,下移CollectionView使圖片完整顯示
    if (!isIntact && (item - currentFirstItem < colCount && item - currentFirstItem > - 1)) {
        [_collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:item inSection:0] atScrollPosition:UICollectionViewScrollPositionTop animated:NO];
    }
    
    //當結束點選圖片在最下一行,並且圖片不能完整顯示時,上移CollectionView使圖片完整顯示
    if (!isIntactBottom && (item - currentFirstItem > showMaxItemCount - colCount - 1 && item - currentFirstItem < showMaxItemCount)) {
        CGFloat movePadding = KItemH - ((int)(_collectionView.contentOffset.y + _collectionView.bounds.size.height) % (KItemH + KMargin) - KMargin);
        [_collectionView setContentOffset:CGPointMake(0, _collectionView.contentOffset.y + movePadding)];
    }
    
    //當結束點選item小於左上第一張圖片時,下移CollectionView至選中行為最上一行
    if (item < currentFirstItem) {
        [_collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:item inSection:0] atScrollPosition:UICollectionViewScrollPositionTop animated:NO];
    }
    
    //當結束點選item大於右下(左上第一張圖片item + 可最大展示item數)時,上移CollectionView至選中行為最下一行
    if (item > currentFirstItem + showMaxItemCount - 1) {
        CGFloat movePadding = KItemH - ((int)(_collectionView.contentOffset.y + _collectionView.bounds.size.height) % (KItemH + KMargin) - KMargin) + (KItemH + KMargin) * (item / colCount - currentBottomFirstItem / colCount);
        if (isIntactBottom) movePadding = movePadding - (KItemH + KMargin);
        [_collectionView setContentOffset:CGPointMake(0, _collectionView.contentOffset.y + movePadding)];
    }
    
    //加延時確保獲取到的cellFrame是collectionView移動之後的
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        //獲取圖片縮小後的frame
        UICollectionViewCell *cell = [_collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:item inSection:0]];
        
        //消失動畫
        [UIView animateWithDuration:0.25f animations:^{
            imageView.frame = [self.view convertRect:cell.frame fromView:_collectionView];
        }completion:^(BOOL finished) {
            [imageView removeFromSuperview];
            imageView = nil;
        }];
    });
}

- (void)getInfo
{
    [_loadingView show];
        
    //網路請求獲取相簿相關資訊這裡省略了,直接模擬獲取到圖片陣列、縮圖陣列、相簿版本號、相簿id
    NSArray *urlArray = @[@"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/axUD1TxyazB3vSFQkDQLn5M0QPSZQB5WWLaDW0V5Xj4!/b/dGkBAAAAAAAA&bo=cQSAAgAAAAAFB9M!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/D0m2YadD0efgMEFUzxDI.VP54dmoNLE6jXG4q3Hu39Q!/b/dGkBAAAAAAAA&bo=AASAAgAAAAAFB6I!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/ajOssbYw05gPzrifwEeHYJQm63nINjpnDNk3lXV7utQ!/b/dGkBAAAAAAAA&bo=cgSAAgAAAAAFANc!&rf=viewer_4", @"http://a1.qpic.cn/psb?/V13GwxkT2AqZnq/Y2bx7nPZOo*xK9CnWnEuMOIvnAOeMDLnvFGuE325tSg!/b/dGgBAAAAAAAA&bo=FQIsAQAAAAAFABk!&rf=viewer_4", @"http://a1.qpic.cn/psb?/V13GwxkT2AqZnq/yJZVj17bdMMR3sCLT1YWwNFKgK057vNjw0zL1IH6FTg!/b/dGgBAAAAAAAA&bo=4AEsAQAAAAAFAO8!&rf=viewer_4", @"http://a1.qpic.cn/psb?/V13GwxkT2AqZnq/HJUjYRFTxYElxuIP00GzRWHoymfiyylKmisGK9gbDUc!/b/dGgBAAAAAAAA&bo=VQOAAgAAAAAFAPc!&rf=viewer_4", @"http://a1.qpic.cn/psb?/V13GwxkT2AqZnq/lNbl.keTXojot43.i4eLl5EDrrrhim3Wxo225dBOQTg!/b/dGgBAAAAAAAA&bo=AASAAgAAAAAFAKU!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/iB6SdannBkn0b8SVuTSpS6H.Mttkwj*YF.i58ge7bp4!/b/dGkBAAAAAAAA&bo=AASAAgAAAAAFAKU!&rf=viewer_4", @"http://a1.qpic.cn/psb?/V13GwxkT2AqZnq/Li0El90EOY0E6*Haw4qtPP5i2C8dtUxxfL8RfvTTyrs!/b/dGgBAAAAAAAA&bo=AASAAgAAAAAFAKU!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/Vtg11LWJ*osQp*WkGRAwfAi89j3Bbk5v3EZM8b3H2q0!/b/dGkBAAAAAAAA&bo=wQOAAgAAAAAFAGM!&rf=viewer_4", @"http://a1.qpic.cn/psb?/V13GwxkT2AqZnq/K2fNvk13QaUO4punmxpIAL1kZFDy4AeNRkfZmdItWyM!/b/dFYBAAAAAAAA&bo=wgH9AgAAAAAFBxg!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/7RzzUgvozabfIqwfX8rv4utG9aT3n2e3Asi4zzxvnmI!/b/dGkBAAAAAAAA&bo=gAI1AwAAAAAFB5A!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/eXRrjOn0pBOFf.7m3yaXyEztQEkoGtYcg8EBoJpaO0U!/b/dGkBAAAAAAAA&bo=yADIAAAAAAAFByQ!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/pw5plnw3LsocGQlRQSgrnZCPy2KV5LvWb*Nz3UNCvJQ!/b/dGkBAAAAAAAA&bo=AASAAgAAAAAFB6I!&rf=viewer_4", @"http://a1.qpic.cn/psb?/V13GwxkT2AqZnq/7EO.WlDqMcvVBYcc27XFLNplGjwdYx2p56TZNwc.Tn4!/b/dFYBAAAAAAAA&bo=ZwJnAgAAAAAFByQ!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/axUD1TxyazB3vSFQkDQLn5M0QPSZQB5WWLaDW0V5Xj4!/b/dGkBAAAAAAAA&bo=cQSAAgAAAAAFB9M!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/D0m2YadD0efgMEFUzxDI.VP54dmoNLE6jXG4q3Hu39Q!/b/dGkBAAAAAAAA&bo=AASAAgAAAAAFB6I!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/ajOssbYw05gPzrifwEeHYJQm63nINjpnDNk3lXV7utQ!/b/dGkBAAAAAAAA&bo=cgSAAgAAAAAFANc!&rf=viewer_4", @"http://a1.qpic.cn/psb?/V13GwxkT2AqZnq/Y2bx7nPZOo*xK9CnWnEuMOIvnAOeMDLnvFGuE325tSg!/b/dGgBAAAAAAAA&bo=FQIsAQAAAAAFABk!&rf=viewer_4", @"http://a1.qpic.cn/psb?/V13GwxkT2AqZnq/yJZVj17bdMMR3sCLT1YWwNFKgK057vNjw0zL1IH6FTg!/b/dGgBAAAAAAAA&bo=4AEsAQAAAAAFAO8!&rf=viewer_4", @"http://a1.qpic.cn/psb?/V13GwxkT2AqZnq/HJUjYRFTxYElxuIP00GzRWHoymfiyylKmisGK9gbDUc!/b/dGgBAAAAAAAA&bo=VQOAAgAAAAAFAPc!&rf=viewer_4", @"http://a1.qpic.cn/psb?/V13GwxkT2AqZnq/lNbl.keTXojot43.i4eLl5EDrrrhim3Wxo225dBOQTg!/b/dGgBAAAAAAAA&bo=AASAAgAAAAAFAKU!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/iB6SdannBkn0b8SVuTSpS6H.Mttkwj*YF.i58ge7bp4!/b/dGkBAAAAAAAA&bo=AASAAgAAAAAFAKU!&rf=viewer_4", @"http://a1.qpic.cn/psb?/V13GwxkT2AqZnq/Li0El90EOY0E6*Haw4qtPP5i2C8dtUxxfL8RfvTTyrs!/b/dGgBAAAAAAAA&bo=AASAAgAAAAAFAKU!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/Vtg11LWJ*osQp*WkGRAwfAi89j3Bbk5v3EZM8b3H2q0!/b/dGkBAAAAAAAA&bo=wQOAAgAAAAAFAGM!&rf=viewer_4", @"http://a1.qpic.cn/psb?/V13GwxkT2AqZnq/K2fNvk13QaUO4punmxpIAL1kZFDy4AeNRkfZmdItWyM!/b/dFYBAAAAAAAA&bo=wgH9AgAAAAAFBxg!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/7RzzUgvozabfIqwfX8rv4utG9aT3n2e3Asi4zzxvnmI!/b/dGkBAAAAAAAA&bo=gAI1AwAAAAAFB5A!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/eXRrjOn0pBOFf.7m3yaXyEztQEkoGtYcg8EBoJpaO0U!/b/dGkBAAAAAAAA&bo=yADIAAAAAAAFByQ!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/pw5plnw3LsocGQlRQSgrnZCPy2KV5LvWb*Nz3UNCvJQ!/b/dGkBAAAAAAAA&bo=AASAAgAAAAAFB6I!&rf=viewer_4", @"http://a1.qpic.cn/psb?/V13GwxkT2AqZnq/7EO.WlDqMcvVBYcc27XFLNplGjwdYx2p56TZNwc.Tn4!/b/dFYBAAAAAAAA&bo=ZwJnAgAAAAAFByQ!&rf=viewer_4"];
    NSArray *thumUrlArray = @[@"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/oDM0rERpn5N83a2QFuXUHu8L*xSgOy0z7lcjNxiGZW4!/b/dGkBAAAAAAAA&bo=lgBUAAAAAAAFAOE!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/q*CPyLxw3XhsNW71p.iNXZ0cY53CpFn884sLFYkj1cs!/b/dGkBAAAAAAAA&bo=lgBeAAAAAAAFB.w!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/1PTuUpFR*aLa2mT6Lxgq1f3TrDAsDF.7wlAvjgLjUvQ!/b/dGkBAAAAAAAA&bo=lgBUAAAAAAAFB.Y!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/FGhSAze1L1rYWEWU4cA45zTtJYp4rvsSSuOM1TvAWT8!/b/dGkBAAAAAAAA&bo=lgBUAAAAAAAFAOE!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/uR*1kuC7oM12uYffpYboQwDNyPUA8Q.9xLNbyMkx.zE!/b/dGYBAAAAAAAA&bo=lgBeAAAAAAAFAOs!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/iI1t618fi94LzYwkJk0zRX8SMQWr8XGHQ98ioZYhUKM!/b/dGkBAAAAAAAA&bo=lgBxAAAAAAAFAMQ!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/tEfmzbb22KtoJwohbUIkNXG9U3348pk7cI1HdlDD3OE!/b/dGkBAAAAAAAA&bo=lgBeAAAAAAAFB.w!&rf=viewer_4", @"http://a1.qpic.cn/psb?/V13GwxkT2AqZnq/fc0EInMMxEzeMqRUfr4n8oDjvjxRSKkAxi0.UTwMcuQ!/b/dGgBAAAAAAAA&bo=lgBeAAAAAAAFB.w!&rf=viewer_4", @"http://a1.qpic.cn/psb?/V13GwxkT2AqZnq/0kKI.OxxnonRO*PamJT8IrvEqFL0YX254sbitqXU9z0!/b/dGgBAAAAAAAA&bo=lgBeAAAAAAAFB.w!&rf=viewer_4", @"http://a1.qpic.cn/psb?/V13GwxkT2AqZnq/4QPH9RB7lBVeZNbUsp0xXUAPm1Iku1Dz*9sdvrVDEns!/b/dGgBAAAAAAAA&bo=lgBkAAAAAAAFB9Y!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/xkR2EkWlq.gBmIq0cWKoUG1qrvl86lDljdBeHRENlug!/b/dGkBAAAAAAAA&bo=WACWAAAAAAAFB.o!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/8jl5H11.QX4iGBJ3j6JS2Fvoy.WlG6IVcoNCEzKMVBU!/b/dGkBAAAAAAAA&bo=dQCWAAAAAAAFB8c!&rf=viewer_4", @"http://a1.qpic.cn/psb?/V13GwxkT2AqZnq/Xwn7NLeHycrnrLJ.w4hzcmq9QmS7FW09gYXsxtLPORc!/b/dGgBAAAAAAAA&bo=lgCWAAAAAAAFByQ!&rf=viewer_4", @"http://a1.qpic.cn/psb?/V13GwxkT2AqZnq/*lg6dH6TTg.qe4HttnA1v4J7G3ucQeJ2s06xuVc1zgU!/b/dFYBAAAAAAAA&bo=lgBeAAAAAAAFB.w!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/pfhX5nA5usc0IqoFItZMJT4EgYloUC8bd5cElsp4FI8!/b/dGkBAAAAAAAA&bo=lgCWAAAAAAAFByQ!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/oDM0rERpn5N83a2QFuXUHu8L*xSgOy0z7lcjNxiGZW4!/b/dGkBAAAAAAAA&bo=lgBUAAAAAAAFAOE!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/q*CPyLxw3XhsNW71p.iNXZ0cY53CpFn884sLFYkj1cs!/b/dGkBAAAAAAAA&bo=lgBeAAAAAAAFB.w!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/1PTuUpFR*aLa2mT6Lxgq1f3TrDAsDF.7wlAvjgLjUvQ!/b/dGkBAAAAAAAA&bo=lgBUAAAAAAAFB.Y!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/FGhSAze1L1rYWEWU4cA45zTtJYp4rvsSSuOM1TvAWT8!/b/dGkBAAAAAAAA&bo=lgBUAAAAAAAFAOE!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/uR*1kuC7oM12uYffpYboQwDNyPUA8Q.9xLNbyMkx.zE!/b/dGYBAAAAAAAA&bo=lgBeAAAAAAAFAOs!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/iI1t618fi94LzYwkJk0zRX8SMQWr8XGHQ98ioZYhUKM!/b/dGkBAAAAAAAA&bo=lgBxAAAAAAAFAMQ!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/tEfmzbb22KtoJwohbUIkNXG9U3348pk7cI1HdlDD3OE!/b/dGkBAAAAAAAA&bo=lgBeAAAAAAAFB.w!&rf=viewer_4", @"http://a1.qpic.cn/psb?/V13GwxkT2AqZnq/fc0EInMMxEzeMqRUfr4n8oDjvjxRSKkAxi0.UTwMcuQ!/b/dGgBAAAAAAAA&bo=lgBeAAAAAAAFB.w!&rf=viewer_4", @"http://a1.qpic.cn/psb?/V13GwxkT2AqZnq/0kKI.OxxnonRO*PamJT8IrvEqFL0YX254sbitqXU9z0!/b/dGgBAAAAAAAA&bo=lgBeAAAAAAAFB.w!&rf=viewer_4", @"http://a1.qpic.cn/psb?/V13GwxkT2AqZnq/4QPH9RB7lBVeZNbUsp0xXUAPm1Iku1Dz*9sdvrVDEns!/b/dGgBAAAAAAAA&bo=lgBkAAAAAAAFB9Y!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/xkR2EkWlq.gBmIq0cWKoUG1qrvl86lDljdBeHRENlug!/b/dGkBAAAAAAAA&bo=WACWAAAAAAAFB.o!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/8jl5H11.QX4iGBJ3j6JS2Fvoy.WlG6IVcoNCEzKMVBU!/b/dGkBAAAAAAAA&bo=dQCWAAAAAAAFB8c!&rf=viewer_4", @"http://a1.qpic.cn/psb?/V13GwxkT2AqZnq/Xwn7NLeHycrnrLJ.w4hzcmq9QmS7FW09gYXsxtLPORc!/b/dGgBAAAAAAAA&bo=lgCWAAAAAAAFByQ!&rf=viewer_4", @"http://a1.qpic.cn/psb?/V13GwxkT2AqZnq/*lg6dH6TTg.qe4HttnA1v4J7G3ucQeJ2s06xuVc1zgU!/b/dFYBAAAAAAAA&bo=lgBeAAAAAAAFB.w!&rf=viewer_4", @"http://a2.qpic.cn/psb?/V13GwxkT2AqZnq/pfhX5nA5usc0IqoFItZMJT4EgYloUC8bd5cElsp4FI8!/b/dGkBAAAAAAAA&bo=lgCWAAAAAAAFByQ!&rf=viewer_4"];
    //相簿版本號,相簿內部有更新時更新版本號,這樣做的缺點是需要全部更新,如果更新頻繁還是單張快取吧,有很多優秀的開源庫
    NSString *newVersion = @"1.0.0";
    //相簿id,本地快取路徑是通過這個id拼接的,多個相簿不會互繞
    NSString *photoID = @"1000";
    
    //獲取本儲存的相簿版本號
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSString *currentVersion = [defaults objectForKey:[HWPhotoManger getPhotoVersionKeyWithPhotoID:photoID]];
    
    //如果有本地快取
    if ([newVersion isEqualToString:currentVersion]) {
        //取本地圖片
        _photos = [NSKeyedUnarchiver unarchiveObjectWithFile:[HWPhotoManger getPhotoPathWithPhotoID:photoID]];
        _thumPhotos = [NSKeyedUnarchiver unarchiveObjectWithFile:[HWPhotoManger getThumPhotoPathWithPhotoID:photoID]];
        
        //重新整理
        [_loadingView dismiss];
        [_collectionView reloadData];
        
    //如果沒有本地快取
    }else {
         HWPhotoManger *photoManger = [HWPhotoManger sharePhotoManger];
        
        //載入圖片
        if (!photoManger.isLoading) {
            [photoManger getImageWithUrlArray:urlArray thumUrlArray:thumUrlArray photoID:photoID version:newVersion];
        }else {
            _loadingView.progress = photoManger.progress;
        }
        
        //載入失敗
        photoManger.error = ^() {
            NSLog(@"載入失敗,請檢查網路");
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [self photoVCBackAction];
            });
        };
        
        //重新整理進度
        photoManger.progressBlock = ^(CGFloat progress) {
            _loadingView.progress = progress;
        };
        
        //重新整理檢視
        photoManger.finishBlock = ^(NSArray *array, NSArray *thumArray) {
            _photos = array;
            _thumPhotos = thumArray;
            [_loadingView dismiss];
            [_collectionView reloadData];
        };
    }
}

- (void)photoVCBackAction
{
    [self dismissViewControllerAnimated:YES completion:nil];
}

#pragma mark - UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return _thumPhotos.count;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
    
    cell.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageWithContentsOfFile:[HWPhotoManger getImagePathWithImageName:_thumPhotos[indexPath.item]]]];
    
    return cell;
}

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    return CGSizeMake(KItemW, KItemH);
}

#pragma mark - UICollectionViewDelegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
    
    CGRect selectPhotoFrame = [self.view convertRect:cell.frame fromView:collectionView];
    
    [self.photoView showWithSelectPhotoFrame:selectPhotoFrame selectItem:indexPath.item];
}

@end

HWPhotoView(圖片展示檢視,用scrollView做為容器展示圖片,設定縮放、點選、滑動事件,添加了一個collectionView做為快速瀏覽檢視,畫筆功能是添加了一個空檢視覆蓋在圖片上,並未與圖片融合):

#import <UIKit/UIKit.h>

@interface HWPhotoView : UIView

@property (nonatomic, strong) NSArray *photos;
@property (nonatomic, strong) NSArray *thumPhotos;
@property (nonatomic, copy) void(^ back)(CGRect imageFrame, NSInteger item);

- (void)showWithSelectPhotoFrame:(CGRect)selectPhotoFrame selectItem:(NSInteger)selectItem;

@end

/*** ---------------分割線--------------- ***/

#import "HWPhotoView.h"
#import "HWPaintView.h"
#import "HWPaintBar.h"
#import "HWPhotoManger.h"
#import "HWNavBar.h"

#define KHWPhotoViewMargin 2
#define KCollectionViewHeigth 100
#define KPicMinScale 1.0
#define KPicMaxScale 2.4
#define KItemW 70
#define KItemH 96

@interface HWPhotoView ()<UICollectionViewDelegate, UICollectionViewDataSource, UIScrollViewDelegate, HWPaintBarDelegate>

@property (nonatomic, weak) UILabel *label;
@property (nonatomic, weak) UIImageView *imageView;
@property (nonatomic, weak) UIScrollView *scrollView;
@property (nonatomic, weak) HWNavBar *navBar;
@property (nonatomic, weak) HWPaintView *paintView;
@property (nonatomic, weak) HWPaintBar *paintBar;
@property (nonatomic, strong) UICollectionView *collectionView;
@property (nonatomic, assign) NSInteger index;
@property (nonatomic, assign) CGFloat startCollectionViewX;
@property (nonatomic, assign) CGFloat lastScrContX;

@end

@implementation HWPhotoView

static NSString *const reuseIdentifier = @"HWPhotoViewCell";

- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        self.backgroundColor = [UIColor blackColor];

        //容器
        UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.bounds];
        scrollView.minimumZoomScale = KPicMinScale;
        scrollView.maximumZoomScale = KPicMaxScale;
        scrollView.showsVerticalScrollIndicator = NO;
        scrollView.showsHorizontalScrollIndicator = NO;
        scrollView.delegate = self;
        [self addSubview:scrollView];
        self.scrollView = scrollView;
        
        //單擊
        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(click)];
        tap.numberOfTouchesRequired = 1;
        tap.numberOfTapsRequired = 1;
        [scrollView addGestureRecognizer:tap];
        
        //雙擊
        UITapGestureRecognizer *tap2 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleClick)];
        tap2.numberOfTapsRequired = 2;
        [scrollView addGestureRecognizer:tap2];
        [tap requireGestureRecognizerToFail:tap2];
        
        //右滑手勢
        UISwipeGestureRecognizer *rightSwip = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(nextPage)];
        rightSwip.direction = UISwipeGestureRecognizerDirectionLeft;
        [scrollView addGestureRecognizer:rightSwip];
        
        //左滑手勢
        UISwipeGestureRecognizer *leftSwip = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(forwardPage)];
        leftSwip.direction = UISwipeGestureRecognizerDirectionRight;
        [scrollView addGestureRecognizer:leftSwip];
        
        //下滑手勢
        UISwipeGestureRecognizer *downSwip = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(downSwipAction)];
        downSwip.direction = UISwipeGestureRecognizerDirectionDown;
        [scrollView addGestureRecognizer:downSwip];
        
        //圖片
        UIImageView *imageView = [[UIImageView alloc] init];
        [self.scrollView addSubview:imageView];
        self.imageView = imageView;
        
        //手繪板
        HWPaintView *paintView = [[HWPaintView alloc] initWithFrame:self.bounds];
        paintView.hidden = YES;
        [self addSubview:paintView];
        self.paintView = paintView;
        
        //導航條
        HWNavBar *navBar = [[HWNavBar alloc] initWithFrame:CGRectMake(0, 0, KMainW, 64)];
        navBar.title = @"相簿";
        [navBar addBackButtonWithAction:@selector(photoViewBackAction)];
        [self addSubview:navBar];
        self.navBar = navBar;
        
        //開啟手繪按鈕
        UIButton *paintBtn = [[UIButton alloc] initWithFrame:CGRectMake(KMainW - 95, 20, 80, 44)];
        paintBtn.selected = YES;
        [paintBtn setTitle:@"顯示畫筆" forState:UIControlStateNormal];
        [paintBtn setTitle:@"隱藏畫筆" forState:UIControlStateSelected];
        [paintBtn addTarget:self action:@selector(paintBtnOnClick:) forControlEvents:UIControlEventTouchUpInside];
        [navBar addSubview:paintBtn];
        
        //畫筆工具條
        HWPaintBar *paintBar = [[HWPaintBar alloc] initWithFrame:CGRectMake(KMainW - 60, 80, 44, 412)];
        paintBar.delegate = self;
        [self addSubview:paintBar];
        self.paintBar = paintBar;
        
        //佈局
        UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
        layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
        layout.minimumLineSpacing = KHWPhotoViewMargin;
        layout.sectionInset = UIEdgeInsetsMake(KHWPhotoViewMargin, KHWPhotoViewMargin, KHWPhotoViewMargin, KHWPhotoViewMargin);
        
        //瀏覽條
        _collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, KMainH - KCollectionViewHeigth, KMainW, KCollectionViewHeigth) collectionViewLayout:layout];
        _collectionView.backgroundColor = [UIColor colorWithHexString:@"003c65"];
        _collectionView.dataSource = self;
        _collectionView.delegate = self;
        [self addSubview:_collectionView];
        
        //註冊標識
        [_collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:reuseIdentifier];
    }
    
    return self;
}

//左滑事件
- (void)nextPage
{
    if (_index < _photos.count - 1) {
        _index ++;
        
        //當滑動展示檢視超出展示範圍,未選中時進行翻頁,先恢復到原位置
        [_collectionView setContentOffset:CGPointMake(_startCollectionViewX, 0)];
        
        //滑動時展示圖最右邊圖片是否能完全顯示
        BOOL isIntactRight = (int)(_collectionView.contentOffset.x + KMainW) % (KHWPhotoViewMargin + KItemW) <= KHWPhotoViewMargin;
        
        //滑動時展示圖最右邊圖片item
        NSInteger currentRightItem = (int)(_collectionView.contentOffset.x + KMainW) / (KHWPhotoViewMargin + KItemW);
        if (isIntactRight) currentRightItem--;
        
        //如果最右邊item不能完全顯示,並滑動到該item,左移檢視至item完全顯示
        if (!isIntactRight && _index == currentRightItem) {
            CGFloat movePadding = KItemW - ((int)(_collectionView.contentOffset.x + KMainW) % (KItemW + KHWPhotoViewMargin) - KHWPhotoViewMargin);
            [_collectionView setContentOffset:CGPointMake(_collectionView.contentOffset.x + movePadding, 0)];
        }
        
        //如果滑動超出展示檢視最大item,左移檢視至item顯示
        if (_index > currentRightItem) {
            CGFloat movePadding = KItemW - ((int)(_collectionView.contentOffset.x + KMainW) % (KItemW + KHWPhotoViewMargin) - KHWPhotoViewMargin) + (KItemW + KHWPhotoViewMargin);
            if (isIntactRight) movePadding = movePadding - (KItemW + KHWPhotoViewMargin);
            [_collectionView setContentOffset:CGPointMake(_collectionView.contentOffset.x + movePadding, 0)];
        }
        
        //重新整理檢視
        [self reloadPhotoView];
        [self transitionWithType:@"pageCurl" WithSubtype:kCATransitionFromRight ForView:self];
        
    }else {
        NSLog(@"已經是最後一頁了");
    }
}

//右滑事件
- (void)forwardPage
{
    if (_index > 0) {
        _index --;
        
        //當滑動展示檢視超出展示範圍,未選中時進行翻頁,先恢復到原位置
        [_collectionView setContentOffset:CGPointMake(_startCollectionViewX, 0)];
        
        //滑動時展示圖最左邊圖片是否能完全顯示
        BOOL isIntactLeft = (int)_collectionView.contentOffset.x % (KHWPhotoViewMargin + KItemW) <= KHWPhotoViewMargin;
        
        //滑動時展示圖最左邊圖片item
        NSInteger currentLeftItem = (int)_collectionView.contentOffset.x / (KHWPhotoViewMargin + KItemW);
        
        //如果最左邊item不能完全顯示,並滑動到該item,右移檢視至item完全顯示
        if (!isIntactLeft && _index == currentLeftItem) {
            CGFloat movePadding = (int)_collectionView.contentOffset.x % (KItemW + KHWPhotoViewMargin);
            [_collectionView setContentOffset:CGPointMake(_collectionView.contentOffset.x - movePadding, 0)];
        }
        
        //如果滑動超出展示檢視最小item,右移檢視至item顯示
        if (_index < currentLeftItem) {
            CGFloat movePadding = (int)_collectionView.contentOffset.x % (KItemW + KHWPhotoViewMargin) + (KItemW + KHWPhotoViewMargin);
            [_collectionView setContentOffset:CGPointMake(_collectionView.contentOffset.x - movePadding, 0)];
        }
        
        //重新整理檢視
        [self reloadPhotoView];
        [self transitionWithType:@"pageUnCurl" WithSubtype:kCATransitionFromRight ForView:self];
        
    }else {
        NSLog(@"已經是第一頁了");
    }
}

//下滑事件
- (void)downSwipAction
{
    [self dismiss];
    [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone];
    
    if (_back) _back(_imageView.frame, _index);
}

//更新檢視資訊
- (void)reloadPhotoView
{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self reloadSelectItem];
    });
    _scrollView.zoomScale = KPicMinScale;
    _imageView.image = [UIImage imageWithContentsOfFile:[HWPhotoManger getImagePathWithImageName:_photos[_index]]];
    [self setImageViewFrame];
    _label.text = [NSString stringWithFormat:@"%ld / %ld", _index + 1, _photos.count];
}

//更新展示檢視選中項
- (void)reloadSelectItem
{
    _startCollectionViewX = _collectionView.contentOffset.x;
    
    UICollectionViewCell *cell;
    for (int i = 0; i < _photos.count; i++) {
        cell = [_collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
        cell.backgroundView.layer.borderWidth = i == _index ? 5.0f : 0.0f;
    }
}

//設定翻頁動畫效果
- (void)transitionWithType:(NSString *)type WithSubtype:(NSString *)subtype ForView:(UIView *)view
{
    CATransition *animation = [CATransition animation];
    animation.duration = 0.7f;
    animation.type = type;
    if (subtype != nil) {
        animation.subtype = subtype;
    }
    animation.timingFunction = UIViewAnimationOptionCurveEaseInOut;
    [view.layer addAnimation:animation forKey:@"animation"];
}

//單擊螢幕顯示隱藏選單
- (void)click
{
    if (_navBar.hidden == YES) {
        _navBar.hidden = NO;
        _collectionView.hidden = NO;
        [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone];
        
    }else {
        _navBar.hidden = YES;
        _collectionView.hidden = YES;
        [[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone];
    }
}

//雙擊螢幕放大縮小圖片
- (void)doubleClick
{
    [UIView animateWithDuration:0.25f animations:^{
        _scrollView.zoomScale = _scrollView.zoomScale == KPicMinScale ? KPicMaxScale : KPicMinScale;
    }];
}

//返回按鈕點選事件
- (void)photoViewBackAction
{
    [self dismiss];
    
    if (_back) _back(_imageView.frame, _index);
}

//開啟編輯按鈕點選事件
- (void)paintBtnOnClick:(UIButton *)btn
{
    btn.selected = !btn.selected;
    
    _paintBar.hidden = !btn.selected;
}

//顯示
- (void)showWithSelectPhotoFrame:(CGRect)selectPhotoFrame selectItem:(NSInteger)selectItem
{
    self.hidden = NO;
    
    _index = selectItem;
    
    _label.text = [NSString stringWithFormat:@"%ld / %ld", _index + 1, _photos.count];
    [_collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:_index inSection:0] atScrollPosition:UICollectionViewScrollPositionLeft animated:NO];
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self reloadSelectItem];
    });
    
    _imageView.image = [UIImage imageWithContentsOfFile:[HWPhotoManger getImagePathWithImageName:_photos[_index]]];
    _imageView.frame = selectPhotoFrame;
    
    [UIView animateWithDuration:0.25f animations:^{
        [self setImageViewFrame];
    }];
}

//更新圖片frame
- (void)setImageViewFrame
{
    CGRect frame = _imageView.frame;
    frame.size.width = _imageView.image.size.width > KMainW ? KMainW : _imageView.image.size.width;
    frame.size.height = frame.size.width * (_imageView.image.size.height / _imageView.image.size.width);
    if (frame.size.height > KMainH) {
        frame.size.height = KMainH;
        frame.size.width = KMainH * (_imageView.image.size.width / _imageView.image.size.height);
    }
    frame.origin.x = (KMainW - frame.size.width) * 0.5;
    frame.origin.y = (KMainH - frame.size.height) * 0.5;
    _imageView.frame = frame;
}

//隱藏
- (void)dismiss
{
    _scrollView.zoomScale = KPicMinScale;
    self.hidden = YES;
}

#pragma mark - HWPaintBarDelegate
- (void)didClickClearBtnInHWPaintBar:(HWPaintBar *)paintBar
{
    [_paintView clear];
}

- (void)didClickCancelBtnInHWPaintBar:(HWPaintBar *)paintBar
{
    [_paintView cancel];
}

- (void)paintBar:(HWPaintBar *)paintBar didClickBrushBtnWithState:(BOOL)isOpen
{
    _paintView.hidden = !isOpen;
    
    if (!isOpen) [_paintView clear];
}

- (void)paintBar:(HWPaintBar *)paintBar didClickStateBtnWithState:(int)state
{
    _paintView.lineState = state;
}

- (void)paintBar:(HWPaintBar *)paintBar didClickColorBtnWithColor:(UIColor *)color
{
    _paintView.lineColor = color;
}

#pragma mark - UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return _thumPhotos.count;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
    
    cell.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageWithContentsOfFile:[HWPhotoManger getImagePathWithImageName:_thumPhotos[indexPath.item]]]];
    cell.backgroundView.layer.borderColor = [[UIColor colorWithHexString:@"#00BFFF"] CGColor];
    if (indexPath.item == _index) cell.backgroundView.layer.borderWidth = 5.0f;
    
    return cell;
}

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    return CGSizeMake(KItemW, KItemH);
}

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
    _index = indexPath.item;
    
    [_paintView clear];
    
    [self reloadPhotoView];
}

#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
    CGFloat offsetX = (scrollView.bounds.size.width > scrollView.contentSize.width) ? (scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5 : 0.0;
    CGFloat offsetY = (scrollView.bounds.size.height > scrollView.contentSize.height) ? (scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5 : 0.0;
    self.imageView.center = CGPointMake(scrollView.contentSize.width * 0.5 + offsetX, scrollView.contentSize.height * 0.5 + offsetY);
}

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
    return self.imageView;
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    if (scrollView.isZooming) return;
    
    //設定允許翻頁的偏移量
    CGFloat movePadding = 70;
    
    //仿蘋果原生相簿,圖片放大後,滑動前在邊界時才允許翻頁,這裡加了±10的偏移量
    if (scrollView.contentOffset.x < - movePadding && self.lastScrContX < 10) {
        _scrollView.zoomScale = KPicMinScale;
        [self forwardPage];
    }
    
    if (scrollView.contentSize.width - scrollView.contentOffset.x < KMainW - movePadding && scrollView.contentSize.width != 0 && fabs(self.lastScrContX + KMainW - scrollView.contentSize.width) < 10) {
        _scrollView.zoomScale = KPicMinScale;
        [self nextPage];
    }
}

//滑動自然停止時呼叫
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    //記錄滑動停止時的偏移量
    self.lastScrContX = scrollView.contentOffset.x;
}

//滑動手動停止時呼叫
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
    self.lastScrContX = scrollView.contentOffset.x;
}

@end

HWPaintView(繪畫檢視):

#import <UIKit/UIKit.h>

typedef enum {
    HWPaintLineStateNormal = 0,
    HWPaintLineStateLight,
} HWPaintLineState;

@interface HWPaintView : UIView

@property (nonatomic, assign) HWPaintLineState lineState;
@property (nonatomic, strong) UIColor *lineColor;

- (void)clear;
- (void)cancel;

@end

/*** ---------------分割線--------------- ***/

#import "HWPaintView.h"

@interface HWPaintView ()

@property (nonatomic, strong) NSMutableArray *paths;

@end

@implementation HWPaintView

- (NSMutableArray *)paths
{
    if (!_paths) {
        _paths = [NSMutableArray array];
    }
    
    return _paths;
}

- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        self.backgroundColor = [UIColor clearColor];
        self.lineColor = [UIColor colorWithHexString:@"#fa4d32"];
    }
    
    return self;
}

- (void)clear
{
    [self.paths removeAllObjects];
    
    [self setNeedsDisplay];
}

- (void)cancel
{
    [self.paths removeLastObject];
    
    [self setNeedsDisplay];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    CGPoint startPoint = [touch locationInView:touch.view];
    
    UIBezierPath *path = [UIBezierPath bezierPath];
    path.lineCapStyle = kCGLineCapRound;
    path.lineJoinStyle = kCGLineJoinRound;
    path.lineWidth = 5;
    [path moveToPoint:startPoint];
    [path addLineToPoint:startPoint];
    [self.paths addObject:path];
    
    [self setNeedsDisplay];
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    CGPoint currentPoint = [touch locationInView:touch.view];
    
    UIBezierPath *path = [self.paths lastObject];
    [path addLineToPoint:currentPoint];
    
    [self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect
{
    for (UIBezierPath *path in self.paths) {
        if (self.lineState == HWPaintLineStateNormal) {
            [self.lineColor set];
            
        }else if (self.lineState == HWPaintLineStateLight) {
            [[UIColor whiteColor] set];
            CGContextSetShadowWithColor(UIGraphicsGetCurrentContext(), CGSizeMake(0, 0), 8, [self.lineColor CGColor]);
        }
        
        [path stroke];
    }
}

@end

HWPhotoManger(圖片載入快取管理類,在這裡獲取圖片、路徑、進度):

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface HWPhotoManger : NSObject

@property (nonatomic, copy) void(^progressBlock)(CGFloat progress);
@property (nonatomic, copy) void(^finishBlock)(NSArray *array, NSArray *thumArray);
@property (nonatomic, copy) void(^error)();
@property (nonatomic, assign, readonly) BOOL isLoading;
@property (nonatomic, assign) CGFloat progress;

+ (instancetype)sharePhotoManger;
+ (NSString *)getPhotoVersionKeyWithPhotoID:(NSString *)photoID;
+ (NSString *)getPhotoPathWithPhotoID:(NSString *)photoID;
+ (NSString *)getThumPhotoPathWithPhotoID:(NSString *)photoID;
+ (NSString *)getImagePathWithImageName:(NSString *)imageName;
- (void)getImageWithUrlArray:(NSArray *)urlArray thumUrlArray:(NSArray *)thumUrlArray photoID:(NSString *)photoID version:(NSString *)version;

@end

/*** ---------------分割線--------------- ***/

#import "HWPhotoManger.h"

#define KPhotoCachesPath [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]

@interface HWPhotoManger ()

@property (nonatomic, assign, readwrite) BOOL isLoading;

@end

@implementation HWPhotoManger

+ (instancetype)sharePhotoManger
{
    static HWPhotoManger *instance;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    
    return instance;
}

- (void)getImageWithUrlArray:(NSArray *)urlArray thumUrlArray:(NSArray *)thumUrlArray photoID:(NSString *)photoID version:(NSString *)version
{
    _isLoading = YES;
    if (_progressBlock == nil) _progressBlock = ^(CGFloat progress) {};
    if (_finishBlock == nil) _finishBlock = ^(NSArray *array, NSArray *thumArray) {};
    if (_error == nil) _error = ^(){};
    
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSString *versionKey = [NSString stringWithFormat:@"photosVersionKey%@", photoID];
    
    //本地相簿路徑(原圖)
    NSString *photoName = [@"HWPhotosCache" stringByAppendingString:photoID];
    NSString *photoPath = [KPhotoCachesPath stringByAppendingPathComponent:photoName];
    //本地相簿路徑(縮圖)
    NSString *thumPhotoName = [@"HWThumPhotosCache" stringByAppendingString:photoID];
    NSString *thumPhotoPath = [KPhotoCachesPath stringByAppendingPathComponent:thumPhotoName];
    
    NSMutableArray *temArr = [NSMutableArray array];
    NSMutableArray *thumTemArr = [NSMutableArray array];
    
    __block int i = 0;
    weakify(self);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        strongify(weakSelf);
        for (NSString *url in urlArray) {
            //載入圖片(這個方式並不好)
            UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:url]]];
            if (image == nil) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    strongSelf.error();
                    strongSelf.isLoading = NO;
                    strongSelf.progress = 0;
                });
                return;
            }
            //將圖片儲存到本地
            NSString *imageName = [NSString stringWithFormat:@"%@image%d", photoName, i];
            NSString *iamgePath = [KPhotoCachesPath stringByAppendingPathComponent:imageName];
            [UIImagePNGRepresentation(image) writeToFile:iamgePath atomically:YES];
            [temArr addObject:imageName];
            
            UIImage *thumImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:thumUrlArray[i++]]]];
            if (thumImage == nil) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    strongSelf.error();
                    strongSelf.isLoading = NO;
                    strongSelf.progress = 0;
                });
                return;
            }
            NSString *thumImageName = [NSString stringWithFormat:@"%@thumImage%d", thumPhotoName, i];
            NSString *thumIamgePath = [KPhotoCachesPath stringByAppendingPathComponent:thumImageName];
            [UIImagePNGRepresentation(thumImage) writeToFile:thumIamgePath atomically:YES];
            [thumTemArr addObject:thumImageName];
            
            dispatch_async(dispatch_get_main_queue(), ^{
                strongSelf.progress = (CGFloat)thumTemArr.count / urlArray.count;
                strongSelf.progressBlock(strongSelf.progress);
                
                if (urlArray.count == thumTemArr.count) {
                    strongSelf.finishBlock(temArr, thumTemArr);
                    strongSelf.isLoading = NO;
                    strongSelf.progress = 0;
                    
                    //儲存
                    [defaults setObject:version forKey:versionKey];
                    [NSKeyedArchiver archiveRootObject:temArr toFile:photoPath];
                    [NSKeyedArchiver archiveRootObject:thumTemArr toFile:thumPhotoPath];
                }
            });
        }
    });
}

+ (NSString *)getPhotoVersionKeyWithPhotoID:(NSString *)photoID
{
    return [NSString stringWithFormat:@"photosVersionKey%@", photoID];
}

+ (NSString *)getPhotoPathWithPhotoID:(NSString *)photoID
{
    return [KPhotoCachesPath stringByAppendingPathComponent:[@"HWPhotosCache" stringByAppendingString:photoID]];
}

+ (NSString *)getThumPhotoPathWithPhotoID:(NSString *)photoID
{
    return [KPhotoCachesPath stringByAppendingPathComponent:[@"HWThumPhotosCache" stringByAppendingString:photoID]];
}

+ (NSString *)getImagePathWithImageName:(NSString *)imageName
{
    return [KPhotoCachesPath stringByAppendingPathComponent:imageName];
}

@end

還有幾個自定義的檢視不貼出來了。

寫部落格的初心是希望大家共同交流成長,博主水平有限難免有偏頗之處,歡迎批評指正。