0

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.

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?

amdixon
  • 3,814
  • 8
  • 25
  • 34
Serge Pedroza
  • 2,160
  • 3
  • 28
  • 41

1 Answers1

1

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.

Community
  • 1
  • 1
pbasdf
  • 21,386
  • 4
  • 43
  • 75