標籤:
簡介
雖然目前市面上有一些不錯的加密相簿App,但不是內建廣告,就是對上傳的張數有所限制。本文介紹了一個加密相簿的製作過程,該加密相簿將包括多密碼(輸入不同的密碼即可訪問不同的空間,可掩人耳目)、WiFi傳圖、照片檔案加密等功能。目前項目和文章會同時前進,項目的原始碼可以在github上下載。
點擊前往GitHub
概述
上一篇文章主要介紹了照片的儲存、刪除批處理的實現。這篇文章將介紹圖片瀏覽器原圖瀏覽、縮放和滑動切換圖片的實現細節。
圖片縮放的實現總體說明
可以利用UIScrollView的zoom相關屬性和方法來處理視圖的縮放,它支援捏和手勢。在scrollView上有代理方法說明縮放作用於哪個視圖,如下。
- (nullable UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView;
通過下面的屬性可以設定縮放尺度以及當前縮放值。
@property(nonatomic) CGFloat minimumZoomScale; // default is 1.0@property(nonatomic) CGFloat maximumZoomScale; // default is 1.0. must be > minimum zoom scale to enable zooming@property(nonatomic) CGFloat zoomScale; // default is 1.0
可以將適應螢幕的圖片尺寸的scale設定為1.0,最大縮放值取決於原圖尺寸,如果原圖尺寸對應的scale小於5.0則取5.0,否則取原圖尺寸,這樣可以保證小圖也可以被縮放。
在改變圖片尺寸的同時,將scrollView的contentSize進行同樣的設定,這樣可以保證通過滑動到達圖片的不同部分,且左右滑動時先滾動到圖片邊緣再進行圖片切換。
類結構
對於原圖瀏覽,每張圖片都是一個UIScrollView的子類,對圖片單擊會將事件向外傳遞,對應導覽列和工具列的隱藏和顯示狀態翻轉,雙擊會進行原圖狀態(SGImageViewStateOrigin)和適應螢幕狀態的翻轉(SGImageViewStateFit),採用捏合手勢會使得圖片進入中間狀態(SGImageViewStateNone),具體實現如下。
typedef NS_OPTIONS(NSInteger, SGImageViewState) { SGImageViewStateNone = 0, SGImageViewStateFit, SGImageViewStateOrigin};// 用於將單擊事件向外傳遞的block定義typedef void(^SGZoomingImageViewTapHandlerBlock)(void);@interface SGZoomingImageView : UIScrollView@property (nonatomic, assign) SGImageViewState state;// 用於顯示圖片的ImageView@property (nonatomic, strong) UIImageView *innerImageView;// 用於判斷當前顯示的是縮圖還是原圖,用於記憶體最佳化,下文講解@property (nonatomic, assign) BOOL isOrigin;// 單擊事件回調block的setter- (void)setSingleTapHandler:(SGZoomingImageViewTapHandlerBlock)handler;// 將圖片縮放到適應螢幕(圖片寬度等於螢幕寬度)- (void)scaleToFitAnimated:(BOOL)animated;// 將圖片縮放到原始大小- (void)scaleToOriginSize:(BOOL)animated;// 翻轉適應螢幕與原始大小,如果處於中間狀態,則縮放到原圖- (void)toggleState:(BOOL)animated;@end
單擊和雙擊的處理
由於雙擊經過了單擊,因此雙擊之前必定觸發單擊,這裡為了保證雙擊和單擊單獨觸發,有兩種解決方案。
一是通過UIGestureRecognizer的requireGestureRecognizerToFail:方法設定手勢觸發的依賴,定義兩個tap手勢分別需要單擊和雙擊觸發,並且單擊事件要求雙擊失敗才能觸發,當雙擊事件失敗後,才觸發單擊事件。
二是在單擊時不直接執行相關邏輯,而是使用performSelector:::的延時方法,將邏輯執行滯後,接下來如果觸發了雙擊事件,則在雙擊事件裡對之前的延時執行方法進行取消,取消方法為NSObject的類方法,具體實現如下。
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; 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 toggleState:YES];}
縮放處理
每個SGZoomingImageView的image由外部負責傳入,如果按照螢幕寬度來調整imageView,使得照片寬度正好等於螢幕寬度,則是適應螢幕顯示模式,實現代碼如下。
- (void)scaleToFitAnimated:(BOOL)animated { self.state = SGImageViewStateFit; _animationDuration = 0.3f; UIImage *image = self.innerImageView.image; CGFloat imageW = image.size.width; CGFloat imageH = image.size.height; CGSize visibleSize = [UIScreen mainScreen].bounds.size; // 計算等比縮放到圖片寬度等於螢幕寬度 CGFloat scale = visibleSize.width / imageW; imageW = visibleSize.width; imageH = imageH * scale; // 調整contentSize,計算imageView在scrollView上的位置,對於是否使用動畫有兩次調用,因此使用block儲存該代碼塊 void (^ModifyBlock)() = ^{ // 以適應螢幕的顯示模式作為縮放基準 self.zoomScale = 1.0f; self.contentSize = self.bounds.size; CGRect frame = self.innerImageView.frame; frame.size.width = imageW; frame.size.height = imageH; frame.origin.x = (self.contentSize.width - imageW) * 0.5f; frame.origin.y = (self.contentSize.height - imageH) * 0.5f; self.innerImageView.frame = frame; }; if (animated) { [UIView animateWithDuration:_animationDuration animations:^{ ModifyBlock(); }]; } else { ModifyBlock(); }}
根據image的尺寸去顯示,則是原圖模式,實現該模式的方法是先擷取image尺寸,並將imageView的尺寸和contentSize設定為image尺寸,位置為scrollView的contentSize的中心,實現代碼如下。
- (void)scaleToOriginSize:(BOOL)animated { self.state = SGImageViewStateOrigin; UIImage *image = self.innerImageView.image; CGFloat imageW = image.size.width; // 計算原圖時的縮放比 CGFloat scale = imageW / self.bounds.size.width; // 更新scrollView的最大縮放比例為原圖時的縮放比 self.maximumZoomScale = scale; void (^ModifyBlock)() = ^{ self.zoomScale = scale; self.innerImageView.center = CGPointMake(self.contentSize.width * 0.5f, self.contentSize.height * 0.5f); self.contentOffset = CGPointMake((self.contentSize.width - self.bounds.size.width) * 0.5f, (self.contentSize.height - self.bounds.size.height) * 0.5f); }; if (animated) { [UIView animateWithDuration:0.3 animations:^{ ModifyBlock(); }]; } else { ModifyBlock(); }}
圖片切換的原理
上節介紹了顯示每一張圖片的類SGZoomingImageView,要切換圖片,則需要一個分頁的scrollView,來容納每一個SGZoomingScrollView,結構如所示。
這樣的問題在於兩張圖片間沒有間距,為瞭解決這個問題,我們讓scrollView的boundWidth大於螢幕一個d的間距,讓每一個SGZoomingScrollView寬度增加2d,並且設定左右縮排各一d,則可以在每個圖片之間留出d的間距,這時候圖片並不能正對在螢幕上,而是向右偏離了d,因此需要將scrollView的x座標設定為-d,從而對齊螢幕與圖片邊緣,如下。
設照片之間的間距為PhotoGutt,則實現間隔的代碼如下。
原圖瀏覽時需要傳入照片瀏覽器對象browser,根據browser的資料來源來產生scrollView上的多個圖片來實現滾動,下面代碼中的方法為browser的setter,它屬於原圖瀏覽控制器視圖SGPhotoView(繼承了UIScrollView),具體細節在後面的文章中介紹,這裡只是為了說明帶間距圖片的計算過程。
- (void)setBrowser:(SGPhotoBrowser *)browser { _browser = browser; // 通過照片瀏覽器資料來源的block回調獲得照片模型資料個數,關於資料來源的介紹可以在前面的文章中找到。 NSInteger count = browser.numberOfPhotosHandler(); // 擷取螢幕尺寸作為照片顯示的基準,這裡暫時沒有對橫屏適配 CGSize visibleSize = [UIScreen mainScreen].bounds.size; NSMutableArray *imageViews = @[].mutableCopy; // 首先將照片加寬2d CGFloat imageViewWidth = visibleSize.width + PhotoGutt * 2; // 頁面寬度比照片寬度多d,多餘的d為照片間隔,在螢幕外,切換圖片時才能看到 _pageW = imageViewWidth - PhotoGutt; // 計算scrollView總寬度 self.contentSize = CGSizeMake(count * imageViewWidth, 0); for (NSUInteger i = 0; i < count; i++) { SGZoomingImageView *imageView = [SGZoomingImageView new]; SGPhotoModel *model = self.browser.photoAtIndexHandler(i); // 根據資料模型的縮圖URL設定圖片,sg_setImageWithURL:方法可以處理不同的URL類型,對於檔案URL直接載入,網路URL則是通過SDWebImage下載後非同步載入 [imageView.innerImageView sg_setImageWithURL:model.thumbURL]; // 用於標識載入的是否是原圖,為了最佳化記憶體,預先載入縮圖,故為NO imageView.isOrigin = NO; // 計算每一個圖片在scrollView中的位置,這時候圖片的寬度比實際多2d CGRect frame = (CGRect){imageViewWidth * i, 0, imageViewWidth, visibleSize.height}; // CGRectInset計算出左右各向內縮排d的Rect並設定到圖片 imageView.frame = CGRectInset(frame, PhotoGutt, 0); [imageViews addObject:imageView]; [self addSubview:imageView]; // 預設顯示原圖 [imageView scaleToFitAnimated:NO]; } self.imageViews = imageViews;}
經過這樣的計算後,所有的圖片左側都出現了d的黑邊,這就需要將scrollView向左位移-d座標來將黑邊放到螢幕外,也就是將x設定為-d,同時調整scrollView的頁面寬度為螢幕寬度+2d,代碼如下。
這段代碼位於原圖控制器SGPhotoViewController中,用於添加原圖控制器視圖SGPhotoView,並且實現向左位移-d,頁面寬度為螢幕寬度+2d。
SGPhotoView *photoView = [SGPhotoView new];self.photoView = photoView;self.photoView.controller = self;[self.view addSubview:photoView];CGFloat x = -PhotoGutt;CGFloat y = 0;CGFloat w = self.view.bounds.size.width + 2 * PhotoGutt;CGFloat h = self.view.bounds.size.height;self.photoView.frame = CGRectMake(x, y, w, h);
總結
本文主要介紹了圖片縮放的實現細節與帶間距的滑動切換圖片的實現原理,下一篇文章將重點介紹滑動切換圖片的技術細節,項目的源碼可以在文首的中找到,歡迎關注項目後續。
iOS開源加密相簿Agony的實現(六)