為了博眼球,所以給了個(gè)有問(wèn)題的標(biāo)題。但如果該文是你通過(guò)度娘或是古哥檢索出來(lái)的那我很明確的告訴你這就是你想要的。想直接看效果而不想聽(tīng)我念經(jīng)的,可直接拉至篇尾那里有全部代碼。
之前見(jiàn)騰訊視頻、優(yōu)酷視頻的多欄頁(yè)面做的挺好看的,尤其是頂部那性感的小黃條,于是乎自己寫了個(gè)(細(xì)節(jié)處理很完美的設(shè)計(jì),可直接拿去用的哦)。源碼
為了充分利用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)")
}