iOS开源照片浏览器框架SGPhotoBrowser的设计与实现
簡介
近日在制作一個開源加密相冊時附帶著設計了一個照片瀏覽器,在進一步優化后發布到了GitHub供大家使用,該框架雖然沒有MWPhotoBrowser那么強大,但是使用起來更為方便,操作更符合常規相冊習慣,自定義和修改源碼也十分簡單。
本文主要介紹這個照片瀏覽器框架的技術要點,如果要深入研究和使用,可以在下面的鏈接中下載源碼。
如果你對這個框架有興趣,可以點擊這里前去GitHub下載源碼,歡迎Star與指出不足。
效果圖
縮略圖預覽,點擊縮略圖進入原圖瀏覽,點擊底部工具欄可以進入編輯模式。
批量導出與刪除,通過底部工具欄操作。
查看原圖,單擊可以隱藏導航欄和工具欄,支持雙擊切換縮放狀態、捏和手勢以及左右滑動切圖。
功能與特點
block數據源
照片瀏覽器的數據源是通過block回調的,通過實現相應的block并且提供數據模型即可完成圖片顯示。內存優化
高分辨率的圖片在讀入到內存后的內存占用是十分可觀的,因此在點擊縮略圖進入原圖瀏覽后,由于要左右滑動來查看其它圖片的原圖,因此至少加載三張原圖(不考慮邊緣情況),分別是當前查看的圖片和與之相鄰的圖片,而其他圖片則先加載縮略圖,在滾動到那些圖片時才去加載原圖以及與之相鄰的原圖,并且替換遠處的原圖為縮略圖。滾動優化
在滾動完全結束后才去加載原圖并替換縮略圖,以防止滾動時卡頓。同時支持本地與網絡圖片
通過URL的類型來判斷圖片是否來自網絡,如果來自網絡則異步下載并顯示進度,同時進行緩存。原圖瀏覽時支持常見的手勢
原圖瀏覽器時支持單擊隱藏和顯示導航欄和工具條,雙擊在適應屏幕和原始尺寸之間切換,捏和手勢可以縮放圖片,左右滑動可以切換圖片。支持批量導出與刪除照片
可以通過工具欄進入編輯模式來批量處理圖片的導出與刪除。
技術要點
概述
照片瀏覽器框架依賴了SDWebImage和MBProgressHUD,前者用于處理圖片的異步下載與緩存,后者用于顯示圖片下載的進度。用于縮略圖顯示的是collectionView,查看原圖時每一張圖片都被均勻排列在scrollView上,每一張圖片也被包裹了一個scrollView用于處理縮放。
block數據源
使用代理模式回調數據源會使得代碼較為分散,因此本框架使用了block來回調,在SGPhotoBrowser中有四個數據源block,通過實現他們并且提供相應的數據即可完成圖片顯示,這四個block如下面代碼所示。
@property (nonatomic, copy, readonly) SGPhotoBrowserDataSourceNumberBlock numberOfPhotosHandler; @property (nonatomic, copy, readonly) SGPhotoBrowserDataSourcePhotoBlock photoAtIndexHandler; @property (nonatomic, copy, readonly) SGPhotoBrowserReloadRequestBlock reloadHandler; @property (nonatomic, copy, readonly) SGPhotoBrowserDeletePhotoAtIndexBlock deleteHandler;每個照片通過一個SGPhotoModel數據模型類要描述,其中包含了photoURL與thumbURL,分別代表原圖和縮略圖的URL,通過URL是否是fileURL來決定是否要異步下載緩存。
block數據源在縮略圖瀏覽時被collectionView的dataSource所調用,在原圖瀏覽時被調用以獲取特定位置的圖片URL或進行刪除照片后的數據刷新。
內存優化
在查看原圖時,加載當前位置和與其相鄰位置的原圖,其他位置均加載縮略圖,在滑動過程中,動態的切換原圖的加載位置并將原來位置的原圖替換為縮略圖,以保證內存中最多有三張原圖被加載以節省內存,具體實現代碼如下。
// 點擊index處的縮略圖時調用,來顯示原圖 - (void)loadImageAtIndex:(NSInteger)index {// 通過browser的數據源方法獲取模型數量NSInteger count = self.browser.numberOfPhotosHandler();// 遍歷所有照片模型以及照片視圖for (NSInteger i = 0; i < count; i++) {SGPhotoModel *model = self.browser.photoAtIndexHandler(i);SGZoomingImageView *imageView = self.imageViews[i];NSURL *photoURL = model.photoURL;NSURL *thumbURL = model.thumbURL;// index位置和與其相鄰的位置加載原圖if (i >= index - 1 && i <= index + 1) {if (imageView.isOrigin) continue;// 根據URL選擇圖片是直接從本地加載還是異步下載緩存的方法[imageView.innerImageView sg_setImageWithURL:photoURL model:model];// 用于指示這個imageView是否加載的是原圖imageView.isOrigin = YES;// 縮放至適應屏幕[imageView scaleToFitAnimated:NO];} else {// 對于其他位置的圖片,如果是原圖,則替換為縮略圖if (!imageView.isOrigin) continue;[imageView.innerImageView sg_setImageWithURL:thumbURL model:model];imageView.isOrigin = NO;[imageView scaleToFitAnimated:NO];}} }滾動優化
在scrollView的滾動效果尚未停止時進行耗時操作會造成卡頓,為了避免這種情況,可以在scrollView減速完畢后再進行耗時操作。在本框架中,在左右滑動切換圖片時,如果立即加載原圖,會造成卡頓,因此在scrollView減速完畢后才將縮略圖替換為原圖,具體實現如下。
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {// 先通過偏移量計算出當前滾動到的圖片的索引CGFloat offsetX = scrollView.contentOffset.x;NSInteger index = (offsetX + _pageW * 0.5f) / _pageW;// 索引發生變化時才更新并加載原圖if (_index != index) {_index = index;// 上文提到的加載原圖的方法[self loadImageAtIndex:_index];} }本地圖片與網絡圖片的處理
所有的圖片都是通過URL進行設置,通過為UIImageView添加分類,并添加方法sg_setImageWithURL:model:方法,傳入當前要加載的圖片的URL以及照片模型,在方法內,通過URL類型來判斷是否要進行異步下載和緩存,在異步下載時,使用MBProgressHUD來指示進度,具體代碼如下。
@interface UIImageView (SGExtension) // 通過動態綁定來實現為UIImageView添加屬性 @property (nonatomic, weak) MBProgressHUD *hud; @property (nonatomic, strong) SGPhotoModel *model;- (void)sg_setImageWithURL:(NSURL *)url model:(SGPhotoModel *)model;@end @implementation UIImageView (SGExtension) // 動態綁定hud和model兩個屬性的key static char hudKey; static char modelKey; // 由于分類不允許添加屬性,因此需要手動實現setter與getter @dynamic hud; @dynamic model;- (void)sg_setImageWithURL:(NSURL *)url {if (![url isFileURL]) {// 如果不是文件URL,則說明需要下載,通過SDWebImage處理SDImageCache *cache = [SDImageCache sharedImageCache];SDWebImageManager *mgr = [SDWebImageManager sharedManager];NSString *key = [mgr cacheKeyForURL:url];// 如果在緩存中找到了圖片,則直接加載并返回if ([cache diskImageExistsWithKey:key] || ([cache imageFromMemoryCacheForKey:key] != nil)) {[self sd_setImageWithURL:url];return;}// 如果已經有了進度指示器,則說明正在下載圖片,直接返回if (self.hud != nil) {return;}// 圖片需要下載,且任務還未開始,通過MBProgressHUD指示下載進度,通過SDWebImage來下載和緩存圖片MBProgressHUD *hud = [[MBProgressHUD alloc] initWithView:self];self.hud = hud;hud.mode = MBProgressHUDModeAnnularDeterminate;[self addSubview:hud];[hud showAnimated:YES];// 如果對應于當前原圖的縮略圖已經下載完成,則先在原圖瀏覽中顯示縮略圖作為占位圖,否則顯示默認的黑色圖片。UIImage *placeHolderImage = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"SGPhotoBrowser.bundle/ImagePlaceholder.png" ofType:nil]];if (self.model.thumbURL) {NSString *key = [mgr cacheKeyForURL:self.model.thumbURL];UIImage *tempImage = [cache imageFromMemoryCacheForKey:key];if (tempImage == nil) {tempImage = [cache imageFromDiskCacheForKey:key];}if (tempImage) {placeHolderImage = tempImage;}}[self sd_setImageWithURL:url placeholderImage:placeHolderImage options:SDWebImageRetryFailed progress:^(NSInteger receivedSize, NSInteger expectedSize) {hud.progress = (float)receivedSize / expectedSize;} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {[hud removeFromSuperview];self.hud = nil;}];} else {// 對于文件URL,直接從文件系統中加載self.image = [UIImage imageWithContentsOfFile:url.path];} } // 公共方法,由于占位圖相關邏輯需要縮略圖URL,因此需要傳遞model,上面的方法為私有方法 - (void)sg_setImageWithURL:(NSURL *)url model:(SGPhotoModel *)model {self.model = model;[self sg_setImageWithURL:url]; } // 動態綁定的兩屬性的getter和setter #pragma mark - Setter - (void)setHud:(MBProgressHUD *)hud {objc_setAssociatedObject(self, &hudKey, hud, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }- (void)setModel:(SGPhotoModel *)model {objc_setAssociatedObject(self, &modelKey, model, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }#pragma mark - Getter - (MBProgressHUD *)hud {return objc_getAssociatedObject(self, &hudKey); }- (SGPhotoModel *)model {return objc_getAssociatedObject(self, &modelKey); } @end原圖瀏覽時的手勢處理
每張圖片使用一個scrollView包裹來處理捏合手勢縮放,同時通過touchesEnded::方法來判斷單擊和雙擊,由于雙擊時會經過單擊狀態,這里將單擊事件滯后0.2s處理,如果在這期間觸發了雙擊,則取消單擊事件的處理,實現如下。
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {UITouch *touch = [touches anyObject];CGPoint touchPt = [touch locationInView:self.innerImageView];self.currentTouchPoint = touchPt;NSInteger tapCount = touch.tapCount;switch (tapCount) {case 1:// 延時執行,防止和雙擊事件重疊[self performSelector:@selector(handleSingleTap) withObject:nil afterDelay:0.2];break;case 2:[self handleDoubleTap];break;default:break;}[[self nextResponder] touchesEnded:touches withEvent:event]; }- (void)handleDoubleTap {// 取消單擊事件[NSObject cancelPreviousPerformRequestsWithTarget:self];// 在適應屏幕和原始尺寸之間翻轉圖片的顯示狀態[self toggleStateAnimated:YES]; }圖片的批量處理
在照片的數據模型SGPhotoModel上有一個isSelected屬性來判斷當前圖片是否被選中,通過collectionView的代理方法didUnhighlightItemAtIndexPath:來處理圖片的選中與反選,為了統一點擊事件,將點擊縮略圖進入原圖瀏覽模式的代碼也放到了這里,通過是否是編輯模式來區分,編輯模式由于和工具欄直接相關,因此被記錄在工具欄中,具體實現代碼如下。
- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(nonnull NSIndexPath *)indexPath {SGPhotoCell *cell = (SGPhotoCell *)[collectionView cellForItemAtIndexPath:indexPath];// 如果處于編輯模式,則處理圖片的選中和反選并返回if (self.toolBar.isEditing) {SGPhotoModel *model = self.photoAtIndexHandler(indexPath.row);model.isSelected = !model.isSelected;// 記錄所有選中的圖片數據模型if (model.isSelected) {[self.selectModels addObject:model];} else {[self.selectModels removeObject:model];}cell.model = model;return;}// 如果縮略圖在下載中,則不允許進入原圖瀏覽,hud用于指示下載進度,因此有hud則正在下載if (cell.imageView.hud) return;// 如果縮略圖已經下載完畢,則允許進入原圖瀏覽模式SGPhotoViewController *vc = [SGPhotoViewController new];vc.browser = self;vc.index = indexPath.row;[self.navigationController pushViewController:vc animated:YES]; }更多技術細節可以在GitHub上的源碼中查看,點擊這里前去GitHub下載源碼,歡迎Star和指出不足。
轉載于:https://www.cnblogs.com/aiwz/p/6153999.html
總結
以上是生活随笔為你收集整理的iOS开源照片浏览器框架SGPhotoBrowser的设计与实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: centos7 通过kvm+vnc 实现
- 下一篇: 前端-html实现省份、地市、区县三级联