1

I am getting a call from a remote push notification, which includes a path to an image, which I want to display.

The AppDelegate is:

func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
    // get url from notification payload (shortened version)
    let url = alert["imgurl"] as? NSString 
    let vc = ViewController()
    vc.loadImage(url as String) // cannot find the imageView inside
}

...and the view looks like this:

func loadImage(url:String) {
    downloadImage(url, imgView: self.poolImage)
}

// http://stackoverflow.com/a/28942299/1011227
func getDataFromUrl(url:String, completion: ((data: NSData?) -> Void)) {
    NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: url)!) { (data, response, error) in
        completion(data: NSData(data: data!))
        }.resume()
}

func downloadImage(url:String, imgView:UIImageView){
    getDataFromUrl(url) { data in
        dispatch_async(dispatch_get_main_queue()) {
            imgView.image = UIImage(data: data!)
        }
    }
}

I'm getting an exception saying imgView is null (it is declared as @IBOutlet weak var poolImage: UIImageView! and I can display an image by calling loadImage("http://lorempixel.com/400/200/") from a button click.

I found a couple of related answers on stackoverflow (one is referenced above) but none work.

Eric Aya
  • 69,473
  • 35
  • 181
  • 253
sagism
  • 881
  • 4
  • 11
  • 18
  • Did you keep strong reference to vc variable? – Tiep Vu Van Apr 04 '16 at 16:02
  • If that is actually your code when you get a notification, then you're creating a new instance of your view controller. Your image view is null because the new instance's UI won't be initialised until it's displayed. You need to get a pointer to your view controller that's already on screen, not create a new one. – Simon Apr 04 '16 at 16:16
  • @Simon so how do I obtain a pointer to the existing view controller from the appdelegate, in a way that I can call one of its methods? – sagism Apr 06 '16 at 03:52
  • @sagism please see the new answer I've added to see if that works for you – Simon Apr 06 '16 at 07:05

4 Answers4

4

If you want to show a new view controller modally, then look into rach's answer further.

If you're ViewController is already on screen, you will need to update it to show the information you need.

How you do this will depend on your root view controller setup. Is your view controller embedded in an UINavigationController? If so then try this:

func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
    if let rootViewController = window?.rootViewController as? UINavigationController {
        if let viewController = rootViewController.viewControllers.first as? ViewController {
            let url = alert["imgurl"] as? NSString
            viewController.loadImage(url as String)
        }
    }
}

If it is not, then try this:

func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
    if let rootViewController = window?.rootViewController as? ViewController {
        let url = alert["imgurl"] as? NSString
        rootViewController.loadImage(url as String)
    }
}
sagism
  • 881
  • 4
  • 11
  • 18
Simon
  • 1,354
  • 1
  • 14
  • 18
2

I believe at the point of time where you are calling

let vc = ViewController()

it has yet to be instantiated. Since you are using @IBOutlet for your UIImageView, I am assuming that you actually have that UIViewController.

Maybe it could be corrected by this:

let vc = self.storyboard?.self.storyboard?.instantiateViewControllerWithIdentifier("ViewControllerStoryboardID")
vc.loadImage("imageUrl")
self.presentViewController(vc!, animated: true, completion: nil)

Let me know if it works. :)

EDIT: You could call an instance of storyboard via this method:

self.window = UIWindow(frame: UIScreen.mainScreen().bounds)

var storyboard = UIStoryboard(name: "Main", bundle: nil)

var vc = storyboard.instantiateViewControllerWithIdentifier("ViewController") as! UIViewController

vc.loadImage("imageUrl")

self.window?.rootViewController = vc
self.window?.makeKeyAndVisible()
Rachel Fong
  • 730
  • 1
  • 8
  • 16
  • No Joy :( Value of type AppDelegate has no member 'storyboard' – sagism Apr 04 '16 at 19:52
  • Take a look at my edited answer and see if it helps steer you in the right direction. – Rachel Fong Apr 05 '16 at 04:01
  • Thanks but still does not work: Value of type 'UIViewController' has no member 'loadimage' – sagism Apr 05 '16 at 19:22
  • Well the UIViewController class is just an example. You can replace 'UIViewController' with 'ViewController' or whichever class that has your loadImage function. Also dont forget to crosscheck storyboard identifier so that it matches the one 'instantiateViewControllerWithIdentifier' – Rachel Fong Apr 06 '16 at 04:06
1

By calling @IBOutlet weak var poolImage: UIImageView! you're passing the initialization of poolImage to the storyboard. Since you're initializing the view controller with let vc = ViewController(), vc will know that it is expected to have a reference to poolImage because you declared it in the class, but since you aren't actually initializing vc's view (and subviews) poolImage remains nil until the UI is loaded.

If you need to reference a UIImageView at the time of the initialization of vc, you'll have to manually instantiate it: let poolImage = UIImageView()

EDIT: Your ViewController implementation of poolImage currently looks like this:

class ViewController : UIViewController {

    @IBOutlet weak var poolImage : UIImageView!

    // rest of file

}

To access poolImage from AppDelegate via let vc = ViewController() you have to make the implementation look like this:

class ViewController : UIViewController {

    /* ---EDIT--- add a placeholder where you would like 
    poolImage to be displayed in the storyboard */

    @IBOutlet weak var poolImagePlaceholder : UIView!

    var poolImage = UIImageView()

    // rest of file

    override func viewDidLoad() {
        super.viewDidLoad()

        /* ---EDIT--- make poolImage's frame identical to the
        placeholder view's frame */

        poolImage.frame = poolImagePlaceholder.frame
        poolImagePlaceholder.addSubView(poolImage)
        // then add auto layout constraints on poolImage here if necessary

    }

}

That way it poolImage is available for reference when ViewController is created.

Wes
  • 1,032
  • 7
  • 11
  • Not clear how/where to perform the initialization you are suggesting – sagism Apr 04 '16 at 20:03
  • Thanks, this initializes a new image view alright, but that's not the image view it should be displaying in, but rather a new one. So the image does not display. – sagism Apr 05 '16 at 19:45
  • Yes, that is true. Create a `UIView` that acts as a placeholder in your storyboard and once the UI is actually loaded, add `poolImage` as a subview of the placeholder and programmatically add the appropriate auto layout constraints if necessary. See my edit. – Wes Apr 05 '16 at 19:47
0

The reason I think is because the imageView may not have been initialized. Try the following solution.

Add another init method to your viewController which accepts a URLString. Once you receive a push notification, initialize your viewController and pass the imageURL to the new init method.

Inside the new initMethod, you can use the imageURL to download the image and set it to the imageView. Now your imageView should not be null and the error should not occur.

Ruchira Randana
  • 4,021
  • 1
  • 27
  • 24