0

I am trying to jump to a specific page on a button press, I have made a ViewController which contains a ContainerView of the PageViewController, so that I can have permanent buttons on top. From there i do:

@IBAction func jump(_ sender: Any) {
    PageViewController().jump()
}

and here is the Jump function in the PageViewController

func jump() {

    var lastCoinNumber:Int = Manager.shared.coins.index(of: last!)!
    lastNumber = lastCoinNumber

    if let jumpView = viewControllerAtIndex(lastNumber, storyboard: self.storyboard!) {
        self.setViewControllers([jumpView], direction: .forward, animated: false, completion: nil)
    }
}

For some reason, I always get an error at the ViewControllerAtIndex part of the jump function:

Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value . even if I manually choose an index it does the same.

if let firstViewController = viewControllerAtIndex(0, storyboard: self.storyboard!) {    
    self.setViewControllers([firstViewController], direction: .forward, animated: false, completion: nil)
}

Above is how I set up the ViewControllers initialy in viewDidLoad(), and that works fine, if I am essentially doing the same thing, why doesn't it work? Here is how I index the pages and instantiate the viewcontrollers, they use the same single ViewController so a new instance of it is created for each page, otherwise I would have created an array of viewControllers.

func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { if !completed { return }
    DispatchQueue.main.async() {
        self.dataSource = nil
        self.dataSource = self
        self.pageControl.numberOfPages = (Manager.shared.coins.count)
    }
}

func viewControllerAtIndex(_ index: Int, storyboard: UIStoryboard) -> TemplateViewController? {

    if Manager.shared.coins.count == 0 || index >= Manager.shared.coins.count {
        return nil
    }

    let templateViewController = storyboard.instantiateViewController(withIdentifier: "templateController") as! TemplateViewController

    templateViewController.dataObject = Manager.shared.coins[index]
    return templateViewController
}

func indexOfViewController(_ viewController: TemplateViewController) -> Int {
    self.pageControl.currentPage = Manager.shared.coins.index(of: viewController.dataObject)!
    return Manager.shared.coins.index(of: viewController.dataObject)!
}

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

    var index = self.indexOfViewController(viewController as! TemplateViewController)
    if (index == 0) {
        return nil
    }
    index -= 1
    return self.viewControllerAtIndex(index, storyboard: viewController.storyboard!)
}

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

    var index = self.indexOfViewController(viewController as! TemplateViewController)
    if index == -1 {
        return nil
    }
    index += 1
    if index == Manager.shared.coins.count {
        return nil
    }
    return self.viewControllerAtIndex(index, storyboard: viewController.storyboard!)
}

I hope this is enough information, thank you.

Edit:

Coin is a string array, I declare this:

var last = Manager.shared.coins.last
var lastNumber: Int = 0

and then the jump() takes care of the rest. So I am getting the last object, finding its index, and then doing setViewControllers with the index of the last number, which should correspond with viewControllerAtIndex, because that uses the same array to index and load the pages.

for some reason, when I try to do: var lastCoinNumber:Int = Manager.shared.coins.index(of: last) It forces me to use ! because: "Value of optional type 'Array.Index?' (aka 'Optional') not unwrapped; did you mean to use '!' or '?'?"

I'm not sure what I'm doing wrong in that regard, here is how coins is declared:

var coins = [""]

Edit: Code in full, PageViewController:

import UIKit

var pageView = PageViewController()

class PageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {

    //page Control dots
    var pageControl = UIPageControl()
    var last = Manager.shared.coins.last!
    var lastNumber: Int = 0


    var viewControllerArray = [UIViewController()]


    func configurePageControl() {
    // The total number of pages that are available is based on ManagerCoins names array
        pageControl = UIPageControl(frame: CGRect(x: 0,y: 
    UIScreen.main.bounds.maxY - 50,width: UIScreen.main.bounds.width,height: 
    50))
        self.pageControl.numberOfPages = (Manager.shared.coins.count)
        self.pageControl.currentPage = 0
        self.pageControl.tintColor = UIColor.black
        self.pageControl.pageIndicatorTintColor = UIColor.gray
        self.pageControl.currentPageIndicatorTintColor = UIColor.white
        self.view.addSubview(pageControl)
    }

    // MARK: Delegate functions
    func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {

    }

    func viewControllerAtIndex(_ index: Int, storyboard: UIStoryboard) -> TemplateViewController? {

        if Manager.shared.coins.count == 0 || index >= Manager.shared.coins.count {
            return nil
        }

        let templateViewController = storyboard.instantiateViewController(withIdentifier: "templateController") as! TemplateViewController

        templateViewController.dataObject = Manager.shared.coins[index]
        return templateViewController
    }

    func indexOfViewController(_ viewController: TemplateViewController) -> Int {
        self.pageControl.currentPage = Manager.shared.coins.index(of: viewController.dataObject)!
        return Manager.shared.coins.index(of: viewController.dataObject)!
    }

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

        var index = self.indexOfViewController(viewController as! TemplateViewController)
        if (index == 0) {
            return nil
        }
        index -= 1
        return self.viewControllerAtIndex(index, storyboard: viewController.storyboard!)
    }

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

        var index = self.indexOfViewController(viewController as! TemplateViewController)
        if index == -1 {
            return nil
        }
        index += 1
        if index == Manager.shared.coins.count {
            return nil
        }
        return self.viewControllerAtIndex(index, storyboard: viewController.storyboard!)
    }

    func jump() {

        var lastCoinNumber:Int = Manager.shared.coins.index(of: last!)!
        lastNumber = lastCoinNumber

        if let jumpView = viewControllerAtIndex(lastCoinNumber, storyboard: self.storyboard!){
            self.setViewControllers([jumpView], direction: .forward, animated: false, completion: nil)
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        self.delegate = self
        configurePageControl()

        self.dataSource = self

        if let firstViewController = viewControllerAtIndex(0, storyboard: self.storyboard!){

            self.setViewControllers([firstViewController], direction: .forward, animated: false, completion: nil)
        }
    }
}

Edit:

After much research, This seems to be the correct way to jump a page, by calling setViewControllers, the first comment on this answer seems to have the same problem, it works fine for setting the view on viewDidLoad() but trying to programmatically navigate to a page based on the index falls to an error. https://stackoverflow.com/a/24335008/10058239

Lukas Würzburger
  • 6,543
  • 7
  • 41
  • 75
Peter Ruppert
  • 1,067
  • 9
  • 24

1 Answers1

0

I think it's crashing because of for force unwrapping:

var lastCoinNumber:Int = Manager.shared.coins.index(of: last!)!

if coins is a collection you can use .last to get the last element for example:

let numbers = [10, 20, 30, 40, 50]
if let lastNumber = numbers.last {
    print(lastNumber)
}
// Prints "50" 
Let's_Create
  • 2,963
  • 3
  • 14
  • 33
  • Edited question, coins is an array of string and im just using the .last of that array to get the correct number to correctly call the last page in viewControllerAtIndex, if I change the number in setViewControllers to use for example 0 instead of lastCoinNumber, it still does not work, even though its written exactly the same as in viewDidLoad(), any ideas? – Peter Ruppert Aug 05 '18 at 19:01
  • @PeterRuppert Your code makes no sense. You are saying `var lastCoinNumber:Int = Manager.shared.coins.index(of: last!)!` but you have nothing called `last`. Please show us enough code for us to understand what you're doing — especially since that line _is_, you say, the problematic line! – matt Aug 05 '18 at 19:03
  • @Matt I edited the question to show var last = Manager.shared.coins.last so it is getting index of last item in array, and It does work because I can identify the number after the crash for some reason, I.e. if there are 5 items in the array, lastNumber = 4 when I click on it – Peter Ruppert Aug 05 '18 at 19:05
  • @PeterRuppert Okay but now it doesn't make sense in a new way. Where are you saying that? If at the top of the view controller, then you get `last` _once_. It isn't going to magically update itself to refer to the last element constantly. So it is `nil` when you define and `nil` forevermore. I ask you again, hopefully for the last time, to _show your code_. Not snippets. Not what you _think_ is relevant. Not single lines. Your _actual code_, pasted in wholesale. – matt Aug 05 '18 at 19:07
  • @Matt added full code of the pageViewController, so you can see how all of that works now. I took into account your concern and tried declaring last as an empty string, and then doing last = Manager.shared.coins.last inside the jump() function, to make sure that after the button press, last contains the correct item, but still the exact same problem. – Peter Ruppert Aug 05 '18 at 19:23
  • @Matt the number doesnt seem to be the issue, if I setViewControllers in viewDidLoad() with lastNumber, it correctly loads the view onto the last page, but when I try to do this in its own function so that I can jump to the last page, thats when this nil error comes,. – Peter Ruppert Aug 05 '18 at 19:27