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] } }