Swift - 实现图片全屏展示功能(可左右滑动切换图片)
(本文代码已升级至Swift4)
图片的全屏浏览功能在许多 App 中都会用到。比如微信朋友圈里照片默认是显示一张张缩略图,点击后图片会放大至全屏进行查看。本文演示如何实现这个全屏浏览的功能组件。
1,效果图
(1)作为演示,我们首先在页面上创建各个图片的缩略图。
(2)点击任意一张缩略图,则进行全屏展示。如果有多张图片,还可以左右滑动进行切换。同时为了让展示效果更好,这时默认会将导航栏隐藏,点击图片又会显示出导航栏。
(3)全屏浏览时,照片默认会自动缩放以便完整显示。双击则会放大3倍显示。同时使用手势还可以对图片进行拖动、以及放大缩小。
2,组件实现
- 我们创建一个视图控制器(ImagePreviewVC)进行图片全屏展示。点击缩略图时,将图片数组以及默认起始图片索引传入 ImagePreviewVC,并弹出显示。
- ImagePreviewVC 内使用 UICollectionView 来放置图片,这里将 UICollectionView 设为横向滚动并分页,这样就可以左右滑动切换图片。
import UIKit //图片浏览控制器 class ImagePreviewVC: UIViewController { //存储图片数组 var images:[String] //默认显示的图片索引 var index:Int //用来放置各个图片单元 var collectionView:UICollectionView! //collectionView的布局 var collectionViewLayout: UICollectionViewFlowLayout! //页控制器(小圆点) var pageControl : UIPageControl! //初始化 init(images:[String], index:Int = 0){ self.images = images self.index = index super.init(nibName: nil, bundle: nil) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } //初始化 override func viewDidLoad() { super.viewDidLoad() //背景设为黑色 self.view.backgroundColor = UIColor.black //collectionView尺寸样式设置 collectionViewLayout = UICollectionViewFlowLayout() collectionViewLayout.minimumLineSpacing = 0 collectionViewLayout.minimumInteritemSpacing = 0 //横向滚动 collectionViewLayout.scrollDirection = .horizontal //collectionView初始化 collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: collectionViewLayout) collectionView.backgroundColor = UIColor.black collectionView.register(ImagePreviewCell.self, forCellWithReuseIdentifier: "cell") collectionView.delegate = self collectionView.dataSource = self collectionView.isPagingEnabled = true //不自动调整内边距,确保全屏 if #available(iOS 11.0, *) { collectionView.contentInsetAdjustmentBehavior = .never } else { self.automaticallyAdjustsScrollViewInsets = false } self.view.addSubview(collectionView) //将视图滚动到默认图片上 let indexPath = IndexPath(item: index, section: 0) collectionView.scrollToItem(at: indexPath, at: .left, animated: false) //设置页控制器 pageControl = UIPageControl() pageControl.center = CGPoint(x: UIScreen.main.bounds.width/2, y: UIScreen.main.bounds.height - 20) pageControl.numberOfPages = images.count pageControl.isUserInteractionEnabled = false pageControl.currentPage = index view.addSubview(self.pageControl) } //视图显示时 override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) //隐藏导航栏 self.navigationController?.setNavigationBarHidden(true, animated: false) } //视图消失时 override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) //显示导航栏 self.navigationController?.setNavigationBarHidden(false, animated: false) } //隐藏状态栏 override var prefersStatusBarHidden: Bool { return true } //将要对子视图布局时调用(横竖屏切换时) override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() //重新设置collectionView的尺寸 collectionView.frame.size = self.view.bounds.size collectionView.collectionViewLayout.invalidateLayout() //将视图滚动到当前图片上 let indexPath = IndexPath(item: self.pageControl.currentPage, section: 0) collectionView.scrollToItem(at: indexPath, at: .left, animated: false) //重新设置页控制器的位置 pageControl.center = CGPoint(x: UIScreen.main.bounds.width/2, y: UIScreen.main.bounds.height - 20) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } } //ImagePreviewVC的CollectionView相关协议方法实现 extension ImagePreviewVC:UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{ //collectionView单元格创建 func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! ImagePreviewCell let image = UIImage(named: self.images[indexPath.row]) cell.imageView.image = image return cell } //collectionView单元格数量 func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return self.images.count } //collectionView单元格尺寸 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return self.view.bounds.size } //collectionView里某个cell将要显示 func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { if let cell = cell as? ImagePreviewCell{ //由于单元格是复用的,所以要重置内部元素尺寸 cell.resetSize() } } //collectionView里某个cell显示完毕 func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { //当前显示的单元格 let visibleCell = collectionView.visibleCells[0] //设置页控制器当前页 self.pageControl.currentPage = collectionView.indexPath(for: visibleCell)!.item } }
(2)ImagePreviewCell.swift(图片浏览控制器中使用的单元格)
import UIKit class ImagePreviewCell: UICollectionViewCell { //滚动视图 var scrollView:UIScrollView! //用于显示图片的imageView var imageView:UIImageView! override init(frame: CGRect) { super.init(frame: frame) //scrollView初始化 scrollView = UIScrollView(frame: self.contentView.bounds) self.contentView.addSubview(scrollView) scrollView.delegate = self //scrollView缩放范围 1~3 scrollView.maximumZoomScale = 3.0 scrollView.minimumZoomScale = 1.0 //imageView初始化 imageView = UIImageView() imageView.frame = scrollView.bounds imageView.isUserInteractionEnabled = true imageView.contentMode = .scaleAspectFit scrollView.addSubview(imageView) //单击监听 let tapSingle=UITapGestureRecognizer(target:self, action:#selector(tapSingleDid)) tapSingle.numberOfTapsRequired = 1 tapSingle.numberOfTouchesRequired = 1 //双击监听 let tapDouble=UITapGestureRecognizer(target:self, action:#selector(tapDoubleDid(_:))) tapDouble.numberOfTapsRequired = 2 tapDouble.numberOfTouchesRequired = 1 //声明点击事件需要双击事件检测失败后才会执行 tapSingle.require(toFail: tapDouble) self.imageView.addGestureRecognizer(tapSingle) self.imageView.addGestureRecognizer(tapDouble) } //重置单元格内元素尺寸 func resetSize(){ //scrollView重置,不缩放 scrollView.frame = self.contentView.bounds scrollView.zoomScale = 1.0 //imageView重置 if let image = self.imageView.image { //设置imageView的尺寸确保一屏能显示的下 imageView.frame.size = scaleSize(size: image.size) //imageView居中 imageView.center = scrollView.center } } //视图布局改变时(横竖屏切换时cell尺寸也会变化) override func layoutSubviews() { super.layoutSubviews() //重置单元格内元素尺寸 resetSize() } //获取imageView的缩放尺寸(确保首次显示是可以完整显示整张图片) func scaleSize(size:CGSize) -> CGSize { let width = size.width let height = size.height let widthRatio = width/UIScreen.main.bounds.width let heightRatio = height/UIScreen.main.bounds.height let ratio = max(heightRatio, widthRatio) return CGSize(width: width/ratio, height: height/ratio) } //图片单击事件响应 @objc func tapSingleDid(_ ges:UITapGestureRecognizer){ //显示或隐藏导航栏 if let nav = self.responderViewController()?.navigationController{ nav.setNavigationBarHidden(!nav.isNavigationBarHidden, animated: true) } } //图片双击事件响应 @objc func tapDoubleDid(_ ges:UITapGestureRecognizer){ //隐藏导航栏 if let nav = self.responderViewController()?.navigationController{ nav.setNavigationBarHidden(true, animated: true) } //缩放视图(带有动画效果) UIView.animate(withDuration: 0.5, animations: { //如果当前不缩放,则放大到3倍。否则就还原 if self.scrollView.zoomScale == 1.0 { self.scrollView.zoomScale = 3.0 }else{ self.scrollView.zoomScale = 1.0 } }) } //查找所在的ViewController func responderViewController() -> UIViewController? { for view in sequence(first: self.superview, next: { $0?.superview }) { if let responder = view?.next { if responder.isKind(of: UIViewController.self){ return responder as? UIViewController } } } return nil } required init?(coder aDecoder: NSCoder) { super.init(coder:aDecoder) } } //ImagePreviewCell的UIScrollViewDelegate代理实现 extension ImagePreviewCell:UIScrollViewDelegate{ //缩放视图 func viewForZooming(in scrollView: UIScrollView) -> UIView? { return self.imageView } //缩放响应,设置imageView的中心位置 func scrollViewDidZoom(_ scrollView: UIScrollView) { var centerX = scrollView.center.x var centerY = scrollView.center.y centerX = scrollView.contentSize.width > scrollView.frame.size.width ? scrollView.contentSize.width/2:centerX centerY = scrollView.contentSize.height > scrollView.frame.size.height ? scrollView.contentSize.height/2:centerY print(centerX,centerY) imageView.center = CGPoint(x: centerX, y: centerY) } }
3,使用样例
import UIKit class ViewController: UIViewController { let images = ["image1.jpg","image2.jpg","image3.jpg"] override func viewDidLoad() { super.viewDidLoad() //修改导航栏返回按钮文字 let item = UIBarButtonItem(title: "返回", style: .plain, target: self, action: nil) self.navigationItem.backBarButtonItem = item; //生成缩略图 for i in 0..<images.count{ //创建ImageView let imageView = UIImageView() imageView.frame = CGRect(x:20+i*70, y:80, width:60, height:60) imageView.tag = i imageView.contentMode = .scaleAspectFill imageView.clipsToBounds = true imageView.image = UIImage(named: images[i]) //设置允许交互(后面要添加点击) imageView.isUserInteractionEnabled = true self.view.addSubview(imageView) //添加单击监听 let tapSingle=UITapGestureRecognizer(target:self, action:#selector(imageViewTap(_:))) tapSingle.numberOfTapsRequired = 1 tapSingle.numberOfTouchesRequired = 1 imageView.addGestureRecognizer(tapSingle) } } //缩略图imageView点击 func imageViewTap(_ recognizer:UITapGestureRecognizer){ //图片索引 let index = recognizer.view!.tag //进入图片全屏展示 let previewVC = ImagePreviewVC(images: images, index: index) self.navigationController?.pushViewController(previewVC, animated: true) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } }源码下载:hangge_1513.zip
功能改进:双击时以点击位置为中心放大图片
上面的样例中,当我们双击放大图片时,无论点击的位置是哪里,总是以图片中心为起点放大3倍。如果想要实现点击哪个部分就放大哪个部分,只需要修改 ImagePreviewCell 中的双击响应事件代码即可。(下面高亮的为修改的部分)
//图片双击事件响应 @objc func tapDoubleDid(_ ges:UITapGestureRecognizer){ //隐藏导航栏 if let nav = self.responderViewController()?.navigationController{ nav.setNavigationBarHidden(true, animated: true) } //缩放视图(带有动画效果) UIView.animate(withDuration: 0.5, animations: { //如果当前不缩放,则放大到3倍。否则就还原 if self.scrollView.zoomScale == 1.0 { //以点击的位置为中心,放大3倍 let pointInView = ges.location(in: self.imageView) let newZoomScale:CGFloat = 3 let scrollViewSize = self.scrollView.bounds.size let w = scrollViewSize.width / newZoomScale let h = scrollViewSize.height / newZoomScale let x = pointInView.x - (w / 2.0) let y = pointInView.y - (h / 2.0) let rectToZoomTo = CGRect(x:x, y:y, width:w, height:h) self.scrollView.zoom(to: rectToZoomTo, animated: true) }else{ self.scrollView.zoomScale = 1.0 } }) }
写的可以就是不知道个有问题
写的太好了, 不要骄傲哦
航哥, 请问为什么竖屏转横屏和横屏转竖屏的时候都会有问题? 是不是要设置一下中心对齐?
航哥啊,你上面代码是swift3,用到了sequence(first: self.superview, next: { $0?.superview }),我现在的代码还没升级到swift3,怎么实现上述的功能啊
航哥,为什么我的显示的图片都是全屏的?
航哥,你好。你这个是本地图片,能否写一个加载网络图片的呢,图片可以双击放大,手指捏和放大
你好,航哥。如果想单击就返回要怎么做呢?要怎么调用导航的返回方法呢。
航哥,能不能加个双击哪儿,就放大哪儿的功能啊,缩放三倍只能从中心缩放-..-