iOS詳細寫一個功能完善的PhotoBrowser同時支持GIF(一)
寫一個功能完善的圖片瀏覽器
最終效果 源碼

網絡.gif

本地圖片.gif
本篇文章后完成的效果

效果.gif
首先來列出我們可能會實現的功能
1.基本功能
- 支持雙擊縮小和放大點擊的區域
- 單擊退出
- 支持響應長按手勢
- 支持圖片的捏合實現縮放
- 支持瀏覽的時候顯示頁數(例如 12/40)
- 支持點擊保存圖片
- 支持加載本地圖片
2. 高級功能
- 支持加載網絡圖片
- 支持顯示加載的進度條
- 支持網絡圖片的緩存
- 支持指定下標到特定的圖片
- 支持gif
- 可選擇的顯示每一張圖片的描述文字
清楚了這些可能會實現的功能,接下來就從簡單的開始分布實現
首先處理每一張圖片共有的功能, 比如單擊和雙擊以及縮放, 在本篇中,我們主要是來實現每一張圖片擁有的一些功能
一. 提到單擊和雙擊,大家可能就直接想到了直接在相應的ImageView上添加兩個UITap手勢即可完成, 沒錯可能要實現手勢的響應確實是這么簡單, 但是想想我們是要在雙擊的時候處理縮放, 那么一個難點就是怎樣實現圖片的縮放
- 我們可以在雙擊的時候直接處理ImageView的transform來實現縮放, 而且還可以加上一點動畫讓這個過程更自然, 看上去這還是個可操作易行的方法
- 我們知道UIScrollView本身幫我們處理好了縮放的功能, 使用它來響應雙擊手勢也許也是可行的, 不過現在我還么沒有完全確定, 就是要使用UIScrollView, 因為還有其他的功能沒有分析怎么實現
二. 支持圖片的捏合實現縮放, 要實現這個功能, 看到捏合兩個字, 我們的第一反應,一定是在ImageVIew上添加一個UIPinchGestureRecognizer手勢來處理, 沒錯添加pinch這個手勢確實可以考慮
- 添加UIPinchGestureRecognizer到ImageVIew上, 然后在手勢的響應中對應的處理ImageView的transform實現捏合, 不過,要怎么來處理這個過程中的transform確實還是很麻煩的
- 我們知道UIScrollView本身幫我們處理好了縮放的功能,只需要很簡單的配置就能事項捏合的功能
清楚了上面兩點,選擇UIScrollView來實現圖片的各種手勢效果無疑是簡單的, 所以就選擇了UIScrollView, 所以這樣很自然的就想到了每一張圖片的View層次, 最底層是一個UIView做容器,同時用來成為UIScrollView的代理, 然后在上面添加UIScrollView來響應手勢和縮放, 最后在上面添加ImageView來添加圖片
實現部分
- 新建一個PhotoView類繼承自UIView
- 在PhotoView中依次添加UIScrollView和UIImageView
懶加載子控件.png
- 設置UIScrollView和UIImageView的frame
這里需要注意的是設置UIImageView的frame的時候, 如果你是設置他的frame為UIScrollView的bounds, 那么會出現的情況是進行圖片放大的時候, 因為是對ImageView整體放大, 所以圖片以外的區域也被放大,瀏覽的效果就是這樣的, 不得不吐槽有些app就是這么簡單的處理的
空白區域被放大.gif
而我們希望的imageView的寬高是和設置的圖片寬高相同或者成比例的縮放, 就是說圖片會全部充滿imageView, 效果就是這樣的
空白區域不放大.gif
-
處理UIScrollView實現縮放
要實現縮放, 只需要設置三個地方 1. 設置scrollView的最大最小縮放倍數, 注意最大倍數必須要大于最小倍數(注意不是大于等于) scrollView.maximumZoomScale = 2.0 scrollView.minimumZoomScale = 1.0 2. 設置代理 scrollView.delegate = self 3. 實現縮放的代理方法, 在這個代理方法中返回要縮放的對象, 當然在我們這里就是imageView, 完成這三步后就可以實現捏合的縮放了 func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? { return imageView }恩好完成上面幾步后, 我們就已經完成了圖片的捏合縮放功能, 不過這里有個問題, 在縮小的時候, 我發現圖片沒有居中, 我們肯定希望圖片居中縮放
不能居中縮放.gif
于是在UIScrollView的這個代理方法允許我們監控縮放的過程, 所以我們可以處理imageView的位置func scrollViewDidZoom(scrollView: UIScrollView) { // 居中顯示圖片, 大家可以使用if語句來理解, 不過我發現使用if后居然效果不對(##<>##), 沒有找到原因 let offsetX = (scrollView.zj_width > scrollView.contentSize.width) ? (scrollView.zj_width - scrollView.contentSize.width)*0.5 : 0.0 let offsetY = (scrollView.zj_height > scrollView.contentSize.height) ? (scrollView.zj_height - scrollView.contentSize.height)*0.5 : 0.0 imageView.center = CGPoint(x: scrollView.contentSize.width * 0.5 + offsetX, y: scrollView.contentSize.height * 0.5 + offsetY) }添加完這一小段代碼后效果是這樣的,還是很滿意
居中縮放.gif
-
添加單擊和雙擊手勢, 注意需要解決單擊和雙擊手勢的沖突
let singleTap = UITapGestureRecognizer(target: self, action: #selector(self.handleSingleTap(_:))) singleTap.numberOfTapsRequired = 1 singleTap.numberOfTouchesRequired = 1 let doubleTap = UITapGestureRecognizer(target: self, action: #selector(self.handleDoubleTap(_:))) doubleTap.numberOfTapsRequired = 2 doubleTap.numberOfTouchesRequired = 1 // 允許優先執行doubleTap, 在doubleTap執行失敗的時候執行singleTap // 如果沒有設置這個, 那么將只會執行singleTap 不會執行doubleTap singleTap.requireGestureRecognizerToFail(doubleTap) addGestureRecognizer(singleTap) addGestureRecognizer(doubleTap)單擊手勢的響應, 每一張圖片自身不處理單擊手勢, 我們希望由之后的PhotoBrowser來處理, 所以這里使用了Closure
// 單擊手勢, 給外界處理 func handleSingleTap(ges: UITapGestureRecognizer) { singleTapAction?(gesture: ges) }雙擊手勢處理, 放大或者縮小
if scrollView.zoomScale <= scrollView.minimumZoomScale { // 放大 scrollView.setZoomScale(scrollView.maximumZoomScale, animated: true) } else {// 縮小很簡單, 直接設置縮小到的倍數, 這里我希望縮放到最小 scrollView.setZoomScale(scrollView.minimumZoomScale, animated: true) }這樣就處理好了雙擊的放大和縮小, 但是目前的放大到最大區域的時候, 并不是我們想要的放大點擊區域的效果, 所以放大肯定還需要單獨的處理
固定放大.gif
經過一番的研究, 如下處理效果還比較滿意
if scrollView.zoomScale <= scrollView.minimumZoomScale { // 放大
let location = ges.locationInView(scrollView)
// 放大scrollView.maximumZoomScale倍, 將它的寬高縮小這么多倍
let width = scrollView.zj_width/scrollView.maximumZoomScale
let height = scrollView.zj_height/scrollView.maximumZoomScale
// 這里需要進行一點的數學換算得來
let rect = CGRect(x: location.x * (1 - 1/scrollView.maximumZoomScale), y: location.y * (1 - 1/scrollView.maximumZoomScale), width: width, height: height)
// 這個方法會根據提供的rect來縮放, 如果給的寬高小余scrollView的寬高, 將進行相應的倍數放大的操作, 如果大于, 就會進行縮小到最小操作
scrollView.zoomToRect(rect, animated: true)
} else {// 縮小
scrollView.setZoomScale(scrollView.minimumZoomScale, animated: true)
}
到目前為止, 單張圖片的處理基本就完整了,運行的效果就和最初給的單張相似 如果你只是需要顯示一張圖片, 那么這個圖片瀏覽器就已經很完善了, 當然我相信,你肯定也希望能處理多張圖片, 具體實現將會在下一篇介紹, 歡迎關注
文/ZeroJ(簡書)