12

I have searched around, but have yet to find an answer that doesn't direct me towards a 3rd party service. I do not need anything intricate, just to save a value in NSUserDefaults so when the app opens next, I can display an alert saying the app has crashed.

Thanks.

zennox
  • 624
  • 1
  • 8
  • 19
  • Take a look at: http://stackoverflow.com/questions/10885313/detect-app-crashed-during-load-last-time-it-was-run – Okhan Okbay May 13 '16 at 22:48

4 Answers4

25

Thanks to a little help from @RyanCollins, I was able to solve the problem myself. The function applicationWillTerminate in the App Delegate only runs when the app closes properly. The code to natively detecting an app crash looks like this.

Globally Defined Variables

let crashedNotificationKey = "com.stackoverflow.crashNotificationKey"
var crashedLastTime = true

App Delegate

func applicationWillTerminate(application: UIApplication) {
    crashedLastTime = false
    prefs.setBool(crashedLastTime, forKey: "crash")
}

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    crashedLastTime = prefs.boolForKey("crash")
    if crashedLastTime == true {

        crashedLastTime = false
        prefs.setBool(crashedLastTime, forKey: "crash")
        NSNotificationCenter.defaultCenter().postNotificationName(crashedNotificationKey, object: self)

    } else {

        crashedLastTime = true
        prefs.setBool(crashedLastTime, forKey: "crash")

    }

    return true
}

Root View Controller

override func awakeFromNib() {
    NSNotificationCenter.defaultCenter().addObserver(self, selector: "crashedAlert", name: crashedNotificationKey, object: nil)
}

func crashedAlert() {
    let alert = UIAlertController(title: "The app has crashed!", message: "Sorry about that! I am just a 17 year old highschooler making my first game!", preferredStyle: UIAlertControllerStyle.Alert)
    alert.addAction(UIAlertAction(title: "It's cool bro.", style: UIAlertActionStyle.Default, handler: nil))
    self.presentViewController(alert, animated: true, completion: nil)
}
Zia
  • 14,622
  • 7
  • 40
  • 59
zennox
  • 624
  • 1
  • 8
  • 19
  • 1
    Have not tested, but I think there is a bug here. After checking the value of crashedLastTime in the application didFinishLaunchingWithOptions function, always set the stored boolean to `true`. E.g.: prefs.setBool(true, forKey: "crash") – Awesomeness Oct 26 '17 at 16:12
  • @Awesomeness **Yes! When we close the app from background `applicationWillTerminate` invoked.** – Jack May 16 '18 at 05:34
  • 1
    I'm observing `applicationWilTerminate` *not* being called when force quitting an app, on iOS 11. – Andrew Bennet Jun 20 '18 at 08:17
  • `applicationWillResignActive` is called before the user is able to force quit, so the app is in inactive state before the user is able to force quit. I track te application state and only count crashes as crashes when the last known state was active. When the state was inactive or background, it doesn't bother the user too much, even if it really crashed and not only force quit. – Gerd Castan Sep 06 '18 at 20:03
  • In this code I found the mistake: if app crashed and we caught this in method didFinishLaunchingWithOptions, after that in this session app crashed again, then handling of the crash will be missed, because property for key "crash" was set false. – Vladimir Prigarin Oct 05 '18 at 02:25
  • This code mistakenly assumes that the only time `applicationWillTerminate` will not be called is if the app crashed. That is not true. See the documentation for `applicationWillTerminate`. – HangarRash Aug 05 '23 at 17:38
6

Details

  • swift 4.2
  • Xcode 10.1 (10B61)

Solution 1

https://github.com/zixun/CrashEye/blob/master/CrashEye/Classes/CrashEye.swift

Full sample of solution 1

!!! DO NOT FORGET TO COPY (or INSTALL POD) SOLUTION CODE !!!

AppDelegate.swift

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        CrashEye.add(delegate: self)
        return true
    }
}

extension AppDelegate: CrashEyeDelegate {
    func crashEyeDidCatchCrash(with model: CrashModel) {
        UserDefaults.standard.set(model.reason + "(\(Date()))", forKey: "crash")
    }
}

ViewController.swift

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        let button = UIButton(frame: CGRect(x: 40, y: 40, width: 80, height: 44))
        button.setTitle("BOOOM", for: .normal)
        button.setTitleColor(.blue, for: .normal)
        button.addTarget(self, action: #selector(boomButtonTouchedUpInside), for: .touchUpInside)
        view.addSubview(button)
    }

    override func viewDidAppear(_ animated: Bool) {
        if let date = UserDefaults.standard.string(forKey: "crash") {
            let alert = UIAlertController(title: "", message: date, preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
            present(alert, animated: true)
        }
    }

    @objc func boomButtonTouchedUpInside(_ sender: Any) {
        let arr = [1, 2, 3]
        let elem = arr[4]
    }
}

Solution 2. Crashlytics

Install Crashlytics

Full sample of solution 2

AppDelegate.swift

import UIKit
import Fabric
import Crashlytics

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        Crashlytics.sharedInstance().delegate = self
        Fabric.with([Crashlytics.self])
        return true
    }
}

extension AppDelegate: CrashlyticsDelegate {
    func crashlyticsDidDetectReport(forLastExecution report: CLSReport) {
        let alert = UIAlertController(title: "\(report.dateCreated)", message: report.identifier, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
        DispatchQueue.global(qos: .background).async {
            sleep(3)
            DispatchQueue.main.async {
                self.window?.rootViewController?.present(alert, animated: true)
            }
        }
    }
}

ViewController.swift

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        let button = UIButton(frame: CGRect(x: 40, y: 40, width: 80, height: 44))
        button.setTitle("BOOOM", for: .normal)
        button.setTitleColor(.blue, for: .normal)
        button.addTarget(self, action: #selector(boomButtonTouchedUpInside), for: .touchUpInside)
        view.addSubview(button)
    }

    @objc func boomButtonTouchedUpInside(_ sender: Any) {
        let arr = [1, 2, 3]
        let elem = arr[4]
    }
}

Results

  1. Launch app
  2. Push "Booom" button (app will crash)
  3. Lunch app again

    enter image description here

Vasily Bodnarchuk
  • 24,482
  • 9
  • 132
  • 127
2

The problem is, if the app has crashed, then it can't run code to write to NSUserDefaults.

The best solution I know of is to use PLCrashReporter (https://plcrashreporter.org)

Ryan Collins
  • 679
  • 1
  • 6
  • 13
  • Is there a function or anything that will run if the app closes properly? For example, `applicationWillResignActive` but this runs even if the app crashes. – zennox May 13 '16 at 22:57
  • Yes, in your app delegate, there are functions like `applicationWillTerminate` and `applicationDidEnterBackground` that will do just that. – Ryan Collins May 13 '16 at 23:00
  • I will look into that and get back to you. – zennox May 13 '16 at 23:01
1

FirebaseCrashlytics

If you don't use Fabric because it is deprecated, You need to use following code in your ViewController that was mentioned in Updagte to the Firebase Crashlytics SDK.

I use it like this:

// 1. import Crashlytics
import FirebaseCrashlytics

class YOUR_ROOT_VIEW_CONTROLLER {

   override viewDidLoad(){
       //...

       // 2. register to get the notifications
       self.configCrashlytics()

       // ...
   }

    func configCrashlytics() {
        /* You must set setCrashlyticsCollectionEnabled to false in order to use
        checkForUnsentReportsWithCompletion. */

        Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(false)

        Crashlytics.crashlytics().checkForUnsentReports { hasUnsentReport in
          let hasUserConsent = false
          // ...get user consent.

          if hasUserConsent && hasUnsentReport {
            Crashlytics.crashlytics().sendUnsentReports()
          } else {
            Crashlytics.crashlytics().deleteUnsentReports()
          }
        }

        // Detect when a crash happens during your app's last run.
        if Crashlytics.crashlytics().didCrashDuringPreviousExecution() {
          // ...notify the user that last time the app crashed
          
        }
        
    }
    // 4. Add a button that run fatallError()
    @IBAction func crashApp(_ sender: UIButton) {
        fatalError()
    }
}

Esmaeil
  • 558
  • 5
  • 11