UIPageViewController dots not showing - ios

Everything working fine but dots not showing. My code is...
import UIKit
class MyPageViewController: UIPageViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
dataSource = self
if let firstViewController = orderedViewControllers.first {
setViewControllers([firstViewController],
direction: .forward,
animated: true,
completion: nil)
}
}
private(set) lazy var orderedViewControllers: [UIViewController] = {
return [self.newColoredViewController(color: ""),
self.newColoredViewController(color: "Second"),
self.newColoredViewController(color: "Third")]
}()
private func newColoredViewController(color: String) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil) .
instantiateViewController(withIdentifier: "\(color)ViewController")
}
}
// MARK: UIPageViewControllerDataSource
extension MyPageViewController: UIPageViewControllerDataSource {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.firstIndex(of: viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
guard previousIndex >= 0 else {
return nil
}
guard orderedViewControllers.count > previousIndex else {
return nil
}
return orderedViewControllers[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController,
viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.firstIndex(of: viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
let orderedViewControllersCount = orderedViewControllers.count
guard orderedViewControllersCount != nextIndex else {
return nil
}
guard orderedViewControllersCount > nextIndex else {
return nil
}
return orderedViewControllers[nextIndex]
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int {
setupPageControl()
return orderedViewControllers.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int {
return 0
}
private func setupPageControl() {
let appearance = UIPageControl.appearance()
appearance.pageIndicatorTintColor = UIColor.darkGray
appearance.currentPageIndicatorTintColor = UIColor.red
appearance.backgroundColor = UIColor.black
}
}

You are using the wrong version of those functions. 
In order to show UIPageControl, you need to implement two optional datasource methods. Just return the whole number of pages for presentationCount and the initially selected index for presentationIndex
func presentationCount(for pageViewController: UIPageViewController) -> Int {
setupPageControl()
return orderedViewControllers.count
}
func presentationIndex(for pageViewController: UIPageViewController) -> Int {
return 0
}

you should add page view controller data source and number of pages :
pageControl.numberOfPages = 3

Related

How to optimize UIPageViewController to have re-using mechanism similar to tableView.dequeueReusableCell(withIdentifier:for:)?

So far, most of the UIPageViewController tutorial I encounter, will create UIViewController on the fly, when being swiped to desired page.
Example of UIPageViewController, without optimization
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var contentView: UIView!
let dataSource = ["View Controller One", "View Controller Two", "View Controller Three", "View Controller Four"]
var currentViewControllerIndex = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
configurePageViewController()
}
func configurePageViewController() {
guard let pageViewController = storyboard?.instantiateViewController(withIdentifier: String(describing: CustomPageViewController.self)) as? CustomPageViewController else {
return
}
pageViewController.delegate = self
pageViewController.dataSource = self
addChild(pageViewController)
pageViewController.didMove(toParent: self)
pageViewController.view.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(pageViewController.view)
let views: [String: Any] = ["pageView": pageViewController.view]
contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[pageView]-0-|", options: NSLayoutConstraint.FormatOptions(rawValue: 0), metrics: nil, views: views))
contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[pageView]-0-|", options: NSLayoutConstraint.FormatOptions(rawValue: 0), metrics: nil, views: views))
guard let startingViewController = detailViewControllerAt(index: currentViewControllerIndex) else {
return
}
pageViewController.setViewControllers([startingViewController], direction: .forward, animated: true)
}
func detailViewControllerAt(index: Int) -> DataViewController? {
if index >= dataSource.count || dataSource.count == 0 {
return nil
}
guard let detailViewController = storyboard?.instantiateViewController(withIdentifier: String(describing: DataViewController.self)) as? DataViewController else {
return nil
}
detailViewController.index = index
detailViewController.displayText = dataSource[index]
return detailViewController
}
}
extension ViewController: UIPageViewControllerDelegate, UIPageViewControllerDataSource {
func presentationIndex(for pageViewController: UIPageViewController) -> Int {
print ("presentationIndex \(currentViewControllerIndex)")
return currentViewControllerIndex
}
func presentationCount(for pageViewController: UIPageViewController) -> Int {
print ("presentationCount \(dataSource.count)")
return dataSource.count
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let dataViewController = viewController as? DataViewController
guard var currentIndex = dataViewController?.index else {
return nil
}
print ("viewControllerBefore \(currentIndex)")
if (currentIndex == 0) {
return nil
}
currentIndex -= 1
currentViewControllerIndex = currentIndex
return detailViewControllerAt(index: currentIndex)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let dataViewController = viewController as? DataViewController
guard var currentIndex = dataViewController?.index else {
return nil
}
print ("viewControllerBefore \(currentIndex)")
if currentIndex == dataSource.count-1 {
return nil
}
currentIndex += 1
currentViewControllerIndex = currentIndex
return detailViewControllerAt(index: currentIndex)
}
}
However, I hardly see any example, on how to optimize UIPageViewController, to handle large number of pages.
Re-creating new UIViewController for every page doesn't seem like a optimal solution as it will be slow. It also take up unnecessary memory space, as most of the offscreen UIViewController is not visible.
How can we have something similar to tableView.dequeueReusableCell(withIdentifier:for:) ?
Can we re-use those previous created, currently not visible UIViewController?
Any code example, or tutorial resource on how to implement this correctly, is very much appreciated. Thank you.
Creating controllers on the fly can cause some glitches, so I typically have some array like var viewControllers: [UIViewController] where I store my pages
and implement UIPageViewControllerDataSource
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let index = viewControllers.firstIndex(of: viewController), index > 0 else {
return nil
}
currentViewControllerIndex = index - 1
return viewControllers[currentViewControllerIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let index = viewControllers.firstIndex(of: viewController), index < viewControllers.count - 1 else {
return nil
}
currentViewControllerIndex = index + 1
return viewControllers[currentViewControllerIndex]
}
To reduce memory usage you can just deallocate view of the view controller on page change:
var currentViewControllerIndex = 0 {
didSet {
let range = currentViewControllerIndex - 1...currentViewControllerIndex + 1
for (offset, element) in viewControllers.enumerated() {
if range ~= offset {
element.loadViewIfNeeded()
} else {
element.view = nil
}
}
}
}

uipageviewcontroller disable right swipe

I have a uipageviewcontroller with four individual view controllers in it. I want to disable the right swipe action for the first view controller and disable the left swipe action for the fourth view controller. When the app launches, I want the user to only be able to swipe left and not right, because swiping right, takes the user to the fourth view controller and swiping left on the fourth view controller takes the user to the first view controller. Here is my code.
class TutorialPageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
weak var tutorialDelegate: TutorialPageViewControllerDelegate?
lazy var VCArr: [UIViewController] = {
return [self.VCInstance(name: "FirstVC"),
self.VCInstance(name: "SecondVC"),
self.VCInstance(name: "ThirdVC"),
self.VCInstance(name: "FourthVC")]
}()
private func VCInstance(name: String) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: name)
}
override func viewDidLoad() {
super.viewDidLoad()
let pageControl = UIPageControl.appearance()
//Customizing
pageControl.pageIndicatorTintColor = UIColor.lightGray
pageControl.currentPageIndicatorTintColor = UIColor(red:0.23, green:0.73, blue:1.00, alpha:1.0)
dataSource = self
delegate = self
self.dataSource = self
self.delegate = self
if let FirstVC = VCArr.first {
setViewControllers([FirstVC], direction: .forward, animated: true, completion: nil)
tutorialDelegate?.tutorialPageViewController(tutorialPageViewController: self, didUpdatePageCount: VCArr.count)
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
for view in self.view.subviews {
if view is UIScrollView {
view.frame = UIScreen.main.bounds
} else if view is UIPageControl {
view.backgroundColor = UIColor.clear
}
}
}
public func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = VCArr.index(of: viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
guard previousIndex >= 0 else {
return VCArr.last
}
guard VCArr.count > previousIndex else {
return nil
}
return VCArr[previousIndex]
}
public func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = VCArr.index(of: viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
guard nextIndex < VCArr.count else {
return VCArr.first
}
guard VCArr.count > nextIndex else {
return nil
}
return VCArr[nextIndex]
}
public func presentationCount(for pageViewController: UIPageViewController) -> Int {
return VCArr.count
}
public func presentationIndex(for pageViewController: UIPageViewController) -> Int {
guard let firstViewController = viewControllers?.first,
let firstViewControllerIndex = VCArr.index(of: firstViewController) else {
return 0
}
return firstViewControllerIndex
}
}
extension TutorialPageViewController {
func pageViewController(pageViewController: UIPageViewController,
didFinishAnimating finished: Bool,
previousViewControllers: [UIViewController],
transitionCompleted completed: Bool) {
if let firstViewController = viewControllers?.first,
let index = VCArr.index(of: firstViewController) {
tutorialDelegate?.tutorialPageViewController(tutorialPageViewController: self, didUpdatePageIndex: index)
}
}
}
Basically you need to change following lines
guard previousIndex >= 0 else {
return nil
}
guard nextIndex < VCArr.count else {
return nil
}
----- OR -----
Change viewControllerAfter as
public func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = VCArr.index(of: viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
guard nextIndex < VCArr.count else {
return nil
}
guard VCArr.count > nextIndex else {
return nil
}
return VCArr[nextIndex]
}
Change ViewControllerBefore as
public func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = VCArr.index(of: viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
guard previousIndex >= 0 else {
return nil
}
guard VCArr.count > previousIndex else {
return nil
}
return VCArr[previousIndex]
}
You can use this extension for disable all direction swipe :
extension UIPageViewController {
var isPagingEnabled: Bool {
get {
var isEnabled: Bool = true
for view in view.subviews {
if let subView = view as? UIScrollView {
isEnabled = subView.isScrollEnabled
}
}
return isEnabled
}
set {
for view in view.subviews {
if let subView = view as? UIScrollView {
subView.isScrollEnabled = newValue
}
}
}
}
}
and call this:
pageCtrl.isPagingEnabled = false

iOS / Swift 3: UIView too big within UIPageController

I embedded a PageController within a ContainerView.
Why are the Views I display in the PageController not scaled to fit the PageController? How can I achieve "scale to fit"?
(At least the PageController itself adapts its size to the ContainerView)
My classes are:
import UIKit
class YellowController : UIViewController
{
}
class GreenController : UIViewController
{
}
//from a tutorial:
class PageController : UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate
{
// from stackoverflow to avoid the black box at the bottom of the pagecontroller
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
for view in self.view.subviews
{
if view is UIScrollView
{
view.frame = UIScreen.main.bounds
}
else
{
view.backgroundColor = UIColor.clear
}
}
}
lazy var VCArray: [UIViewController] =
{
return [self.VCInstance(name: "InfoPage"),
self.VCInstance(name: "FragePage"),
self.VCInstance(name: "AntwortPage") ]
}()
private func VCInstance (name: String) -> UIViewController
{
let VCC = UIStoryboard(name: "Lerneinheit", bundle: nil).instantiateViewController(withIdentifier: name)
return VCC
}
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
self.delegate = self
if let InfoPage = VCArray.first
{
setViewControllers([InfoPage], direction: .forward, animated: true, completion: nil)
}
}
public func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController?
{
guard let viewControllerIndex = VCArray.index(of: viewController)
else
{
return nil
}
if (viewControllerIndex<1)
{
return nil
}
return VCArray[viewControllerIndex - 1]
}
public func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController?
{
guard let viewControllerIndex = VCArray.index(of: viewController)
else
{
return nil
}
if viewControllerIndex == VCArray.count-1
{
return nil
}
return VCArray[viewControllerIndex + 1]
}
public func presentationCount(for pageViewController: UIPageViewController) -> Int
{
return VCArray.count
}
public func presentationIndex(for pageViewController: UIPageViewController) -> Int
{
guard let firstViewController = viewControllers?.first, let firstViewControllerIndex = VCArray.index(of: firstViewController)
else
{
return 0
}
return firstViewControllerIndex
}
IB:
Result wth the yellow view beeing cut instead of scaled to fit the PageController:
Constraints for the yellow view:
The code below is redundant, remove it:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
for view in self.view.subviews
{
if view is UIScrollView
{
view.frame = UIScreen.main.bounds
}
else
{
view.backgroundColor = UIColor.clear
}
}
}

Change specific page UiPageViewController

I have a Page view controller (see below), what i want to achieve is to go to the FirstVC from ThirdVC and only from that!! with a function or a button, can anyone help me?
Here you can see the code of my PageVC
import UIKit
class PageVC: UIPageViewController,UIPageViewControllerDataSource, UIPageViewControllerDelegate{
lazy var VCArr: [UIViewController] = {
return [self.VCInstance(name: "FirstVC"), self.VCInstance(name: "SecondVC"), self.VCInstance(name: "ThirdVC")]
}()
private func VCInstance(name: String) -> UIViewController {
return UIStoryboard(name: "Main", bundle:nil).instantiateViewController(withIdentifier: name)
}
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
self.delegate = self
if let firstVC = VCArr.first {
setViewControllers([firstVC], direction: .forward, animated: true, completion: nil)
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
for view in self.view.subviews {
if view is UIScrollView {
view.frame = UIScreen.main.bounds
} else if view is UIPageControl {
view.backgroundColor = UIColor.clear
}
}
}
public func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = VCArr.index(of: viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
guard previousIndex >= 0 else{
return VCArr.last
}
guard VCArr.count > previousIndex else {
return nil
}
return VCArr[previousIndex]
}
public func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = VCArr.index(of: viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
guard nextIndex < VCArr.count else{
return VCArr.first
}
guard VCArr.count > nextIndex else {
return nil
}
return VCArr[nextIndex]
}
public func presentationCount(for pageViewController: UIPageViewController) -> Int {
return VCArr.count
}
public func presentationIndex(for pageViewController: UIPageViewController) -> Int {
guard let firstViewController = viewControllers?.first, let firstViewControllerIndex = VCArr.index(of: firstViewController) else{
return 0
}
return firstViewControllerIndex
}
}
Ok i found a solution here:
How do I change the UIPageViewController from WITHIN one of the UIViewControllers that is part of the UIPageViewController?
Simply i added this function to PageVC:
func nextPageWithIndex(index: Int)
{
let nextWalkthroughVC = VCArr[index]
setViewControllers([nextWalkthroughVC], direction: .forward, animated: true, completion: nil)
}
Then in the ThirdVC view controller i added in my function:
let parent = self.parent as! PageVC
parent.nextPageWithIndex(index: 0)
Where 0 is the index of the view controller that i want to show
And works perfectly!

Error: Page Control jump twice with single page swap

With the following code, the page control has jumped twice instead of once during a single page swap. I need to add the UIPageViewController as subview as below, how can I make the pagecontrol act right? Thank you!
import UIKit
class ViewController: UIViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate{
var pageViewController: UIPageViewController!
let pagesArray = ["Page1ViewController", "Page2ViewController","Page3ViewController"]
let pageControl : UIPageControl = UIPageControl(frame: CGRectMake(100, 600, 200, 20))
func viewControllerAtIndex(index: Int) -> UIViewController? {
let vc = storyboard?.instantiateViewControllerWithIdentifier (pagesArray[index])
return vc
}
func pageViewController(pageViewController:UIPageViewController,
viewControllerBeforeViewController
viewController: UIViewController) -> UIViewController?{
if var index = pagesArray.indexOf(viewController.restorationIdentifier!){
if index > 0 {
index--
self.pageControl.currentPage = index
return viewControllerAtIndex(index)
}
}
return nil
}
func pageViewController(pageViewController:UIPageViewController,
viewControllerAfterViewController
viewController: UIViewController) -> UIViewController?{
if var index = pagesArray.indexOf(viewController.restorationIdentifier!){
if index < pagesArray.count - 1 {
index++
return viewControllerAtIndex(index)
}
}
return nil
}
override func viewDidLoad() {
super.viewDidLoad()
pageControl.numberOfPages = 3
pageControl.currentPage = 0
pageControl.tintColor = UIColor.redColor()
pageControl.pageIndicatorTintColor = UIColor.blackColor()
pageControl.currentPageIndicatorTintColor = UIColor.orangeColor()
if let vc = storyboard?.instantiateViewControllerWithIdentifier("MyPageViewController")
{
self.addChildViewController(vc)
self.view.addSubview(vc.view)
self.view.addSubview(pageControl)
pageViewController = vc as! UIPageViewController
pageViewController.dataSource = self
pageViewController.delegate = self
pageViewController.setViewControllers([viewControllerAtIndex(0)!], direction: .Forward, animated: true, completion: nil)
pageViewController.didMoveToParentViewController(self)
}
}
}
try this
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController?
{
let identifier = viewController.restorationIdentifier
self.index = self.identifiers.indexOfObject(identifier!)
if index == identifiers.count - 1 {
return nil
}
//increment the index to get the viewController before the current one
self.index = self.index + 1
return self.viewControllerAtIndex(self.index)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController?
{
let identifier = viewController.restorationIdentifier
self.index = self.identifiers.indexOfObject(identifier!)
if index == 0 {
return nil
}
//decrement the index to get the viewController before the current one
self.index = self.index - 1
return self.viewControllerAtIndex(self.index)
}
func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int
{
return self.identifiers.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int
{
return 0
}

Resources