iOS - PageViewController & SegmentedControl

若要在一個頁面中使用 SegmentedControl 和 PageViewController 來轉換頁面該如何實作呢?

Step 1: 製作畫面

在主頁面上增加控制項 Segmented Control

新增兩個ViewController頁面,

並設置storyboard id 為firstPage, secondPage

Step 2: Reference SegmentedControl

在ViewController.swift中, 設置SegmentedControl物件和點擊事件.

使用UISegmentedControl.selectedSegmentIndex可得知目前所按到的tab為何

Step 3: 設置 PageViewController

設置PageView內容

新增PageViewController

1
var pageController: UIPageViewController!

使用水平滾動的方式變換頁面

1
pageController = UIPageViewController(transitionStyle: .Scroll, navigationOrientation:.Horizontal, options: nil)

設置PageView顯示位置

1
pageController!.view.frame = CGRectMake(0, mPageSegment.frame.origin.y + mPageSegment.frame.height, view.frame.size.width, view.frame.size.height);

設置首頁

1
2
currentPageIndex = 0;
pageController.setViewControllers([viewControllers.objectAtIndex(currentPageIndex) as! UIViewController], direction: .Forward, animated: false) {(isFinished:Bool) -> Void in}

監聽頁面變化 UIPageViewControllerDelegate及頁面資料UIPageViewControllerDataSource

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pageController.delegate = self;
pageController.dataSource = self;

extension ViewController: UIPageViewControllerDataSource {
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {

return nil
}



func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {

return nil
}
}



extension ViewController: UIPageViewControllerDelegate {
func pageViewController(pageViewController: UIPageViewController,

}
}

將PageViewController新增至畫面中

1
2
self.addChildViewController(pageController)
self.view.addSubview(pageController.view)

Step 4: 設置轉換頁面的資料

由左至右滑動時, 也就是往前翻頁, 若前頁的上一頁的頁數小於0時, 則回傳nil
(若目前第二頁時, 往前翻頁為第一頁, 而第一頁的前頁為nil)

1
2
3
4
5
6
7
8
9
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
currentPageIndex = viewController.view.tag
let pageIndex = viewController.view.tag - 1;
if pageIndex < 0 {
return nil
}

return viewControllers[pageIndex] as? UIViewController
}

由右至左滑動時, 也就是往後翻頁, 若後頁的下一頁的頁數大於總頁數時, 則回傳nil
(若目前第一頁時, 往後翻頁為第二頁, 而第二頁的下頁為nil)

1
2
3
4
5
6
7
8
9
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
currentPageIndex = viewController.view.tag
let pageIndex = viewController.view.tag + 1;
if pageIndex > 1 {
return nil
}

return viewControllers[pageIndex] as? UIViewController
}

以上我們完成了PageViewController的控制, 接下來要結合Segmented Controll來控制PageView

Step 5: Segment的變換

對於SegmentControll的變換, 使用SegmentControll.selectedSegmentIndex來改變選取的Tab

在Step 4 的兩個步驟中, 加入 mPageSegment.selectedSegmentIndex = currentPageIndex;

Step 6: 點擊SegmentControll來改變PageView

之前發生過使用點擊來轉換頁面太快時, 會出NSInternalInconsistencyException

原因是切換PageView時設定了轉移動畫, 當動畫還未結束時, 又再一次要求改變PageView所導致.

解決方式:

  • 轉換PageView時, 不設定動畫

    1
    pageController!.setViewControllers([self.viewControllers[index] as! UIViewController], direction: .Reverse, animated: false, completion:nil)
  • 必須等動畫完成後, 才能再轉換PageView

在此我們採用第二種方式, 當點擊SegmentedControll時, 將enabled設為false.
動畫結束後再設定為true.

設定轉換動畫
若當前頁碼小於選擇頁碼時 (往前翻頁)
direction: UIPageViewControllerNavigationDirection.Forward

若當前頁碼大於選擇頁碼時 (往後翻頁)
direction: UIPageViewControllerNavigationDirection.Reverse

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@IBAction func tapSegment(sender: AnyObject) {
mPageSegment.enabled = false;

let index = (sender as! UISegmentedControl).selectedSegmentIndex;
if (currentPageIndex < index) {
pageController!.setViewControllers([self.viewControllers[index] as! UIViewController], direction: .Forward, animated: true, completion:{(isFinished: Bool) in
self.mPageSegment.enabled = true;
})
} else {
pageController!.setViewControllers([self.viewControllers[index] as! UIViewController], direction: .Reverse, animated: true, completion:{(isFinished: Bool) in
self.mPageSegment.enabled = true;
})
}

currentPageIndex = index
}

使用滑動翻頁時, 當動畫結束後, 須將SegmentControll.enabled設為true

1
2
3
4
5
extension ViewController: UIPageViewControllerDelegate {
func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
mPageSegment.enabled = true;
}
}

Source Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import UIKit

class ViewController: UIViewController {
@IBOutlet weak var mPageSegment: UISegmentedControl!
@IBAction func tapSegment(sender: AnyObject) {
mPageSegment.enabled = false;
let index = (sender as! UISegmentedControl).selectedSegmentIndex;

if (currentPageIndex < index) {
pageController!.setViewControllers([self.viewControllers[index] as! UIViewController], direction: .Forward, animated: true, completion:{(isFinished: Bool) in
self.mPageSegment.enabled = true;
})
} else {
pageController!.setViewControllers([self.viewControllers[index] as! UIViewController], direction: .Reverse, animated: true, completion:{(isFinished: Bool) in
self.mPageSegment.enabled = true;
})
}
currentPageIndex = index
}

var pageController: UIPageViewController!
var viewControllers = NSMutableArray()
var currentPageIndex : Int!

override func viewDidLoad() {
super.viewDidLoad()
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let firstViewController = storyboard.instantiateViewControllerWithIdentifier("firstPage")
firstViewController.view.tag = 0;
viewControllers.addObject(firstViewController)

let secondViewController = storyboard.instantiateViewControllerWithIdentifier("secondPage")
secondViewController.view.tag = 1;
viewControllers.addObject(secondViewController)

pageController = UIPageViewController(transitionStyle: .Scroll, navigationOrientation:.Horizontal, options: nil)

pageController!.view.frame = CGRectMake(0, mPageSegment.frame.origin.y + mPageSegment.frame.height, view.frame.size.width, view.frame.size.height);

currentPageIndex = 0;
pageController.setViewControllers([viewControllers.objectAtIndex(currentPageIndex) as! UIViewController], direction: .Forward, animated: false) {(isFinished:Bool) -> Void in

}

pageController.delegate = self;
pageController.dataSource = self;

self.addChildViewController(pageController)
self.view.addSubview(pageController.view)
}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}

extension ViewController: UIPageViewControllerDataSource {
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
currentPageIndex = viewController.view.tag
mPageSegment.selectedSegmentIndex = currentPageIndex;
let pageIndex = viewController.view.tag - 1;
if pageIndex < 0 {
return nil
}
return viewControllers[pageIndex] as? UIViewController
}

func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
currentPageIndex = viewController.view.tag
mPageSegment.selectedSegmentIndex = currentPageIndex;
let pageIndex = viewController.view.tag + 1;
if pageIndex > 1 {
return nil
}
return viewControllers[pageIndex] as? UIViewController
}
}

extension ViewController: UIPageViewControllerDelegate {
func pageViewController(pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
mPageSegment.enabled = true;
}
}

執行結果



作者

Nick Lin

發表於

2016-10-18

更新於

2023-01-18

許可協議


評論