API JSON & UIPageViewController optional Nil

Okay, I am trying to work out a pageview slider that retrieves images url from an API using this tutorial as a base.

I have already coded the asynchronous downloading of images and appending it to the pageImages array. That part works fine.

  • Using View Controller from storyboard programmatically in a Page View Controller
  • iOS: UIPageViewController - Use button to jump to next page
  • Set timer to UIPageViewController
  • How do I disable the inifinite loop of ViewControllers?
  • Swift: UIPageViewController - Load separate views
  • topLayoutGuide in child view controller
  • However, I received an error when I am trying to run the app in the simulator :

    fatal error: unexpectedly found nil while unwrapping an Optional value

    For some reason, fetching the JSON objects and populating the pageImages Array happens after I call my helper method viewControllerAtIndex.

    my code

    class ViewController: UIViewController, UIPageViewControllerDataSource {
    
    var pageViewController: UIPageViewController!
    var pageImages = NSArray()
    var jsonData = NSArray()
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        ###THIS EXECUTES AFTER MY HELPER METHOD viewControllerAtIndex
        self.fetchPublishers()
    
        self.pageViewController = self.storyboard?.instantiateViewControllerWithIdentifier("PageViewController") as! UIPageViewController
    
        self.pageViewController.dataSource = self
    
        var startVC = self.viewControllerAtIndex(0) as ContentViewController
        var viewControllers = NSArray(object: startVC)
    
        self.pageViewController.setViewControllers(viewControllers as [AnyObject], direction: .Forward, animated: true, completion: nil)
    
        self.pageViewController.view.frame = CGRectMake(0, 30, self.view.frame.width, self.view.frame.size.height)
    
        self.addChildViewController(self.pageViewController)
        self.view.addSubview(self.pageViewController.view)
        self.pageViewController.didMoveToParentViewController(self)
    }
    
    func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
        var vc = viewController as! ContentViewController
        var index = vc.pageIndex as Int
    
        if (index == 0 || index == NSNotFound) {
            return nil
        }
    
        index--
        return self.viewControllerAtIndex(index)
    }
    
    func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
    
        var vc = viewController as! ContentViewController
        var index = vc.pageIndex as Int
    
        if (index == NSNotFound) {
            return nil
        }
    
        index++
    
        if (index == self.pageImages.count) {
            return nil
        }
    
        return self.viewControllerAtIndex(index)
    
    }
    
    func viewControllerAtIndex(index: Int) -> ContentViewController
    {
        if ((self.pageImages.count == 0) || (index >= self.pageImages.count)) {
            return ContentViewController()
        }
    
        var vc: ContentViewController = self.storyboard?.instantiateViewControllerWithIdentifier("ContentViewController") as! ContentViewController
    
        vc.imageFile = self.pageImages[index] as! String
        vc.pageIndex = index
    
        return vc
    
    }
    
    func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int
    {
        return self.pageImages.count
    }
    
    func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int
    {
        return 0
    }
    
    func fetchPublishers () {
    
        var req:Request = Alamofire.request(OauthToken.Router.ReadPublishers(""))
    
        var aClosure =  {(req:NSURLRequest, response:NSHTTPURLResponse?, JSON:AnyObject?, error:NSError?) -> Void in
    
            if (error != nil) {
                println(error)
                println("Couldn't connection to server.")
            } else {
    
              dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
    
                self.jsonData = JSON as! NSArray!
    
                var baseUrl: String = "http://0.0.0.0:3000"
                self.pageImages = JSON!.valueForKey("photo_url") as! NSArray!
    
                ###Prints/executes after
                println(self.pageImages)
    
              }
    
            }
    
        }
    
        req.responseJSON(aClosure)
    
    }
    
    } 
    

    ContentViewController

    class ContentViewController: UIViewController {
    
    @IBOutlet weak var imageView: UIImageView!
    
    var pageIndex: Int!
    var imageFile: String!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        ####THE ERROR HAPPENS HERE
        self.imageView.image = UIImage(named: self.imageFile)
    }
    
    }
    

    How can I fix this?

    Solutions Collect From Internet About “API JSON & UIPageViewController optional Nil”

    Things are happening out of sequence because, to quote from the AlamoFire github page: “Networking in Alamofire is done asynchronously.” That means that the code in aClosure is not executed until the response is received from the server, but execution of your viewDidLoad carries on regardless.

    The consequence is that, when startVC is set by calling viewControllerAtIndex, the pageImages array is empty, and return ContentViewController() is executed. This instantiates an empty content view controller and, since it is not linked to the storyboard, the outlet imageView is nil (and indeed imageFile is nil). This is why you get the error.

    So you need to amend that if statement in viewControllerAtIndex to display something more helpful if the array is empty – perhaps a view controller with a message to the user that the data is downloading, and a visual indicator that the system is working.

    You then have to find a way to reload the page view controller when the data does arrive from the server. To do that, you should add some code at the end of your aClosure. In the tutorial, there is a function restartAction which does the necessary, so either call that, or copy the code into the closure.

    Finally, because that last step involves UI updates, it has to run on the main queue. So you need to amend your dispatch_async call (in the closure) to use the main queue, not the global queue. See this answer for a full explanation on that.