<b>前言 :</b>
为了博眼球,所以给了个有问题的标题。但如果该文是你通过度娘或是古哥检索出来的那我很明确的告诉你这就是你想要的。想直接看效果而不想听我念经的,可直接拉至篇尾那里有全部代码。
<b>走起 :</b>
之前见腾讯视频、优酷视频的多栏页面做的挺好看的,尤其是顶部那性感的小黄条,于是乎自己写了个(细节处理很完美的设计,可直接拿去用的哦)。https://github.com/ChuckonYin/news-scroll-title
为了充分利用UIPageViewController的一些特性,底部并没有用UIScrollView,但又要监听pageViewControlle的偏移量。由于pageviewcontroller的实现和我们常做的循环轮播原理是一样一样的。所以滑动时监听到的偏移量是极不靠谱的,最后配合UIPageviewcontrollerDelegate花了很大的时间成本才算作出一个一模一样的效果来。(这个就不细说了,感兴趣的话可以下载上面的<a href="https://github.com/ChuckonYin/news-scroll-title">源码</a>)
for subView: UIView in view.subviews {
if subView.isKind(of: UIScrollView.classForCoder()) {
let tempScrollView = subView as? UIScrollView
tempScrollView?.delegate = self
}
}
这里有个很不好处理的是偏移量是“0->375->750->375->0”这种姿态的, 由于不连续,处理起来就诸多麻烦。
既然如此,我们有没有办法改变这个呢?在pageviewcontroller中控制器的逻辑切换是连续的。但视图切换在本质上却是不连续的。我们可不可以通过UIPageViewController的逻辑做一个<b>虚拟偏移量</b>呢?(就像web开发中React.js写出来的不是真实DOM,而是虚拟DOM)。对,就是生成虚拟偏移量。这个能实现的话我想在很多场景我们都是用的上的。下面来说下我的实现思路:
1、我们要知道UIPageViewController有个内嵌的UIScrollView,获取到他,监听到scrollview发生了滚动。具体看上面那段代码。
2、在UIScrollView发生了滚动时,计算出虚拟偏移量。注意我们要的不是contentoffset,此场景下,那个太操蛋了,不可信(陪合UIPageViewControllerDelegate还是能用的,逻辑上极不好操作)。那我们要什么呢?我们通过当前可视的那个viewController的view(取左边距)映射到UIPageViewCOntroller的view上,就能获取偏移量了。而这才是我们真实看到的,想要的偏移量。(<b>核心思想就是利用UIView的convert函数计算真实偏移量</b>)
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageWidth = view.frame.width
for vc in readyViewControllers!{
let p = vc.view.convert(CGPoint(), to: view)
//找出屏幕可视范围的控制器视图
if (p.x) > CGFloat(0.0) && (p.x) < pageWidth{
let estimatePage = (readyViewControllers?.index(of: vc))!
estimateOffSetX = CGFloat(estimatePage) * pageWidth - (p.x)
}
}
//若不是循环,最后一个找不到左边距
if estimateOffSetX >= CGFloat((readyViewControllers?.count)!-1)*pageWidth{
let p = readyViewControllers?[(readyViewControllers?.count)!-1].view.convert(CGPoint(), to: view)
estimateOffSetX = CGFloat((readyViewControllers?.count)!-1) * pageWidth - (p?.x)!
}
// print("矫正前:\(estimateOffSetX)")
scrollDidScroll!(estimateOffSetX)
}
当然我们还有两个问题需要注意:
1)最后一个控制器的再往左滑的时候,左边距是不在可视范围内的。需要去掉如下判断条件,单独处理
if (p.x) > CGFloat(0.0) && (p.x) < pageWidth{
2)细心的你一定发现了上面这句判断条件是不包含0和pageWidth两个临界状态的。那是因为在pageviewcontroller滑动到临界状态,会有view的tihuan和contentoffset的突变,所以必须舍弃。那你可能会说,那还不够精准啊。不急,且看第三步👇
3、pageviewcontroller当我们松手的时候会到达零界点,我们用scrollViewDidEndDecelerating来监听,并做微小的修正
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let pageWidth = view.frame.width
currentPage = Int(round(estimateOffSetX/pageWidth))
if currentPage < 0 {
currentPage = (readyViewControllers?.count)! - 1
}
estimateOffSetX = CGFloat(currentPage)*pageWidth
// print("矫正后:\(estimateOffSetX)")
scrollDidScroll!(estimateOffSetX)
}
👌完事了,如此一来我们能获取到一个虚拟偏移量(从另一个角度来看,这才是真实的),变化过程是这样的:
0->10->30->100->200->300->375->380->400->500->600->750->800->990->1040->1170->1200->1400->1600->1800。
天啊噜,这才是我们想要的啊。也难怪apple的pageviewcontroller并没有暴露scrollview属性,因为不可用啊。这样一改就可用了。
Talk is Cheap,Show you the Code👇:
import UIKit
class CYPageViewController: UIPageViewController, UIPageViewControllerDelegate, UIScrollViewDelegate {
private var tempDelegate: UIPageViewControllerDelegate?
private var currentPage: Int = 0
private var scrollDidScroll: ((CGFloat)->Void)?
private var readyViewControllers: [UIViewController]?
private var estimateOffSetX: CGFloat = 0
override func viewDidLoad() {
super.viewDidLoad()
for subView: UIView in view.subviews {
if subView.isKind(of: UIScrollView.classForCoder()) {
let tempScrollView = subView as? UIScrollView
tempScrollView?.delegate = self
}
}
}
func addListenerWithReadyViewControllers(_ readyViewControllers: [UIViewController], didScroll scrollDidScroll: @escaping (CGFloat)->Void){
self.readyViewControllers = readyViewControllers
self.scrollDidScroll = scrollDidScroll
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageWidth = view.frame.width
for vc in readyViewControllers!{
let p = vc.view.convert(CGPoint(), to: view)
if (p.x) > CGFloat(0.0) && (p.x) < pageWidth{
let estimatePage = (readyViewControllers?.index(of: vc))!
estimateOffSetX = CGFloat(estimatePage) * pageWidth - (p.x)
}
}
//若不是循环,最后一个找不到左边距
if estimateOffSetX >= CGFloat((readyViewControllers?.count)!-1)*pageWidth{
let p = readyViewControllers?[(readyViewControllers?.count)!-1].view.convert(CGPoint(), to: view)
estimateOffSetX = CGFloat((readyViewControllers?.count)!-1) * pageWidth - (p?.x)!
}
// print("矫正前:\(estimateOffSetX)")
scrollDidScroll!(estimateOffSetX)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let pageWidth = view.frame.width
currentPage = Int(round(estimateOffSetX/pageWidth))
if currentPage < 0 {
currentPage = (readyViewControllers?.count)! - 1
}
estimateOffSetX = CGFloat(currentPage)*pageWidth
// print("矫正后:\(estimateOffSetX)")
scrollDidScroll!(estimateOffSetX)
}
}
import UIKit
class ViewController: UIViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
private var pageCtrl: CYPageViewController = CYPageViewController.init(transitionStyle: UIPageViewControllerTransitionStyle.scroll, navigationOrientation: UIPageViewControllerNavigationOrientation.horizontal, options: nil)
lazy var viewControllers: [UIViewController] = {
var arr = Array<UIViewController>()
let color = [UIColor.red, UIColor.green, UIColor.brown, UIColor.yellow]
for i in 0...3{
let vc = UIViewController()
vc.view.backgroundColor = color[i]
arr.append(vc)
}
return arr
}()
override func viewDidLoad() {
super.viewDidLoad()
pageCtrl.view.frame = self.view.bounds
view.addSubview(pageCtrl.view)
pageCtrl.setViewControllers([viewControllers[0]], direction: UIPageViewControllerNavigationDirection.forward, animated: false) { (cp) in
}
pageCtrl.delegate = self
pageCtrl.dataSource = self
pageCtrl.addListenerWithReadyViewControllers(viewControllers) { (x) in
print("修复后的偏移量___\(x)")
}
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let index = viewControllers.index(of: viewController)
if index == 0 {
// return viewControllers[viewControllers.count-1]
return nil;
}
return viewControllers[index!-1]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let index = viewControllers.index(of: viewController)
if index == viewControllers.count-1 {
// return viewControllers[0]
return nil;
}
return viewControllers[index!+1]
}
}
以上是全部代码👆,代码量少copy吧😊😊
<b>写在最后 :</b>
see,其实你只调用了如下一句代码就获取到了你想要的,是不是很nice。即便是使用到当前项目中,污染也是极小极小的。如果你说你用UIScrollView替换了,那我告诉你UITableView、UICollectionView你也可以自己写复用机制用UIScrollView替换,但那样会很low。存在即有价值。
至于OC版的,这几句代码我想你几分钟就能翻译了的
pageCtrl.addListenerWithReadyViewControllers(viewControllers) { (x) in
print("修复后的偏移量___\(x)")
}
网友评论