1、前言ios
一、微博、資訊類型的APP客戶端應用都會有圖片瀏覽的需求,所以網絡上涌現出大量的第三方圖片瀏覽器插件,不論是用什麼樣的技術,大致的都能知足用戶瀏覽圖片的需求,例如單擊圖片隱藏、雙擊圖片放大、手勢縮放、左右切換以及保存圖片等功能,就如本文要介紹的SDPhotoBrowser,也是我在研究github代碼發現的,而後本身研究了源碼;git
二、原理大致以下,github
(1)點擊圖片,彈出SDPhotoBrowser視圖,此視圖中包含scrollview滾動視圖;瀏覽器
(2)SDPhotoBrowser視圖被添加到window視圖中,並在didMoveToSuperview方法中初始化視圖;網絡
(3)layoutSubviews佈局視圖,並加載currentIndex位置的圖片,即showFirstImage函數;app
(4)關鍵的部分,SDPhotoBrowser提供兩個委託函數,- (UIImage *)photoBrowser:(SDPhotoBrowser *)browser placeholderImageForIndex:(NSInteger)index; - (NSURL *)photoBrowser:(SDPhotoBrowser *)browser highQualityImageURLForIndex:(NSInteger)index;分別獲取指定index的縮略圖或者是高清圖;ide
(5)SDBrowserImageView自定義UIImageView,用於顯示圖片以及進行圖片縮放與清除縮放等操做;函數
三、圖片放大操做步驟,佈局
(1)添加子視圖_zoomingScroolView滾動視圖,並在此滾動視圖上建立_zoomingImageView顯示放大圖片的視圖;測試
(2)執行放大操做,_zoomingScroolView的contentSize是臨時UIImageView的size的兩倍;
(3)全部的視圖從新佈局。
2、存在的問題與個人改進
一、原來的photoClick方法中,獲取sourceView存在比較嚴重的耦合;
(1)我認爲此處應該定義委託方法,執行委託方法獲取視圖比較穩妥,這個問題其實已有人在github中提出,即- (UIView *)photoBrowser:(SDPhotoBrowser *)browser viewWithIndex:(NSInteger)index;獲取當前index的view視圖;(同理showFirstImage方法也有這個問題)
- (void)photoClick:(UITapGestureRecognizer *)recognizer { _scrollView.hidden = YES; _willDisappear = YES; SDBrowserImageView *currentImageView = (SDBrowserImageView *)recognizer.view; NSInteger currentIndex = currentImageView.tag; UIView *sourceView = nil; if ([self.sourceImagesContainerView isKindOfClass:UICollectionView.class]) { UICollectionView *view = (UICollectionView *)self.sourceImagesContainerView; NSIndexPath *path = [NSIndexPath indexPathForItem:currentIndex inSection:0]; sourceView = [view cellForItemAtIndexPath:path]; }else { sourceView = self.sourceImagesContainerView.subviews[currentIndex]; } CGRect targetTemp = [self.sourceImagesContainerView convertRect:sourceView.frame toView:self]; UIImageView *tempView = [[UIImageView alloc] init]; tempView.contentMode = sourceView.contentMode; tempView.clipsToBounds = YES; tempView.image = currentImageView.image; CGFloat h = (self.bounds.size.width / currentImageView.image.size.width) * currentImageView.image.size.height; if (!currentImageView.image) { // 防止 因imageview的image加載失敗 致使 崩潰 h = self.bounds.size.height; } tempView.bounds = CGRectMake(0, 0, self.bounds.size.width, h); tempView.center = self.center; [self addSubview:tempView]; _saveButton.hidden = YES; [UIView animateWithDuration:SDPhotoBrowserHideImageAnimationDuration animations:^{ tempView.frame = targetTemp; self.backgroundColor = [UIColor clearColor]; _indexLabel.alpha = 0.1; } completion:^(BOOL finished) { [self removeFromSuperview]; }]; }
(2)經過委託對象下降耦合,改進以下;
- (void)photoClick:(UITapGestureRecognizer *)recognizer { _scrollView.hidden = YES; _willDisappear = YES; SDBrowserImageView *currentImageView = (SDBrowserImageView *)recognizer.view; NSInteger currentIndex = currentImageView.tag; UIView *sourceView = nil; if ([self.sourceImagesContainerView isKindOfClass:UICollectionView.class]) { if ([self.delegate respondsToSelector:@selector(photoBrowser:viewWithIndex:)]) { sourceView = [self.delegate photoBrowser:self viewWithIndex:currentIndex]; } } else { sourceView = self.sourceImagesContainerView.subviews[currentIndex]; } CGRect targetTemp = [self.sourceImagesContainerView convertRect:sourceView.frame toView:self]; UIImageView *tempView = [[UIImageView alloc] init]; tempView.contentMode = sourceView.contentMode; tempView.clipsToBounds = YES; tempView.image = currentImageView.image; CGFloat h = (self.bounds.size.width / currentImageView.image.size.width) * currentImageView.image.size.height; if (!currentImageView.image) { // 防止 因imageview的image加載失敗 致使 崩潰 h = self.bounds.size.height; } tempView.bounds = CGRectMake(0, 0, self.bounds.size.width, h); tempView.center = self.center; [self addSubview:tempView]; _saveButton.hidden = YES; [UIView animateWithDuration:SDPhotoBrowserHideImageAnimationDuration animations:^{ tempView.frame = targetTemp; self.backgroundColor = [UIColor clearColor]; _indexLabel.alpha = 0.1; } completion:^(BOOL finished) { [self removeFromSuperview]; }]; }
二、- (void)scrollViewDidScroll:(UIScrollView *)scrollView,滾動視圖委託,清除圖片的縮放效果有偏差,好比快速拖動已縮放圖片縮放效果會被清除以及已縮放圖片向左拖動會有很大的概率致使沒法清除縮放效果;
(1)本來做者的意圖是向左或者是向右拖動150的距離後,已縮放圖片清除縮放效果,但我在測試的過程當中向左拖動會清除縮放,向右拖動有機率不會清除縮放;
- (void)scrollViewDidScroll:(UIScrollView *)scrollView { int index = (scrollView.contentOffset.x + _scrollView.bounds.size.width * 0.5) / _scrollView.bounds.size.width; // 有過縮放的圖片在拖動必定距離後清除縮放 CGFloat margin = 150; CGFloat x = scrollView.contentOffset.x; if ((x - index * self.bounds.size.width) > margin || (x - index * self.bounds.size.width) < - margin) { SDBrowserImageView *imageView = _scrollView.subviews[index]; if (imageView.isScaled) { [UIView animateWithDuration:0.5 animations:^{ imageView.transform = CGAffineTransformIdentity; } completion:^(BOOL finished) { [imageView eliminateScale]; }]; } } if (!_willDisappear) { _indexLabel.text = [NSString stringWithFormat:@"%d/%ld", index + 1, (long)self.imageCount]; } [self setupImageOfImageViewForIndex:index]; }
(2)個人改進的思路是當沒有成功切換至下一張圖片時,不會清除已縮放圖片的縮放效果,反之清除;經過visibleZoomingScrollViews保存已經顯示的SDBrowserImageView視圖,經過計算只保留當前顯示在界面上的SDBrowserImageView的縮放效果,而後清除其餘視圖縮放效果;
- (void)scrollViewDidScroll:(UIScrollView *)scrollView { CGRect visibleBounds = _scrollView.bounds; // 當scrollview滾動中止,firstIndex == lastIndex,即當前顯示zoomingImageView的tag值 NSInteger firstIndex = floor((CGRectGetMinX(visibleBounds)) / CGRectGetWidth(visibleBounds)); NSInteger lastIndex = floor((CGRectGetMaxX(visibleBounds)-1) / CGRectGetWidth(visibleBounds)); if (firstIndex < 0) { firstIndex = 0; } if (firstIndex >= self.imageCount) { firstIndex = self.imageCount - 1; } if (lastIndex < 0){ lastIndex = 0; } if (lastIndex >= self.imageCount) { lastIndex = self.imageCount - 1; } // 回收再也不顯示的zoomingImageView NSInteger zoomingImageViewIndex = 0; for (SDBrowserImageView *zoomingImageView in self.visibleZoomingScrollViews) { zoomingImageViewIndex = zoomingImageView.tag; if (zoomingImageViewIndex < firstIndex || zoomingImageViewIndex > lastIndex) { zoomingImageView.transform = CGAffineTransformIdentity; [zoomingImageView eliminateScale]; } } int index = (scrollView.contentOffset.x + _scrollView.bounds.size.width * 0.5) / _scrollView.bounds.size.width; if (!_willDisappear) { _indexLabel.text = [NSString stringWithFormat:@"%d/%ld", index + 1, (long)self.imageCount]; } [self setupImageOfImageViewForIndex:index]; }
三、- (void)didMoveToSuperview執行了兩次,官方文檔Tells the view that its superview changed,意思是當superview改變的時候會執行此方法,也就是說添加子視圖或者是移除子視圖,都會執行;所以下面的代碼邏輯會執行兩遍,移除視圖在執行如下邏輯會形成內存泄露;
- (void)didMoveToSuperview { [self setupScrollView]; [self setupToolbars]; }
所以,能夠經過- (void)didMoveToWindow官網文檔說明以下;
The window
property may be nil
by the time that this method is called, indicating that the receiver does not currently reside in any window. This occurs when the receiver has just been removed from its superview or when the receiver has just been added to a superview that is not attached to a window. Overrides of this method may choose to ignore such cases if they are not of interest.
意思是說,方法被執行後,window對象有可能爲nil,發生此類狀況時,好比視圖已從父視圖中被移除或者是被添加至另一個並不附加任何window的父視圖中。所以能夠經過判斷self.window是否爲空判斷SDPhotoBrowser是被添加仍是被移除window,而咱們須要的是添加至window是初始化view,以下;
- (void)didMoveToWindow { if (self.window) { [self setupScrollView]; [self setupToolbars]; } }
四、因爲collectionView的重用機制致使的問題,沒有顯示在視圖界面上的cell是沒法獲取到的,所以會致使單擊關閉圖片的動畫失效;
個人解決方法,在scrollView切換圖片的同時,經過委託方法- (void)photoBrowser:(SDPhotoBrowser *)browser scrollToItemAtIndex:(NSInteger)index滾動collectionView,確保切換圖片對應的cell被顯示;
// 滾動至指定的indexPath - (void)photoBrowser:(SDPhotoBrowser *)browser scrollToItemAtIndex:(NSInteger)index { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0]; [self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionTop animated:NO]; } // 加載圖片 - (void)setupImageOfImageViewForIndex:(NSInteger)index { SDBrowserImageView *imageView = _scrollView.subviews[index]; self.currentImageIndex = index; // 加載圖片的同時滾動collectionView,確保cell正常顯示; if ([self.delegate respondsToSelector:@selector(photoBrowser:scrollToItemAtIndex:)]) { [self.delegate photoBrowser:self scrollToItemAtIndex:index]; } if (imageView.hasLoadedImage) return; if ([self highQualityImageURLForIndex:index]) { [imageView setImageWithURL:[self highQualityImageURLForIndex:index] placeholderImage:[self placeholderImageForIndex:index]]; } else { imageView.image = [self placeholderImageForIndex:index]; } imageView.hasLoadedImage = YES; [self.visibleZoomingScrollViews addObject:imageView]; }