1

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:

  • Creating and using a new label
  • Renaming the label
  • Checked that the Outlet is properly connected
  • Checking classes are properly set

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"

    }
}
Ethan Humphries
  • 1,786
  • 3
  • 19
  • 28
  • theLabel.text is when called after viewcontroller load ? – Divyesh Gondaliya Aug 17 '17 at 12:11
  • 5
    `var vc = ViewController()` That's creating a new instance/object `ViewController`. That's not the one you think it is. Also, since it's declared `@IBOutlet weak var theLabel: UILabel!` it means that the label is connected through a StoryBoard or a Xib. But `ViewController()` doesn't call the `initWithNibName` or instatiateViewControllerFromStoryboard. So the IBOutlet is not connected. – Larme Aug 17 '17 at 12:11
  • `let url = userInfo["url"]` should be an optional: `let url: String? = userInfo["url"]` otherwise you can't expect to compare with `nil` – thedp Aug 17 '17 at 12:13
  • 1
    right @Larme if u store that text in some variable which one you have to update and that will be changed in viewwillapper method – Divyesh Gondaliya Aug 17 '17 at 12:15
  • That makes more sense, thanks for the comments, it really cleared it all up a bit on my end. – Ethan Humphries Aug 17 '17 at 13:14
  • Possible duplicate of [What does "fatal error: unexpectedly found nil while unwrapping an Optional value" mean?](https://stackoverflow.com/questions/32170456/what-does-fatal-error-unexpectedly-found-nil-while-unwrapping-an-optional-valu) – Cristik Mar 22 '18 at 06:38

1 Answers1

1

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...

Matic Oblak
  • 16,318
  • 3
  • 24
  • 43
  • Thanks for explaining this, it will definitely help me out. I had forgotten that the Outlet was also an optional. I'll practice with all this a bit more and hopefully it'll get this working. – Ethan Humphries Aug 17 '17 at 13:11