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 HWPhotoVCstatic 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));//创建图片并设置初始frameUIImageView __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;//点击图片时左上第一张图片itemNSInteger currentFirstItem = (int)_collectionView.contentOffset.y / (KMargin + KItemH) * colCount;//点击图片时左下第一张图片itemNSInteger 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(), ^{//获取图片缩小后的frameUICollectionViewCell *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];//网络请求获取相册相关信息这里省略了,直接模拟获取到图片数组、缩略图数组、相册版本号、相册idNSArray *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 HWPhotoViewstatic 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;//滑动时展示图最右边图片itemNSInteger 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;//滑动时展示图最左边图片itemNSInteger 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

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

iOS 浏览相册功能实现 —— HERO博客相关推荐

  1. iOS 答题功能实现 —— HERO博客

    上一篇简述了搭建项目框架的流程,本篇在此基础上实用,做了一个简单的类似题库答题页面. 首先看一下效果图: 简单阐述一下:这里没有做网络请求数据,题目数据是本地模拟服务端写的,没有做本地缓存,每次进入. ...

  2. iOS 计算日期间隔天数 —— HERO博客

    计算两个日期间隔天数,下面贴上代码,计算指定日期与当前日期间隔天数: - (void)viewDidLoad {[super viewDidLoad];self.view.backgroundColo ...

  3. iOS 添加字体库 —— HERO博客

    简述一下如何在项目中添加字体及其使用. 首先去网上下载所需字体,将下载的字体文件导入到工程中.如图20-1,将下载的"迷你简少儿.ttf"文件导入到工程中. 在工程的Info.pl ...

  4. 关于iOS学习进阶的必读一些博客总结

    关于iOS学习进阶的必读一些博客总结 作者 Raybon_lee 2016.01.16 22:28* 字数 3110 经过一周的思考还是决定重组一下优秀的博客,首次整理这些博客比较乱,但是后期会慢慢增 ...

  5. iOS学习的比较好的博客地址

    如果你正在寻找见解深刻,并富有教育意义的iOS开发博客,那就找对地方了.下面列出了质量最好且最为活跃的105个线上博客.如你所知,互联网上高质量的iOS博客并不缺乏,但我决定再过滤一下,去芜存菁,找出 ...

  6. 《Hexo: 从零开始编写自己的主题》5. 添加本地搜索功能以及发布博客让官网接收

    <Hexo: 从零开始编写自己的主题> 1. Hexo概述以及Hexo工作原理 2. 入门Hexo主题编写 3. 优化样式,设计自己的主题 4. fancybox优化图片展示效果.代码高亮 ...

  7. mysql自带订阅功能_为博客提供订阅功能

    原标题:为博客提供订阅功能 以"为博客增加订阅功能"为例,来介绍用ajax提交表单的方法. 前端html5+js 欢迎使用ExASIC订阅服务 仅用于ExASIC最新文章通知,方便 ...

  8. iOS遇到的开发框架和优质博客,不断更新中

    少年,既然你打开了,你往下翻,中间优质博客你看了肯定会收藏的,历年来收藏的点点滴滴 非常牛B的iOS实现源码点击打开链接 1.iRate   评分的弹窗    https://github.com/n ...

  9. 用小程序·云开发打造功能全面的博客小程序丨实战

    用小程序·云开发将博客小程序常用功能"一网打尽" 本文介绍mini博客小程序的详情页的功能按钮如何实现,具体包括评论.点赞.收藏和海报功能,这里记录下整个实现过程和实际编码中的一些 ...

最新文章

  1. 现在不坚持,以后都会放弃
  2. 仅IE6中链接A的href为javascript协议时不能在当前页面跳转
  3. mysql中如何去除重复数据_MySQL中如何删除重复数据只保留一条
  4. java 反射与泛型_Java基础系列 - 泛型和反射机制
  5. [转载] Java static关键字与static{}语句块
  6. 解决Zend OPcache huge_code_pages: mmap(HUGETLB) fail
  7. 使用fastadmin的页面跳转模板
  8. MongoDB实战系列之三:MongoDB的主从部署
  9. java 匿名内部类多态,下面这个简单的程序验证匿名内部类的多态属性,但出现错误。...
  10. 微型计算机的分类通常以什么来划分,微机的分类通常以微处理器的什么来划分...
  11. DynamipsGUI支持的全系列Cisco IOS下载
  12. EnableViewState=true
  13. android输入法剪切板历史记录,Android Q 获取剪切板内容
  14. 美团外卖推荐系统之智能流量分发的实践与探索
  15. 哈佛结构和冯诺依曼结构?STM32属于哈佛结构还是冯诺依曼结构?
  16. chrome 前端开发插件:尺子
  17. 多协程和队列,爬取时光网电视剧TOP100的数据(剧名、导演、主演和简介)
  18. Easy2D 轻量级游戏开发框架(2)
  19. w3af漏扫的基本使用
  20. <笔试> 回转寿司题解 动态规划

热门文章

  1. Java中的IO流与Properties
  2. 动漫网站-前端网页技术精美网站源码HTML+CSS+JS
  3. java+static知乎_用Java抓取知乎日报信息
  4. 在Windows下也可以玩Ghost
  5. P1486 [NOI2004]郁闷的出纳员
  6. matlab 电力电子元件对应名称,MATLAB在电力电子技术的应用
  7. R语言|散点图 ———R语言数据可视化系列(一)
  8. 论文写作 9: 引言需要讲述完整的故事
  9. 这几行 C++ 代码,真的骚!
  10. 【Leetcode】444. Sequence Reconstruction