干货:苹果UIPageViewController偏移量contentOffset的bug解决方案

为了博眼球,所以给了个有问题的标题。但如果该文是你通过度娘或是古哥检索出来的那我很明确的告诉你这就是你想要的。想直接看效果而不想听我念经的,可直接拉至篇尾那里有全部代码。

之前见腾讯视频、优酷视频的多栏页面做的挺好看的,尤其是顶部那性感的小黄条,于是乎自己写了个(细节处理很完美的设计,可直接拿去用的哦)。源码

IMG_1390.PNG

为了充分利用UIPageViewController的一些特性,底部并没有用UIScrollView,但又要监听pageViewControlle的偏移量。由于pageviewcontroller的实现和我们常做的循环轮播原理是一样一样的。所以滑动时监听到的偏移量是极不靠谱的,最后配合UIPageviewcontrollerDelegate花了很大的时间成本才算作出一个一模一样的效果来。(这个就不细说了,感兴趣的话可以下载上面的源码

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

当然我们还有两个问题需要注意:

  • 最后一个控制器的再往左滑的时候,左边距是不在可视范围内的。需要去掉如下判断条件,单独处理
if (p.x) > CGFloat(0.0) && (p.x) < pageWidth{
  • 细心的你一定发现了上面这句判断条件是不包含0和pageWidth两个临界状态的。那是因为在pageviewcontroller滑动到临界状态,会有view的tihuan和contentoffset的突变,所以必须舍弃。那你可能会说,那还不够精准啊。不急,且看第三步👇
  • 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)")
        }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容