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


还有几个自定义的视图不贴出来了。

相册Demo下载链接:http://code.cocoachina.com/view/134624

写博客的初心是希望大家共同交流成长,博主水平有限难免有偏颇之处,欢迎批评指正。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值