swift之图片浏览器

start.gif

1.通过collectionView设置


image.png

在ViewController这个类里面展示九宫格

创建UICollectionView,UICollectionViewCell

import UIKit
//标示�ID
private let cellID = "cellID"
//间隔
private let margin : CGFloat = 10

class ViewController: UIViewController {
    //collecitonView
    lazy var collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: PhotoCellFlowLayout())
    //存放图片url的数组
    var urlArr : [URL] = [URL]()


extension ViewController {
    private func setupUI() {
        view.addSubview(collectionView)
        collectionView.frame = view.bounds
        collectionView.dataSource = self
        collectionView.delegate = self
        collectionView.backgroundColor = UIColor.white
        collectionView.register(PhotoCell.self, forCellWithReuseIdentifier: cellID)
    }
}


extension ViewController : UICollectionViewDataSource{
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return urlArr.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        
        let cell  = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath) as! PhotoCell

        cell.url = urlArr[indexPath.item]
    
        return cell
    }
}

//在同一个类里面写个UICollectionViewCell类
//自定义cell
class PhotoCell: UICollectionViewCell {
    //设置cell的属性为url
    @objc var url : URL?{
        didSet{
           guard let picUrl = url else { return }
            //下载图片,并设置图片
            pictureImageView.sd_setImage(with: picUrl, placeholderImage: UIImage(named: ""), options: [], completed: nil)
            
        }
    }
    //图片属性
    var pictureImageView : UIImageView = UIImageView()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func setupUI() {
        pictureImageView.frame = contentView.bounds
        contentView.addSubview(pictureImageView)
        pictureImageView.contentMode = .scaleAspectFill
        pictureImageView.clipsToBounds = true
    }
}


//在同一个类里面写个UICollectionViewFlowLayout类
//自定义布局
class PhotoCellFlowLayout : UICollectionViewFlowLayout{
    override func prepare() {
        super.prepare()
        //三个item
        let width = (UIScreen.main.bounds.width - 4 * margin)/3
        let heigth = width
        //item的大小
        itemSize = CGSize(width: width, height: heigth)
        //行间距
        minimumLineSpacing = margin
        //竖间距
        minimumInteritemSpacing = margin
        //上下左右间距
        sectionInset = UIEdgeInsets(top: 0, left: margin, bottom: 0, right: margin)
        //竖滚动条
        collectionView?.showsVerticalScrollIndicator = false
        //横滚动条
        collectionView?.showsHorizontalScrollIndicator = false
    
    }
}

展示大图的控制器PhotoBrowerViewController

  • 也使用UICollectionView来展示
  • 同时也在这个类里面,写UICollectionViewFlowLayout
  • 自定义UICollectionViewCell
  • 自定义cell里面设置一个UIScrollView,UIScrollView里面设置一个UIImageView比如长图需要滚动,同时给图片增加手势,让cell拥有一个代理对象,然后UICollectionView实现代理,即使执行关闭按钮点击事件

import UIKit
import SnapKit
import SVProgressHUD

private let photoBrowerCellID = "photoBrowerCellID"
class PhotoBrowerViewController: UIViewController {

    lazy var collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: PhotoBrowerCellFlowLayout())
    lazy var saveBtn = UIButton(bgColor: UIColor.lightGray, font: 14, title: "保存")
    lazy var closeBtn = UIButton(bgColor: UIColor.lightGray, font: 14, title: "关闭")
    var urlArr : [URL] = [URL]()
    var indexpath : IndexPath
    
    //构造函数,传进图片数组和下标
    init(indexPath:IndexPath,urlArr:[URL]) {
        
        self.urlArr = urlArr
        self.indexpath = indexPath
        //控制器的构造函数要重写父类的这个方法
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func loadView() {
        super.loadView()
        //屏幕尺寸增加20的宽度。这里是为了item的间距,到时候cell的srollview还要减去20
        view.frame.size.width += 20
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        setupUI()
        //滚动到对应cell
        collectionView.scrollToItem(at:indexpath as IndexPath, at: .left, animated: false)

    }
    
}

extension PhotoBrowerViewController{
    func setupUI() {
        
        view.addSubview(collectionView)
        view.addSubview(saveBtn)
        view.addSubview(closeBtn)
        //保存按钮的约束
        saveBtn.snp.makeConstraints { (make) in
            make.right.equalTo(-40)
            make.bottom.equalTo(-20)
            make.size.equalTo(CGSize(width: 90, height: 35))
        }
        //关闭按钮的约束
        closeBtn.snp.makeConstraints { (make) in
            make.left.equalTo(20)
            make.bottom.equalTo(-20)
            make.size.equalTo(CGSize(width: 90, height: 35))
        }
    
        collectionView.frame = view.bounds
        //给保存按钮增加点击事件
        collectionView.dataSource = self
        collectionView.delegate = self
        //注册cell
        collectionView.register(PhotoBrowerCollectionViewCell.self, forCellWithReuseIdentifier: photoBrowerCellID)
        //给关闭按钮增加点击事件
        closeBtn.addTarget(self, action: #selector(PhotoBrowerViewController.closeBtnClick), for: .touchUpInside)
        //给保存按钮增加点击事件
        saveBtn.addTarget(self, action: #selector(PhotoBrowerViewController.saveBtnClick), for: .touchUpInside)
    }
}

extension PhotoBrowerViewController {
    //关闭按钮点击事件
    @objc func closeBtnClick() {
        dismiss(animated: true, completion: nil)
    }
    
    //保存图片按钮点击事件
    @objc func saveBtnClick() {
        //获取展示的cell
        let cell = collectionView.visibleCells.first as! PhotoBrowerCollectionViewCell
        //获取cell中的图片
        let picture = cell.pictureImageView.image
        //校验
        guard let pictureImage = picture else { return }
        //保存到相册
        UIImageWriteToSavedPhotosAlbum(pictureImage, self, #selector(image(image:didFinishSavingWithError:contextInfo:)), nil)
    }
    
    @objc private func image(image : UIImage, didFinishSavingWithError error : NSError?, contextInfo context : AnyObject) {
        // 1.判断是否有错误
        let message = error == nil ? "保存成功" : "保存失败"
        // 2.显示保存结果
        SVProgressHUD.showInfo(withStatus: message)
    }
}

extension PhotoBrowerViewController : UICollectionViewDataSource,UICollectionViewDelegate{
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        print(urlArr.count)
        return urlArr.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        
        let  cell  = collectionView.dequeueReusableCell(withReuseIdentifier: photoBrowerCellID, for: indexPath) as! PhotoBrowerCollectionViewCell
        //图片的url
        cell.url = urlArr[indexPath.item]
        //设置代理 手势代理
        cell.delegate = self as PhotoBrowerCollectionViewCellDelegate
        
        return cell
    }
}

//cell 手势图片点击的代理
extension PhotoBrowerViewController : PhotoBrowerCollectionViewCellDelegate {
    func pictureClick() {
        //关闭按钮点击
        closeBtnClick()
    }
}

//在同一个类里面写个UICollectionViewFlowLayout类
//布局
class PhotoBrowerCellFlowLayout: UICollectionViewFlowLayout {

    override func prepare() {
        super.prepare()
        //设置item的大小
        itemSize = (collectionView?.frame.size)!
        //行间距
        minimumLineSpacing = 0
        //列间距
        minimumInteritemSpacing = 0
        //滚动方向
        scrollDirection = .horizontal
        //竖滚动条
        collectionView?.showsVerticalScrollIndicator = false
        //横滚动条
        collectionView?.showsHorizontalScrollIndicator = false
        //分页
        collectionView?.isPagingEnabled = true
    }
}

cell的自定义


import UIKit
import SDWebImage

protocol PhotoBrowerCollectionViewCellDelegate : NSObjectProtocol {
    func pictureClick()
}

class PhotoBrowerCollectionViewCell: UICollectionViewCell {
    
        @objc var url : URL? {
            didSet{
                guard let picUrl = url else { return }
                
                let picture = SDWebImageManager.shared().imageCache?.imageFromCache(forKey:picUrl.absoluteString)
                
                guard let pictureImage = picture else { return }
                // 3.计算imageView的位置和尺寸
                calculateImageFrame(image: pictureImage)

                pictureImageView.sd_setImage(with: url, placeholderImage: UIImage(named: ""), options: [], progress: { (current, total, _) in
                    
                }) { (image, _, _, _) in
                    if image != nil {
                        self.calculateImageFrame(image:image!)
                        self.pictureImageView.image = image
                    }
                }
                
            }
        }
    
        /// 计算imageView的frame和显示位置
        private func calculateImageFrame(image : UIImage) {
            // 1.计算位置
            let imageWidth = UIScreen.main.bounds.width
            let imageHeight = image.size.height / image.size.width * imageWidth
            
            // 2.设置frame
            pictureImageView.frame = CGRect(x: 0, y: 0, width: imageWidth, height: imageHeight)
            // 3.设置contentSize
            scrollView.contentSize = CGSize(width: imageWidth, height: imageHeight)
            
            // 4.判断是长图还是短图
            if imageHeight < UIScreen.main.bounds.height { // 短图
                // 设置偏移量
                let topInset = (UIScreen.main.bounds.height - imageHeight) * 0.5
                scrollView.contentInset = UIEdgeInsets(top: topInset, left: 0, bottom: 0, right: 0)
            } else { // 长图
                scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
            }
        }
    
        //scrollView
        var scrollView = UIScrollView()
        //图片
        var pictureImageView = UIImageView()
        //代理
        var delegate : PhotoBrowerCollectionViewCellDelegate?
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            setupUI()
        }
        
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        func setupUI() {
            contentView.addSubview(scrollView)
            scrollView.addSubview(pictureImageView)
            scrollView.frame = bounds
            scrollView.frame.size.width -= 20
           
            //给图片添加手势
            pictureImageView.isUserInteractionEnabled = true
            let tap = UITapGestureRecognizer(target: self, action: #selector(pictureClick))
            pictureImageView.addGestureRecognizer(tap)
        }
    
        @objc func pictureClick() {
            delegate?.pictureClick()
        }
}

PhotoBrowerAnmation这个类来执行动画

  • 注意,这里只是拿对应图片做动画,动画结束后要把做动画的图片移除

  • 申明两个协议。弹出动画协议,消失动画协议

  • 弹出动画的协议为了拿到动画开始的起始位置,动画结束的结束位置,哪个图片做动画

  • 消失动画的协议为了拿到做动画的图片,拿到做动画图片的下标

  • 最后去到对应控制器去拿到这两个动画协议里面需的东西

import UIKit

class PhotoBrowerAnmation: NSObject {
    
    var isPresented : Bool = false
    
    // 定义indexPath和presentedDelegate属性
    var indexPath : NSIndexPath?
    // 定义弹出的presentedDelegate
    var presentedDelegate : PhotoBrowserPresentedDelegate?
    // 定义消失的DismissDelegate
    var dismissDelegate : PhotoBrowserDismissDelegate?

}

protocol PhotoBrowserPresentedDelegate : NSObjectProtocol {
    // 1.提供弹出的imageView
    func imageForPresent(indexPath : NSIndexPath) -> UIImageView
    
    // 2.提供弹出的imageView的frame
    func startRectForPresent(indexPath : NSIndexPath) -> CGRect
    
    // 3.提供弹出后imageView的frame
    func endRectForPresent(indexPath : NSIndexPath) -> CGRect
}

protocol PhotoBrowserDismissDelegate : NSObjectProtocol {
    // 1.提供退出的imageView
    func imageViewForDismiss() -> UIImageView
    
    // 2.提供退出的indexPath
    func indexPathForDismiss() -> NSIndexPath
}




extension PhotoBrowerAnmation : UIViewControllerTransitioningDelegate {
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        isPresented = true
        return self
    }
    
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        isPresented = false
        return self
    }
}

extension PhotoBrowerAnmation : UIViewControllerAnimatedTransitioning {

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 1
    }

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        
        isPresented ? presentedView(using: transitionContext) : dissmissedView(using: transitionContext)
    }
    
    
    func presentedView(using transitionContext: UIViewControllerContextTransitioning) {
        
        guard let presentedDelegate = presentedDelegate, let indexPath = indexPath else {
            return
        }
        
        // 1.取出弹出的View
        let presentedView = transitionContext.view(forKey: .to)
        transitionContext.containerView.addSubview(presentedView!)
        //取图片做动画
        let tempImageView = presentedDelegate.imageForPresent(indexPath: indexPath)
        transitionContext.containerView.addSubview(tempImageView)
        
        tempImageView.frame = presentedDelegate.startRectForPresent(indexPath: indexPath)
        
        // 3.执行动画
        presentedView!.alpha = 0.0
        transitionContext.containerView.backgroundColor = UIColor.black
        UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
            tempImageView.frame = presentedDelegate.endRectForPresent(indexPath: indexPath)
        }) { (_) in
            transitionContext.containerView.backgroundColor = UIColor.clear
            transitionContext.completeTransition(true)
            tempImageView.removeFromSuperview()
            presentedView!.alpha = 1.0
        }

    }
    
    
    func dissmissedView(using transitionContext: UIViewControllerContextTransitioning) {
        
        guard let dismissDelegate = dismissDelegate, let presentedDelegate = presentedDelegate else {
            return
        }
        
        // 1.取出消失的View
        let dismissView = transitionContext.view(forKey: .from)
        dismissView?.alpha = 0
        //取出图片做动画
        let tempImageView = dismissDelegate.imageViewForDismiss()
        transitionContext.containerView.addSubview(tempImageView)
        
        // 2.执行动画
        UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
            tempImageView.frame = presentedDelegate.startRectForPresent(indexPath: dismissDelegate.indexPathForDismiss())

        }) { (_) in
            tempImageView.removeFromSuperview()
            dismissView?.removeFromSuperview()
            transitionContext.completeTransition(true)
        }

    }
}

  • 弹出动画的执行代理是viewController,就是那个九宫格的那个控制器,因为它可以拿到图片,拿到下标,拿到起始动画开始的起始位置


extension ViewController : UICollectionViewDelegate {
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        
        let photoVC = PhotoBrowerViewController(indexPath: indexPath, urlArr: urlArr)
        //自定义动画
        photoVC.modalPresentationStyle = .custom
        //设置谁负责动画
        photoVC.transitioningDelegate = animation
        // 4.设置photoBrowserAnimator的相关属性
        animation.indexPath = indexPath as NSIndexPath
        //设置弹出动画的代理
        animation.presentedDelegate = self
        //设置消失动画的代理
        animation.dismissDelegate = (photoVC as PhotoBrowserDismissDelegate)
        //弹出控制器
        present(photoVC, animated: true, completion: nil)
    }
    
}


// MARK:- 用于提供动画的内容
extension ViewController : PhotoBrowserPresentedDelegate {
    func imageForPresent(indexPath: NSIndexPath) -> UIImageView {
        // 1.创建用于做动画的UIImageView
        let imageView = UIImageView()
        
        // 2.设置imageView属性
        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true
        
        // 3.设置图片
        imageView.sd_setImage(with: urlArr[indexPath.item], placeholderImage: UIImage(named: "empty_picture"))
        
        return imageView
    }
    
    func startRectForPresent(indexPath: NSIndexPath) -> CGRect {
        // 1.取出cell
        guard let cell = collectionView.cellForItem(at: indexPath as IndexPath) else {
            return CGRect(x: collectionView.bounds.width * 0.5, y: UIScreen.main.bounds.height + 50, width: 0, height: 0)
        }
        
        // 2.计算转化为UIWindow上时的frame
        let startRect = collectionView.convert(cell.frame, to: UIApplication.shared.keyWindow)
        
        
        return startRect
    }
    
    func endRectForPresent(indexPath: NSIndexPath) -> CGRect {
        // 1.获取indexPath对应的URL
        let url = urlArr[indexPath.item]
        
        // 2.取出对应的image
        var image = SDWebImageManager.shared().imageCache!.imageFromDiskCache(forKey: url.absoluteString)
        
        if image == nil {
            image = UIImage(named: "empty_picture")
        }
        
        // 3.根据image计算位置
        let screenW = UIScreen.main.bounds.width
        let screenH = UIScreen.main.bounds.height
        let imageH = screenW / image!.size.width * image!.size.height
        var y : CGFloat = 0
        if imageH < screenH {
            y = (screenH - imageH) * 0.5
        } else {
            y = 0
        }
        
        return CGRect(x: 0, y: y, width: screenW, height: imageH)
    }
}

  • 消失动画的执行代理是PhotoBrowerViewController,就展示大图的控制器

extension PhotoBrowserController : PhotoBrowserDismissDelegate {
    func imageViewForDismiss() -> UIImageView {
        // 1.创建UIImageView对象
        let tempImageView = UIImageView()
        
        // 2.设置属性
        tempImageView.contentMode = .ScaleAspectFill
        tempImageView.clipsToBounds = true
        
        // 3.设置图片
        let cell = collectionView.visibleCells()[0] as! PhotoBrowserCell
        tempImageView.image = cell.imageView.image
        tempImageView.frame = cell.scrollView.convertRect(cell.imageView.frame, toCoordinateSpace: UIApplication.sharedApplication().keyWindow!)
        
        return tempImageView
    }
    
    func indexPathForDismiss() -> NSIndexPath {
        return collectionView.indexPathsForVisibleItems()[0]
    }
}

代码留给你

https://gitee.com/lanyingwei/codes/rj1w83inc6h74oydmsugz34

  • 备注:

如果有不足或者错误的地方还望各位读者批评指正,可以评论留言,笔者收到后第一时间回复。

QQ/微信:2366889552 /lan2018yingwei。

简书号:凡尘一笑:[简书]

感谢各位观众老爷的阅读,如果觉得笔者写的还凑合,可以关注或收藏一下,不定期分享一些好玩的实用的demo给大家。

文/凡尘一笑(简书作者)

著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”

推荐阅读更多精彩内容

  • 一 、 前言 图像浏览器功能是提供给用户查看应有的信息图像,不重复的循环查看。像这种功能常见于 App 的首页轮播...
    NetWork小贱阅读 306评论 0 3
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 9,593评论 1 27
  • 因为要结局swift3.0中引用snapKit的问题,看到一篇介绍Xcode8,swift3变化的文章,觉得很详细...
    uniapp阅读 3,313评论 0 12
  • 缘起 那时,我想要一个这样的图片浏览器: 从小图进入大图浏览时,使用转场动画 可加载网络图片,且过渡自然,不阻塞操...
    囧书阅读 6,527评论 35 107
  • 一、本次复盘基本情况 (一)复盘主题 (二)基本信息 复盘时间: 复盘地点: 参加人员: 用时: (三)概况简述 ...
    张大可ke阅读 725评论 2 0
  • 念佛随缘。随缘念佛。
    玄妙_70c4阅读 51评论 0 0
  • 我想画一组多肉
    3J自由自在阅读 92评论 0 3
  • 昨天晚上坐一夜的车,今早上5点半到达北京,7点半到达王东华教授办公室,终于见到了王东华教授,王教授的热情,睿智与大...
    悟宝_ad67阅读 73评论 0 0
  • 发现问题,做出改变时很难,如果我们太注重改变的结果,压力会很大,这时我们可以缩小目标,对自己的行为做出一小步的改变。
    蔡爱春阅读 79评论 0 0