先看运行效果:
1. UIPageViewController简介
它是iOS 5.0之后提供的一个分页控件可以实现图片轮播效果和翻书效果.使用起来也很简单方便.先来了解一下UIPageViewController,新建工程查看官方示例Demo,新建完成直接运行起来
在RootViewController里面可以看到其实现的核心代码
override func viewDidLoad() {
super.viewDidLoad()
self.pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
self.pageViewController!.delegate = self
//设置子控制器,并且指定翻页的方向
let startingViewController: DataViewController = self.modelController.viewControllerAtIndex(0, storyboard: self.storyboard!)!
let viewControllers = [startingViewController]
self.pageViewController!.setViewControllers(viewControllers, direction: .forward, animated: false, completion: {done in })
//设置数据源代理
self.pageViewController!.dataSource = self.modelController
//添加子控制器和子控制器视图
self.addChildViewController(self.pageViewController!)
self.view.addSubview(self.pageViewController!.view)
// Set the page view controller's bounds using an inset rect so that self's view is visible around the edges of the pages.
var pageViewRect = self.view.bounds
if UIDevice.current().userInterfaceIdiom == .pad {
pageViewRect = pageViewRect.insetBy(dx: 40.0, dy: 40.0)
}
self.pageViewController!.view.frame = pageViewRect
//完成添加子控制器
self.pageViewController!.didMove(toParentViewController: self)
}
在初始化方法中我们可以设置不同的参数使其展现不同的样式
self.pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
各个参数的大概说明
第一个参数transitionStyle
为一个枚举值,展现不同的样式
public enum UIPageViewControllerTransitionStyle : Int {
case pageCurl // Navigate between views via a page curl transition.
case scroll // Navigate between views by scrolling.
}
-
pageCurl
:样式运行结果可以看出该分页的效果类似翻书. 应用场景:小说阅读之类的它来实现应该是个不错的选择,如下
-
scroll
图片的轮播效果,如下图. 应用场景:比如图片浏览器的实现,demo中的图片浏览器就是使用这种样式来实现的,
第二各参数navigationOrientation
,也是一个枚举
public enum UIPageViewControllerNavigationOrientation : Int {
case horizontal
case vertical
}
第三个参数options
,是一个字典,实现图片浏览器的时候给它赋值之后可以看到图片左右滑动的时候两张图片之间的间隔
2. UIPageViewController的数据源代理
/**
返回前一页控制器,viewController指当前显示的控制器,如果返回为nil说明滑动到头了
*/
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
var index = self.indexOfViewController(viewController as! DataViewController)
if (index == 0) || (index == NSNotFound) {
return nil
}
index -= 1
return self.viewControllerAtIndex(index, storyboard: viewController.storyboard!)
}
/**
返回前一页控制器,viewController指当前显示的控制器,如果返回为nil说明滑动到尾了
*/
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
var index = self.indexOfViewController(viewController as! DataViewController)
if index == NSNotFound {
return nil
}
index += 1
if index == self.pageData.count {
return nil
}
return self.viewControllerAtIndex(index, storyboard: viewController.storyboard!)
}
2. 图片浏览器的实现代码
import UIKit
class ScanViewController: UIViewController {
//:MARK - 属性
///选中照片的索引
private let selectedIndex:Int
///图片的url字符串数组
private let urls:[URL]
init(selectIndex: Int, urls: [URL]) {
self.selectedIndex = selectIndex
self.urls = urls
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
setUpUI()
NotificationCenter.default().addObserver(self, selector: #selector(ScanViewController.tapAction), name: "notify", object: nil)
}
deinit {
NotificationCenter.default().removeObserver(self)
}
func tapAction() {
dismiss(animated: true, completion: nil)
}
}
extension ScanViewController {
@objc private func setUpUI() {
view.backgroundColor = UIColor.black()
//1 实例化分页控制器
let pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: [UIPageViewControllerOptionInterPageSpacingKey:20])
pageViewController.dataSource = self
let viewer = PhotoViewController(photoIndex: selectedIndex, photoUrl: urls[selectedIndex], count: urls.count)
pageViewController.setViewControllers([viewer], direction: .forward, animated: false, completion: nil)
view.addSubview(pageViewController.view)
addChildViewController(pageViewController)
pageViewController.didMove(toParentViewController: self)
view.gestureRecognizers = pageViewController.gestureRecognizers
let tap = UITapGestureRecognizer(target: self, action: #selector(ScanViewController.tapAction))
tap.numberOfTapsRequired = 1
view.addGestureRecognizer(tap)
}
}
extension ScanViewController: UIPageViewControllerDataSource {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
//从控制器取出当前照片的索引
var index = (viewController as! PhotoViewController).photoIndex
//判断是否到头
index += 1
if index >= urls.count {
return nil
}
return PhotoViewController(photoIndex: index, photoUrl:urls[index], count: urls.count)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
//从控制器取出当前照片的索引
var index = (viewController as! PhotoViewController).photoIndex
//判断是否到头
if index == NSNotFound {
return nil
}
index -= 1
if index <= 0 {
return nil
}
return PhotoViewController(photoIndex: index, photoUrl:urls[index], count: urls.count)
}
}
图片浏览器页面PhotoViewController.swift
import UIKit
import SDWebImage
class PhotoViewController: UIViewController {
//:MARK - 属性和控件
private lazy var scrollview = UIScrollView()
private lazy var imageView = UIImageView()
var photoIndex: Int
var photoUrl:URL?
var count: Int
var recordImage:UIImage?
///是否双击图片
var isClick:Bool = false
init(photoIndex: Int, photoUrl:URL, count: Int) {
self.photoIndex = photoIndex
self.photoUrl = photoUrl
self.count = count
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
setupIU()
}
@objc private func setupIU() {
view.addSubview(scrollview)
scrollview.addSubview(imageView)
scrollview.backgroundColor = UIColor.clear()
view.addSubview(titleLabel)
self.titleLabel.text = "\(photoIndex)/\(count)"
scrollview.delegate = self
scrollview.maximumZoomScale = 4.0
scrollview.minimumZoomScale = 0.5
scrollview.showsVerticalScrollIndicator = false
scrollview.showsHorizontalScrollIndicator = false
imageView.isUserInteractionEnabled = true
let tapTwo = UITapGestureRecognizer(target: self, action: #selector(PhotoViewController.handleDouble(recongnizer:)))
tapTwo.numberOfTapsRequired = 2
imageView.addGestureRecognizer(tapTwo)
let tapOne = UITapGestureRecognizer(target: self, action: #selector(PhotoViewController.handleOneClick(recongnizer:)))
tapOne.numberOfTapsRequired = 1
imageView.addGestureRecognizer(tapOne)
//当没有检测到双击 或者 检测双击失败,单击才有效
tapOne.require(toFail: tapTwo)
loadImage()
}
func handleOneClick(recongnizer:UITapGestureRecognizer) {
if isClick == true {
UIView.animate(withDuration: 0.3, animations: {
self.scrollview.zoomScale = 0
self.setImageViewPosition(image: self.recordImage!)
})
isClick = false
return
}
NotificationCenter.default().post(name: "notify" as NSNotification.Name, object: nil)
}
/// 双击图片的处理
func handleDouble(recongnizer:UITapGestureRecognizer) {
let status = recongnizer.state
if isClick == false {
switch status {
case .began: break
case .changed: break
case .cancelled: break
case .ended:
//以点击的点为中心,放大图片
let point = recongnizer.location(in: recongnizer.view)
let zoom = scrollview.zoomScale > 0 ? true : false
let scale = zoom ? scrollview.maximumZoomScale : scrollview.minimumZoomScale
UIView.animate(withDuration: 0.3, animations: {
self.scrollview.zoomScale = scale
if zoom {
var x = point.x * scale - self.scrollview.bounds.size.width/2
let maxX = self.scrollview.contentSize.width - self.scrollview.bounds.size.width
let minX = CGFloat.leastNormalMagnitude
x = x > maxX ? maxX : x
x = x < minX ? minX : x
var y = point.y * scale - self.scrollview.bounds.size.height * 0.5
let maxY = self.scrollview.contentSize.height - self.scrollview.bounds.size.height
let minY = CGFloat.leastNormalMagnitude
y = y > maxY ? maxY : y
y = y < minY ? minY : y
self.scrollview.contentOffset = CGPoint(x: x, y: y)
}
})
isClick = true
default:
break
}
}else{
switch status {
case .began: break
case .changed: break
case .cancelled: break
case .ended:
UIView.animate(withDuration: 0.3, animations: {
self.scrollview.zoomScale = 0
self.setImageViewPosition(image: self.recordImage!)
})
isClick = false
default:
break
}
}
}
func loadImage() {
imageView.sd_setImage(with: photoUrl) { (image, error, _, _) in
guard let image = image else {
return
}
//设置图像视图的大小
self.setImageViewPosition(image: image)
self.recordImage = image
}
}
func setImageViewPosition(image:UIImage) {
let size = imageSizeWithScreen(image: image)
imageView.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
scrollview.frame = UIScreen.main().bounds
scrollview.contentSize = size
if size.height < scrollview.bounds.size.height {
titleLabel.isHidden = false
imageView.frame.origin.y = (scrollview.bounds.size.height - size.height) * 0.5
}else{
titleLabel.isHidden = true
}
}
func imageSizeWithScreen(image:UIImage) -> CGSize {
var size = UIScreen.main().bounds.size
size.height = image.size.height * size.width / image.size.width
return size
}
private lazy var titleLabel:UILabel = {
let tip = UILabel()
tip.frame = CGRect(x: (screenWidth - 100) * 0.5, y: 20, width: 100, height: 20)
tip.textColor = UIColor.white()
tip.textAlignment = NSTextAlignment.center
return tip
}()
}
extension PhotoViewController:UIScrollViewDelegate {
//指定缩放UIScrolleView时,缩放UIImageView来适配
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
//缩放后让图片显示到屏幕中间
func scrollViewDidZoom(_ scrollView: UIScrollView) {
let originalSize = scrollView.bounds.size;
let contentSize = scrollView.contentSize;
let offsetX = originalSize.width > contentSize.width ? (originalSize.width-contentSize.width)/2 : 0
let offsetY = originalSize.height > contentSize.height ? (originalSize.height-contentSize.height)/2 : 0
self.imageView.center = CGPoint(x: contentSize.width/2+offsetX, y: contentSize.height/2+offsetY)
}
}