How can I call a view controller function from an

2019-02-15 20:18发布

问题:

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.

回答1:

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)
    }
}


回答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()


回答3:

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.



回答4:

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.