写一个功能完善的图片浏览器
最终效果 源码
网络.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(简书)