簡介
雖然目前市面上有一些不錯的加密相冊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);
總結
本文主要介紹了圖片縮放的實現細節與帶間距的滑動切換圖片的實現原理,下一篇文章將重點介紹滑動切換圖片的技術細節,項目的源碼可以在文首的下載地址中找到,歡迎關注項目後續。