Ethan Humphries
Ethan Humphries

Reputation: 1836

Unexpectedly found nil while unwrapping an Optional value, when trying to change a label's text, triggered by a notification

Currently I have a function in my AppDelegate.swift file which fires when a notification is received. I am using Firebase to send these notifications.

When the function runs, it checks for any extra attached data, with the key 'url', and if so, it runs a function in my ViewController.swift file.

The issue is that the function that is run in the ViewController tries to change the text of a label to "The function has ran", but when this line is run, it throws the error "Unexpectedly found nil while unwrapping an Optional value".

I can't figure out what this Optional is, so if anyone could suggest a possible issue with my code that would be great.

Things I have tried:

Snippet of the AppDelegate.swift code:

import UIKit
import Firebase
import FirebaseInstanceID
import FirebaseMessaging
import UserNotifications

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var vc = ViewController()

    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                 fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {

        print(userInfo)

        let url = userInfo["url"]

        print("url is: \(String(describing: url))")

        if url != nil {

            hasRun = true

            vc.changeLabel()

            print("Func done")

        }

        completionHandler(UIBackgroundFetchResult.newData)
    }
}

Snippet of the ViewController.swift code:

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var theLabel: UILabel!

    func changeLabel() {

        theLabel.text = "The change label code has run here"

    }
}

Upvotes: 1

Views: 996

Answers (1)

Matic Oblak
Matic Oblak

Reputation: 16774

The optional part is your label property. It is marked as force unwrap with having "!" at the end @IBOutlet weak var theLabel: UILabel!. This is a default when connecting outlets and to generally avoid such issue I suggest you to practice @IBOutlet private weak var <#name#>: <#type#>?.

As already mentioned in comment by @Larme you are creating an instance of your view controller through empty constructor which will not load the controller from your storyboard or xib by default (you might have overridden it though). Still even if overridden the view is probably not yet loaded at that point and your label is still nil.

From the code you posted it seems you want to push some data to a view controller which should exist unless your app is opened by triggering a notification. There are many ways to handle this but one you might consider is to have a static method changeLabel which can be called without creating an instance of view controller. The method should check if a view controller is loaded and call your method on it if it does. If it is not yet loaded then it should be called in view did load once it does load (assuming it loads during a default UI pipeline).

To achieve such a system you may do the following:

Add a private static variable of your view controller within itself like private var currentInstance: ViewController?. On view did load call ViewController.currentInstance = self. Also add static variable for data you need (like your url) static var myURL: Any?. Then:

static func setURL(url: Any?) {
   if currentInstance != nil {
      currentInstance.changeLabel() // Will be called immediately
   } else {
      myURL = url
   }
}

And view did load:

override func viewDidLoad(animated: Bool) {
   ...
   if let url = ViewController.myUrl {
       ViewController.myUrl = nil
       changeLabel()
   }
   ...
}

This way all you need to do in your app delegate is call ViewController.setURL(userInfo["url"]).

I hope you get a basic idea...

Upvotes: 1

Related Questions