Search code examples
iosswift3uipageviewcontrolleruipagecontrol

PageViewController - Auto Slide - Swift 3


I am trying to auto-slide the page view controller pages and the page indicator simultaneously. There are 5 pages in my page view controller. The flow works fine from pic 1-5, but I am unable to go back from 5th to 1st image and restart the auto sliding.

Below is my code:

import UIKit

class PageSliderViewController: UIViewController , UIPageViewControllerDataSource, UIPageViewControllerDelegate {

    @IBOutlet weak var pageControl: UIPageControl!

    var pageContainer: UIPageViewController!

    // The pages it contains
    var pages = [UIViewController]()

    // Track the current index
    var currentIndex: Int?
    private var pendingIndex: Int?

    override func viewDidLoad() {
        super.viewDidLoad()

        // Setup the pages
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let page1: UIViewController! = storyboard.instantiateViewController(withIdentifier: "Page1")
        let page2: UIViewController! = storyboard.instantiateViewController(withIdentifier: "Page2")
        let page3: UIViewController! = storyboard.instantiateViewController(withIdentifier: "Page3")
        let page4: UIViewController! = storyboard.instantiateViewController(withIdentifier: "Page4")
        let page5: UIViewController! = storyboard.instantiateViewController(withIdentifier: "Page5")
        pages.append(page1)
        pages.append(page2)
        pages.append(page3)
        pages.append(page4)
        pages.append(page5)
        // Create the page container
        pageContainer = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
        pageContainer.delegate = self
        pageContainer.dataSource = self
        pageContainer.setViewControllers([page1], direction: UIPageViewControllerNavigationDirection.forward, animated: false, completion: nil)

        currentIndex = 0

        // Add it to the view
        view.addSubview(pageContainer.view)

        // Configure our custom pageControl
        self.view.bringSubview(toFront: self.loginButton)
        self.view.bringSubview(toFront: self.signUpButton)
        self.view.bringSubview(toFront: self.pageControl)
        pageControl.numberOfPages = pages.count
        pageControl.currentPage = 0

        Timer.scheduledTimer(timeInterval: 5,
                             target: self,
                             selector: #selector(self.next(_:)),
                             userInfo: nil,
                             repeats: true)

    }

    func next(_ timer: Timer) {
        pageContainer.goToNextPage()
        currentIndex = currentIndex! + 1
        if currentIndex == pageControl.numberOfPages{
            currentIndex = 0
        }
        pageControl.currentPage = currentIndex!
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // MARK: - UIPageViewController delegates

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        let currentIndex = pages.index(of: viewController)!
        if currentIndex == 0 {
            return nil
        }
        let previousIndex = abs((currentIndex - 1) % pages.count)
        return pages[previousIndex]
    }

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        let currentIndex = pages.index(of: viewController)!
        if currentIndex == pages.count-1 {
            return nil
        }
        let nextIndex = abs((currentIndex + 1) % pages.count)
        return pages[nextIndex]
    }

    func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
        pendingIndex = pages.index(of: pendingViewControllers.first!)
    }

    func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
        if completed {
            currentIndex = pendingIndex
            if let index = currentIndex {
                pageControl.currentPage = index
            }
        }
    }
}

extension UIPageViewController {
    func goToNextPage(animated: Bool = true, completion: ((Bool) -> Void)? = nil) {
        if let currentViewController = viewControllers?[0] {
            if let nextPage = dataSource?.pageViewController(self, viewControllerAfter: currentViewController) {
                setViewControllers([nextPage], direction: .forward, animated: animated, completion: completion)
            }
        }
    }
}

Note that through this code the page control goes from 5th to 1st dot but the page is not changing. There is some minor mistake but unable to figure it out. Any help would be greatly appreciated!


Solution

  • I haven't tried to run this code, but my guess is:

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        let currentIndex = pages.index(of: viewController)!
        if currentIndex == pages.count-1 {
            return nil
        }
        let nextIndex = abs((currentIndex + 1) % pages.count)
        return pages[nextIndex]
    }
    

    Arrays are zero-based, so if you are currently looking at page5, you are looking at pages[4] ... and pages.count - 1 equals 4. So, your code is saying:

    if the current page is page5
        return nil
    

    So your func goToNextPage() extension calls for viewControllerAfter and gets nil in return.

    You most likely want to change viewControllerAfter to be:

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        let currentIndex = pages.index(of: viewController)!
    
        // you don't need this, because you are using % modulo operator
        // to keep "nextIndex" within the array bounds
        //if currentIndex == pages.count-1 {
        //    return nil
        //}
    
        let nextIndex = abs((currentIndex + 1) % pages.count)
        return pages[nextIndex]
    }