干貨:蘋果UIPageViewController偏移量contentOffset的bug解決方案

為了博眼球,所以給了個(gè)有問(wèn)題的標(biāo)題。但如果該文是你通過(guò)度娘或是古哥檢索出來(lái)的那我很明確的告訴你這就是你想要的。想直接看效果而不想聽(tīng)我念經(jīng)的,可直接拉至篇尾那里有全部代碼。

之前見(jiàn)騰訊視頻、優(yōu)酷視頻的多欄頁(yè)面做的挺好看的,尤其是頂部那性感的小黃條,于是乎自己寫了個(gè)(細(xì)節(jié)處理很完美的設(shè)計(jì),可直接拿去用的哦)。源碼

IMG_1390.PNG

為了充分利用UIPageViewController的一些特性,底部并沒(méi)有用UIScrollView,但又要監(jiān)聽(tīng)pageViewControlle的偏移量。由于pageviewcontroller的實(shí)現(xiàn)和我們常做的循環(huán)輪播原理是一樣一樣的。所以滑動(dòng)時(shí)監(jiān)聽(tīng)到的偏移量是極不靠譜的,最后配合UIPageviewcontrollerDelegate花了很大的時(shí)間成本才算作出一個(gè)一模一樣的效果來(lái)。(這個(gè)就不細(xì)說(shuō)了,感興趣的話可以下載上面的源碼

  for subView: UIView in view.subviews {
            if subView.isKind(of: UIScrollView.classForCoder()) {
                let tempScrollView = subView as? UIScrollView
                tempScrollView?.delegate = self
            }
        }

這里有個(gè)很不好處理的是偏移量是“0->375->750->375->0”這種姿態(tài)的, 由于不連續(xù),處理起來(lái)就諸多麻煩。
既然如此,我們有沒(méi)有辦法改變這個(gè)呢?在pageviewcontroller中控制器的邏輯切換是連續(xù)的。但視圖切換在本質(zhì)上卻是不連續(xù)的。我們可不可以通過(guò)UIPageViewController的邏輯做一個(gè)<b>虛擬偏移量</b>呢?(就像web開(kāi)發(fā)中React.js寫出來(lái)的不是真實(shí)DOM,而是虛擬DOM)。對(duì),就是生成虛擬偏移量。這個(gè)能實(shí)現(xiàn)的話我想在很多場(chǎng)景我們都是用的上的。下面來(lái)說(shuō)下我的實(shí)現(xiàn)思路:

  • 1、我們要知道UIPageViewController有個(gè)內(nèi)嵌的UIScrollView,獲取到他,監(jiān)聽(tīng)到scrollview發(fā)生了滾動(dòng)。具體看上面那段代碼。

  • 2、在UIScrollView發(fā)生了滾動(dòng)時(shí),計(jì)算出虛擬偏移量。注意我們要的不是contentoffset,此場(chǎng)景下,那個(gè)太操蛋了,不可信(陪合UIPageViewControllerDelegate還是能用的,邏輯上極不好操作)。那我們要什么呢?我們通過(guò)當(dāng)前可視的那個(gè)viewController的view(取左邊距)映射到UIPageViewCOntroller的view上,就能獲取偏移量了。而這才是我們真實(shí)看到的,想要的偏移量。(<b>核心思想就是利用UIView的convert函數(shù)計(jì)算真實(shí)偏移量</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)
            }
        }
        //若不是循環(huán),最后一個(gè)找不到左邊距
        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)
    }

當(dāng)然我們還有兩個(gè)問(wèn)題需要注意:

  • 最后一個(gè)控制器的再往左滑的時(shí)候,左邊距是不在可視范圍內(nèi)的。需要去掉如下判斷條件,單獨(dú)處理
if (p.x) > CGFloat(0.0) && (p.x) < pageWidth{
  • 細(xì)心的你一定發(fā)現(xiàn)了上面這句判斷條件是不包含0和pageWidth兩個(gè)臨界狀態(tài)的。那是因?yàn)樵趐ageviewcontroller滑動(dòng)到臨界狀態(tài),會(huì)有view的tihuan和contentoffset的突變,所以必須舍棄。那你可能會(huì)說(shuō),那還不夠精準(zhǔn)啊。不急,且看第三步??
  • pageviewcontroller當(dāng)我們松手的時(shí)候會(huì)到達(dá)零界點(diǎn),我們用scrollViewDidEndDecelerating來(lái)監(jiān)聽(tīng),并做微小的修正
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)
    }

??完事了,如此一來(lái)我們能獲取到一個(gè)虛擬偏移量(從另一個(gè)角度來(lái)看,這才是真實(shí)的),變化過(guò)程是這樣的:
0->10->30->100->200->300->375->380->400->500->600->750->800->990->1040->1170->1200->1400->1600->1800。
天啊嚕,這才是我們想要的啊。也難怪apple的pageviewcontroller并沒(méi)有暴露scrollview屬性,因?yàn)椴豢捎冒?。這樣一改就可用了。

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)
           }
       }
       //若不是循環(huán),最后一個(gè)找不到左邊距
       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("修復(fù)后的偏移量___\(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,其實(shí)你只調(diào)用了如下一句代碼就獲取到了你想要的,是不是很nice。即便是使用到當(dāng)前項(xiàng)目中,污染也是極小極小的。如果你說(shuō)你用UIScrollView替換了,那我告訴你UITableView、UICollectionView你也可以自己寫復(fù)用機(jī)制用UIScrollView替換,但那樣會(huì)很low。存在即有價(jià)值。
至于OC版的,這幾句代碼我想你幾分鐘就能翻譯了的

 pageCtrl.addListenerWithReadyViewControllers(viewControllers) { (x) in
            print("修復(fù)后的偏移量___\(x)")
        }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容