Flutter IOS Workmanager Background Fetch Never Called in Production - ios

I'm seemingly unable to get background fetch working with Flutter workmanager on IOS.
I can confirm that it is working when called within xcode through debug. Just never when deployed to the device.
I've got my workmanager initialised and callback setup in main.dart
...
Workmanager().initialize(
callbackDispatcher,
isInDebugMode: true
);
}
void callbackDispatcher()
{
Workmanager().executeTask((task, inputData) async
{
function()
return Future.value(true);
});
}
I've added fetch background mode to info.plist
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
</array>
I've added system capabilities to project.pbxproj
SystemCapabilities = {
com.apple.BackgroundModes = {
enabled = 1;
};
};
I've added the plugin to appdelegate.swift
import UIKit
import Flutter
import workmanager
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
UIApplication.shared.setMinimumBackgroundFetchInterval(TimeInterval(60*15))
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
Be much appreciated if anyone has a working setup.

Ok. For future people reading this with the same issue, this does 'eventually' work.
After having the app open in the background for ~48hrs, I finally got a background task to run. This is by no means a complete success as it is not really at the frequency desired, but this implementation does 'work' none the less.
Just run in debug mode to confirm and be very patient.

Related

Flutter - firebase FCM messages not working on Testflight release builds at all

Preface:
My App is Flutter based - but native code implementation is required to get FCM messages working, see below for more details
GitHub issue #154 for reference.
I'm having immense trouble getting FCM notifications working on iOS, specifically on my app published to Testflight. I have been stuck on this problem for a week and have absolutely no idea how to proceed.
Problem
When running locally using debug/release builds on my devices using Xcode/Android Studio, notifications are received in the background, foreground, etc. When uploading the exact same app to Testflight, not a single notification will come through via FCM.
This is crucial as FCM delivers VoIP notifications, these aren't being received on Testflight which is extremely distressing
Questions & Solutions?
There are 2 questions I found (here & here), both seemed to indicate it is a APNS certificate problem (APNS -> Firebase). I have recreated my certificate and added it to the Firebase console (using the same .csr file for all certificate generating operations)
Setup/Configuration:
APNS
Key generated & added to Firebase
Capabilities:
Tried with:
<key>FirebaseAppDelegateProxyEnabled</key>
<string>NO</string>
with:
<key>FirebaseAppDelegateProxyEnabled</key>
<string>0</string>
and with :
<key>FirebaseAppDelegateProxyEnabled</key>
<boolean>false</boolean>
Background modes:
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>bluetooth-central</string>
<string>external-accessory</string>
<string>fetch</string>
<string>location</string>
<string>processing</string>
<string>remote-notification</string>
<string>voip</string>
<string>remote-notification</string>
</array>
Tutorials/sources:
Setup FCM & APNS
ConnectyCube P2P Session source
Swift Code: (targeting >=10.0)
import UIKit
import CallKit
import Flutter
import Firebase
import UserNotifications
import GoogleMaps
import PushKit
import flutter_voip_push_notification
import flutter_call_kit
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate, PKPushRegistryDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// run firebase app
FirebaseApp.configure()
// setup Google Maps
GMSServices.provideAPIKey("google-maps-api-key")
// register notification delegate
UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate
GeneratedPluginRegistrant.register(with: self)
// register VOIP
self.voipRegistration()
// register notifications
application.registerForRemoteNotifications();
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
// Handle updated push credentials
public func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
// Process the received pushCredentials
FlutterVoipPushNotificationPlugin.didUpdate(pushCredentials, forType: type.rawValue);
}
// Handle incoming pushes
public func pushRegistry(_ registry: PKPushRegistry,
didReceiveIncomingPushWith payload: PKPushPayload,
for type: PKPushType,
completion: #escaping () -> Swift.Void){
FlutterVoipPushNotificationPlugin.didReceiveIncomingPush(with: payload, forType: type.rawValue)
let signalType = payload.dictionaryPayload["signal_type"] as! String
if(signalType == "endCall" || signalType == "rejectCall"){
return
}
let uuid = payload.dictionaryPayload["session_id"] as! String
let uID = payload.dictionaryPayload["caller_id"] as! Int
let callerName = payload.dictionaryPayload["caller_name"] as! String
let isVideo = payload.dictionaryPayload["call_type"] as! Int == 1;
FlutterCallKitPlugin.reportNewIncomingCall(
uuid,
handle: String(uID),
handleType: "generic",
hasVideo: isVideo,
localizedCallerName: callerName,
fromPushKit: true
)
completion()
}
// Register for VoIP notifications
func voipRegistration(){
// Create a push registry object
let voipRegistry: PKPushRegistry = PKPushRegistry(queue: DispatchQueue.main)
// Set the registry's delegate to self
voipRegistry.delegate = self
// Set the push type to VoIP
voipRegistry.desiredPushTypes = [PKPushType.voIP]
}
}
public func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
if #available(iOS 14.0, *) {
completionHandler([ .banner, .alert, .sound, .badge])
} else {
completionHandler([.alert, .sound, .badge])
}
}
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
print(deviceToken)
Messaging.messaging().apnsToken = deviceToken;
}
Flutter main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
await initializeDateFormatting();
setupLocator();
var fcmService = locator<FCMService>();
FirebaseMessaging.onBackgroundMessage(FCMService.handleFirebaseBackgroundMessage);
FirebaseMessaging.onMessage.listen((event) {
print("Foreground message");
Fluttertoast.showToast(msg: "Received onMessage event");
FCMService.processCallNotification(event.data);
});
FirebaseMessaging.onMessageOpenedApp.listen((event) {
print("On message opened app");
Fluttertoast.showToast(msg: "Received onMessageOpenedAppEvent");
FCMService.handleInitialMessage(event);
});
FirebaseMessaging.instance.getInitialMessage().then((value) {
Fluttertoast.showToast(msg: "Received onLaunch event");
if (value != null) {
FCMService.handleInitialMessage(value);
}
});
initConnectyCube();
runApp(AppProviders());
}
FCMService.dart
// handle any firebase message
static Future<void> handleFirebaseBackgroundMessage(RemoteMessage message) async {
print("Received background message");
Fluttertoast.showToast(msg: "Received Firebase background message");
await Firebase.initializeApp();
setupLocator();
var fcmService = locator<FCMService>();
fcmService.init();
_handleMessage(message, launchMessage: true);
}
Testing:
Testing is done on 2 physical iPhones (6s & 8). Both work with Firebase FCM when building directly from Mac (Android Studio & XCode) using (debug & release) modes. Neither works when downloading the same from TestFlight.
If any can provide insight into a misconfiguration, an error in setup or missing/incorrect Swift code, or simply a mistake or omission, it would be much appreciated.
This problem sometimes drive people crazy even they apply everything in the correct scenario, so please try to check the following:
1- in your apple developer account make sure that you have only one Apple Push Services Certificate assigned to the app identifier ( Bundle ID ), please avoid duplication.
2- If you are using the APNs key to receive notification you have to make sure its set on the production mode when your app is uploaded to TestFlight or AppStore
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
print("APNs Device Token: \(token)")
Messaging.messaging().apnsToken = deviceToken
Messaging.messaging().setAPNSToken(deviceToken, type: .prod)
}
Note: TestFlight considered as a release (Production) mode not as sandbox mode
Preface:
the issue was mine.
TL;DR
changed only 1 reference of CubeEnvironment to PRODUCTION.
There are multiple locations to change CubeEnvironment:
push_notifications_manager.dart#111
push_notifications_manager.dart#111
call_manager.dart#111
Suggestion to use, even better to add this in your "CallManagerService"'s init() method:
bool isProduction = bool.fromEnvironment('dart.vm.product');
parameters.environment = isProduction ? CubeEnvironment.PRODUCTION : CubeEnvironment.DEVELOPMENT;
Debugging (process):
The debugging process (being somewhat unfamiliar with Swift & XCode) could have been better. I considered various provisioning profiles, aps-environment settings, etc.
Since the issue only occurred on Testflight, it made debugging alot more challenging and time consuming as uploading a debug build had its own set of issues
Finally I added a bunch of logging, the one that was crucial was the CB-SDK debug entry (when a notification is received):
[
{
"subscription": {
"id": sub id,
"_id": "insert sub id",
"user_id": cube_user_id,
"bundle_identifier": "insert bundle id",
"client_identification_sequence": "insert client id",
"notification_channel_id": 6,
"udid": "insert-uuid",
"platform_id": 1,
"environment": "development",
"notification_channel": {
"name": "apns_voip"
},
"device": {
"udid": "insert-uuid",
"platform": {
"name": "ios"
}
}
}
}
]
specifically, the following entry.
environment": "development
This is due to APS used 2 different push notification environments, each with its own certificates (certificate is assigned to unique URL's where push notifications can come from). This, aps-environment is set to 'production (see on upload Archive screen right before you start uploading) but I'm receiving development environment notifications - that needed fixing.
Reviewing my code, I finally found the issue (and fix mentioned above).
I tried everything but what worked for me was discovering that App Flyer SDK was preventing my push notifications from coming through. Removing this from pubspec solved it for me. End of a 30 hour debugging journey.

iOS - Intents eligible for in-app handling not work

I have an application with a deployment target on iOS 14 and built entirely with swiftui.
I have added an intentdefinition file and added a couple of intents with their respective handlers, and I have added to my app delegate the function to handle them:
struct ...App: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
...
}
extension AppDelegate {
func application(_ application: UIApplication, handlerFor intent: INIntent) -> Any? {
switch intent {
case is NewTaskIntent:
return NewTaskHandler()
case is TodayTasksIntent:
return TodayTasksHandler()
default:
return nil
}
}
}
The problem is that this function is never called and when I run the shortcut from its app it always launches my app.
The info Plist contains the keys correctly:
<key>INIntentsSupported</key>
<array>
<string>NewTaskIntent</string>
<string>TodayTasksIntent</string>
</array>
Any idea what is happening?
If you are using SwiftUI, try onContinueUserActivity(_:perform:).
https://developer.apple.com/documentation/swiftui/view/oncontinueuseractivity(_:perform:)
Note: its first argument activityType is defined in your main app's Custom iOS Target Properties with NSUserActivityTypes key. This property seems to be generated by Xcode auto-ly.
Inspired by the "Step Six" in https://toolboxpro.app/blog/adding-shortcuts-to-an-app-part-four
However, it's quite wired that my func application(_ application: UIApplication, handlerFor intent: INIntent) -> Any? triggered yesterday, but makes no sense right now. Is it a 'feature' 😂

Flutter: disable screenshot capture for app

I am making a Flutter app and I need to make sure the user is not able to capture screenshot of the app (any screen). Is there any way to achieve this in Flutter or do I need to write native code for both Android and IOS?
Android
Method 1 (all app screens):
Locate your MainActivity (.java or .kt) class inside the embedded android project dir in your Flutter Project,
Add the following import to your main activity class:
import android.view.WindowManager.LayoutParams;
Add the following line to your MainActivity's onCreate method:
getWindow().addFlags(LayoutParams.FLAG_SECURE);
Method 2 (for specific screens):
Use FlutterWindowManagerPlugin:
https://pub.dev/packages/flutter_windowmanager
Thanks, #Kamlesh!
It's only in iOS,just modify in AppDelegate.
And no more plugins
import UIKit
import Flutter
import Firebase
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate {
override func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions[UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
self.window.makeSecure() //Add this line
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
//And this extension
extension UIWindow {
func makeSecure() {
let field = UITextField()
field.isSecureTextEntry = true
self.addSubview(field)
field.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
field.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
self.layer.superlayer?.addSublayer(field.layer)
field.layer.sublayers?.first?.addSublayer(self.layer)
}
}
See image here
For Flutter2 project
Method 1 : using package flutter_windowmanager
Method 2 :
in Android with kotlin
Step 1 Open the file "mainActivity.kt" using the path
android\app\src\main\kotlin\com\example\auth_email\MainActivity.kt
Step 2 Import Library
import android.view.WindowManager.LayoutParams
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
Step 3 In main activity class
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
window.addFlags(LayoutParams.FLAG_SECURE)
super.configureFlutterEngine(flutterEngine)
}
}
In iOS Swift : AppDelegate.swift file
import UIKit
import Flutter
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
// <Add>
override func applicationWillResignActive(
_ application: UIApplication
) {
self.window.isHidden = true;
}
override func applicationDidBecomeActive(
_ application: UIApplication
) {
self.window.isHidden = false;
}
}
The simplest way to do this is to use a flutter package called flutter_windowmanager
Works only for Android, not for IOS!
First import its latest version in pubspec.yaml file of your Flutter project and run pub get. Then add the below code inside the widget's initState() method for which you want to disable screenshot and screen recording.
Future<void> secureScreen() async {
await FlutterWindowManager.addFlags(FlutterWindowManager.FLAG_SECURE);
}
#override
void initState() {
secureScreen();
super.initState();
}
#override
void dispose(){
super.dispose();
await FlutterWindowManager.clearFlags(FlutterWindowManager.FLAG_SECURE);
}
If you want to make your whole app screenshot disable just call securescreen() method (defined above) inside your main() function in main.dart file.
What worked for me was writing the below code in MainActivity.java file.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
}
and importing these packages!
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.os.Bundle; // required for onCreate parameter
On iOS I have disabled taking of screenshots with the help of extension https://stackoverflow.com/a/67054892/4899849. Follow next steps:
Add property in AppDelegate:
var field = UITextField()
in didFinishLaunchingWithOptions call next method: addSecuredView()
private func addSecuredView() {
if (!window.subviews.contains(field)) {
window.addSubview(field)
field.centerYAnchor.constraint(equalTo: window.centerYAnchor).isActive = true
field.centerXAnchor.constraint(equalTo: window.centerXAnchor).isActive = true
window.layer.superlayer?.addSublayer(field.layer)
field.layer.sublayers?.first?.addSublayer(window.layer)
}
}
override delegate methods:
override func applicationWillResignActive(_ application: UIApplication) {
field.isSecureTextEntry = false
}
override func applicationDidBecomeActive(_ application: UIApplication) {
field.isSecureTextEntry = true
}
Now, when you make a screenshot in the app or record a screen video you will see a black image or video. Hope, it will help cuz I spent 2 days trying to make it work)
Flutter
Method 1: Using this package screen_protector
Method 2:
In Whole your Application
Open AppDelegate file and add UITextField variable.
private var textField = UITextField()
Create a function in AppDelegate file.
// Screenshot Prevent Functions
private func makeSecureYourScreen() {
if (!self.window.subviews.contains(textField)) {
self.window.addSubview(textField)
textField.centerYAnchor.constraint(equalTo: self.window.centerYAnchor).isActive = true
textField.centerXAnchor.constraint(equalTo: self.window.centerXAnchor).isActive = true
self.window.layer.superlayer?.addSublayer(textField.layer)
textField.layer.sublayers?.first?.addSublayer(self.window.layer)
}
}
Call this method into the didFinishLaunchingWithOptions function.
makeSecureYourScreen()
Code Screenshot
In Specific Screen - Using Method Channel
Open AppDelegate file and add UITextField variable.
private var textField = UITextField()
Create a function in AppDelegate file.
// Screenshot Prevent Functions
private func makeSecureYourScreen() {
if (!self.window.subviews.contains(textField)) {
self.window.addSubview(textField)
textField.centerYAnchor.constraint(equalTo: self.window.centerYAnchor).isActive = true
textField.centerXAnchor.constraint(equalTo: self.window.centerXAnchor).isActive = true
self.window.layer.superlayer?.addSublayer(textField.layer)
textField.layer.sublayers?.first?.addSublayer(self.window.layer)
}
}
Call this method into the didFinishLaunchingWithOptions function.
makeSecureYourScreen()
Also, add your method channel code in the didFinishLaunchingWithOptions function.
let controller : FlutterViewController = self.window?.rootViewController as! FlutterViewController
let securityChannel = FlutterMethodChannel(name: "secureScreenshotChannel", binaryMessenger: controller.binaryMessenger)
securityChannel.setMethodCallHandler({
(call: FlutterMethodCall, result: #escaping FlutterResult) -> Void in
if call.method == "secureiOS" {
self.textField.isSecureTextEntry = true
} else if call.method == "unSecureiOS" {
self.textField.isSecureTextEntry = false
}
})
Add your code below code to your flutter files to disable the screenshot on a specific screen.
// Declare your method channel varibale here
var iosSecureScreenShotChannel = const MethodChannel('secureScreenshotChannel');
Now add code to initState to prevent screenshot
#override
void initState() {
// this method to user can't take screenshots of your application
iosSecureScreenShotChannel.invokeMethod("secureiOS");
// TODO: implement initState
super.initState();
}
For add code to dispose to allow screenshots on another screen.
#override
void dispose() {
// this method to the user can take screenshots of your application
iosSecureScreenShotChannel.invokeMethod("unSecureiOS");
// TODO: implement dispose
super.dispose();
}
Code Screenshot in Xcode
Code Screenshot in Flutter
You can disable screenshots and video captures like the Netflix app and Disney Hotstar app.
I have tried it in my application and it works fine. 😊
Screenshots can be prevented very easily by following below two steps.
I am using VS code.
Step 1 Open the file "mainActivity.kt" using the path android\app\src\main\kotlin\com\example\auth_email\MainActivity.kt
Step 2 Add the two lines
(a) import android.view.WindowManager.LayoutParams;
(b) getWindow().addFlags(LayoutParams.FLAG_SECURE); in MainActivity: FlutterActivity() section
Restart the app
enter image description here
This works for iOS. In your Runner > AppDelegate.m:
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (void)applicationWillResignActive:(UIApplication *)application{
self.window.hidden = YES;
}
- (void)applicationDidBecomeActive:(UIApplication *)application{
self.window.hidden = NO;
}
#end
If you are using kotlin
open MainActivity.kt
Add below code at end of imports
import android.view.WindowManager.LayoutParams
Add below code at end of super.onCreate(savedInstanceState)
window.addFlags(LayoutParams.FLAG_SECURE)
Its done.
try to use
for android edit MainActivity.kt
package com.package.app_name
import android.view.WindowManager.LayoutParams
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
window.addFlags(LayoutParams.FLAG_SECURE)
super.configureFlutterEngine(flutterEngine)
}
}
define this package inside pubspec.yaml file
flutter_windowmanager: ^0.0.1+1
get dependencies
flutter pub get
You need to call a method of FlutterWindowManager using await and async.
You have to add a few lines of code in your StatefulWidget.
Future<void> secureScreen() async {
await FlutterWindowManager.addFlags(FlutterWindowManager.FLAG_SECURE);
}
#override
void initState() {
secureScreen();
super.initState();
}
https://pub.dev/packages/screen_protector
use this one. works for Android, iOS both.
In iOS, screenshot will be captured but output will be black screen.
You can use the flutter_windowmanager: ^0.2.0 package to disable screenshot capture in a Flutter app. To do this, add the following code in your main.dart file:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await FlutterWindowManager.addFlags(FlutterWindowManager.FLAG_SECURE);
runApp(MyApp());
}
This will add the FLAG_SECURE flag to your app, which will prevent
screenshots from being captured. Note that this will only work on
Android devices.
You could maybe listen for the screenshot keys on iOS and when the combination is being pressed blacken the screen.

How to get incoming/outgoing call event in background state

In one of my app it has a feature of playing sound that I achieved successfully. Even though when app is running (foreground state) and we received the incoming call, app music gets stopped and resume again when call gets disconnected.
Now real problem is here. When app enters in the background state, we are not receiving any event for incoming/outgoing call. In the background mode If music is playing inside my app and we get any incoming call, then app music is stopped automatically but not resume again when call disconnected unlike iPhone Music app.
Is it a limitation of the iOS or can we achieve that ?
Note: I'm not looking for any solution for Jailbreak devices or Enterprise apps
Have you tried to create call center and assign handler block in AppDelegate class? The following has to work.
import UIKit
import CoreLocation
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let callCenter: CTCallCenter = CTCallCenter()
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window?.rootViewController = ViewController()
window?.makeKeyAndVisible()
callCenter.callEventHandler = {
(call: CTCall!) in
switch call.callState {
case CTCallStateConnected:
print("CTCallStateConnected")
case CTCallStateDisconnected:
print("CTCallStateDisconnected")
case CTCallStateIncoming:
print("CTCallStateIncoming")
default:
print("default")
}
}
return true
}
}
Do not forget to switch on Background Modes for this. And perform something in the background as well, smth like receiving location.

Detect app crashed during load / last time it was run?

I would like for my app to reset to standard settings if it crashed during startup. Preferably also if it crashed last time it was run.
EDIT: Crittercism has a crittercismDidCrashOnLastLoad method, but it only handles the case of crashing during load. It didn't work properly in the version of the library I used, but this has since been fixed.
Suggestions?
Make 2 functions in your AppDelegate.m file:
void HandleException(NSException *exception) {
NSLog(#"App crashing with exception: %#", exception);
//Save somewhere that your app has crashed.
}
void HandleSignal(int signal) {
NSLog(#"We received a signal: %d", signal);
//Save somewhere that your app has crashed.
}
Then in your -(BOOL)application:didFinishLaunchingWithOptions before anything else put:
NSSetUncaughtExceptionHandler(&HandleException);
struct sigaction signalAction;
memset(&signalAction, 0, sizeof(signalAction));
signalAction.sa_handler = &HandleSignal;
sigaction(SIGABRT, &signalAction, NULL);
sigaction(SIGILL, &signalAction, NULL);
sigaction(SIGBUS, &signalAction, NULL);
Using Crashlytics you then can set CrashlyticsDelegate to detect a crash on Swift or ObjC code.
import Fabric
import Crashlytics
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
{
Crashlytics.sharedInstance().delegate = self
Fabric.with([Crashlytics.self])
return true
}
//MARK: - CrashlyticsDelegate
func crashlyticsDidDetectReport(forLastExecution report: CLSReport, completionHandler: #escaping (Bool) -> Void)
{
completionHandler(true)
}
From Crashlytics docs
Your delegate must invoke the completionHandler, but does not need to
do so synchronously, or even on the main thread. Invoking
completionHandler with NO will cause the detected report to be deleted
and not submitted to Crashlytics. This is useful for implementing
permission prompts, or other more-complex forms of logic around
submitting crashes.
Make certain that the delegate is setup before starting Crashlytics
with startWithAPIKey:… or via [Fabric with:…]. Failure to do will
result in missing any delegate callbacks that occur synchronously
during start.

Resources