iOS 13 Killing app because it never posted an incoming call to the system after receiving a PushKit VoIP callback
On this thread from apple forums, someone from apple staff explained this:
On iOS 13.0 and later, incoming Voice over IP calls must be reported
when they are received and before the didReceiceIncomingPush() method
finishes execution, using the CallKit framework, or the system will
terminate your app.Repeatedly failing to report calls may prevent
your app from receiving any more incoming call notifications.Basically, you can no longer use VoIP pushes for non VoIP messaging,
and will need to use regular push notifications for those.This was
announced during the WWDC session "Advances in App Background
Execution" https://developer.apple.com/videos/play/wwdc2019/707/
I've been searching for answers on how to adapt an app for this change, and what I could gather is the following:
Voip Pushes
When your app receive this kind of push, it will need to report a new incoming call using CallKit. Therefore, this kind of push will be exclusive for calls that use CallKit.
It's recommended that you set the notification's apns-expiration
to 0, so you won't receive a push and be forced to present a call screen for a call that already expired.
Push Notifications
Regular push notifications are another option. If your server has all the information you need to write the notification text, you can send notifications that won't even run your app in the background. If you need to modify the content of the notification before presenting it to the user, you can use a Notification Service app extension, and if you need your app to be woken up and execute something in background, you can send silent push notifications.
Notification Service App Extension
To use this, you must set your notification's mutable-content
to 1. This way, your extension will receive the notification before it is presented to the user, allowing you to change its content, with a 30 seconds time limit.
The cons are that your app will stay in the background, only your extension will be allowed to run. This might mean that you will need to share information and code between your app and the extension, either by using user defaults, keychain, or by sharing your entire database (which might not be a simple task if your app is not prepared for that).
Silent Push Notifications
To send silent push notifications, you must set your notification's content-available
to 1 and remove it's alert, badge and sound. This notification will wake up your app in the background, and call your app delegate's didReceiveRemoteNotification
.
The downsides are quite annoying for this option:
- You will only get 30 seconds to run.
- These notifications must have a
apns-priority
of 5, which might cause them to be grouped and delivered in bursts, and even throttled or not delivered. - If the user force closes the app, it will completely ignore all silent notifications until the user opens the app again.
NSInternalInconsistencyException', reason: 'Killing app because it never posted an incoming call to the system after receiving a PushKit VoIP push
There are two main reasons why you get that error.
As you may know, you always have to report a new incoming call when receiving a VoIP push. If, for any reason, the value contained in
dict["handle"]
isnil
or is not anNSDictionary
, you fail to report a new incoming call and so you get the error.The
completion()
should be called only after you successfully reported a new incoming call, so inside the completion handler ofreportNewIncomingCall
as you've done. The problem is thecompletion()
that you call outside theif
. Given thatreportNewIncomingCall
is an asynchronous method, thecompletion()
outside theif
will be called before thereportNewIncomingCall
has completed, hence the system will think that you've failed to report a new incoming call. So, just remove thecompletion()
outside theif
.
iOS 13 reason: 'Killing app because it never posted an incoming call to the system after receiving a PushKit VoIP push callback.'
I think that the problem is that you're reporting the call inside a DispatchQueue.main.asyncAfter
. Even if you set the delay to 0, your code will run in the next run loop, i.e. not immediately. In this way the pushRegistry(_:didReceiveIncomingPushWith:for:completion:)
function will end before your call has been reported and thus the system will complain.
So, try to remove the DispatchQueue.main.asyncAfter
.
CallKit reportNewIncomingCall completion called but still getting Killing app because it never posted an incoming call ... crash
I think that the problem could be the DispatchQueue.main.async
. This will cause your completion
handler to be executed later in a future run loop and probably for the system is too late.
Just try to remove it:
provider.reportNewIncomingCall(with: uuid, update: callUpdate, completion: { error in
if let error = error {
print("reportNewIncomingCall error: \(error.localizedDescription)")
}
completion()
})
EDIT
In the new code you've provided, I can see at least three errors that can lead to the crash.
- If one of the
guard
statements fails, you will not report a new incoming call, hence the app crashes. You should do something like this:
guard let payloadData = payload.dictionaryPayload as? [String: Any],
let data = payloadData["data"] as? [String: Any],
let typeCall = data["type"] as? String else {
reportFakeCall(completion) // <---
return
}
Report a fake call and immediately terminate it.
- Given that the
reportNewIncomingCall
is an asynchronous method, you are not guaranteed that thecompletion()
of thepushRegistry(didReceiveIncomingPushWith...)
will be called after the completion of thereportNewIncomingCall
. So, in some cases, the app will crash because you havn't reported a new incoming call before the completion. You should do something like this:
if typeCall == "OPEN_ACTIVITY_CALL" {
guard let userName = data["userName"] as? String else {
reportFakeCall(completion) // <-- As in point 1
return
}
self.appleCallKit?.showIncomingCall(username: userName, completion) // <---
}
Pass the completion handler to the showIncomingCall
and call it inside the completion of the reportNewIncomingCall
.
- If
typeCall
is not equal toOPEN_ACTIVITY_CALL
you don't report a new incoming call and the app crashes.
if typeCall == "OPEN_ACTIVITY_CALL" {
...
} else {
reportFakeCall(completion)
}
You can implement the reportFakeCall
method as follows:
func reportFakeCall(completion: @escaping () -> Void)
{
let callUpdate = CXCallUpdate()
let vCallId = UUID()
provider.reportNewIncomingCall(
with: vCallId,
update: callUpdate,
completion: { error in
completion()
self.endCall(with: vCallId) // CXEndCallAction transaction
})
}
VOIP notification with CallKit is not invoking reportIncomingCall when app is inactive killed state
Have you tried to call the pushRegistry
completion handler inside the reportNewIncomingCall
completion handler?
func pushRegistry(_ registry: PKPushRegistry,
didReceiveIncomingPushWith payload: PKPushPayload,
for type: PKPushType,
completion: @escaping () -> Void) {
provider.reportNewIncomingCall(with: uuid, update: update) { error in
completion() // <----
}
}
Even if it's not clear in the documentation, I think it's the correct way to handle a new incoming call.
Voip call got crashed when app is background and triggered endCall method. (After adding timer for unanswered case)
I can above to resolve this issue by initiating a fake call if there is no Active calls in the app.
private func showFakeCall(of session: String, callerName: String, completion: @escaping () -> Void) {
callCenter.showIncomingCall(of: session, callerName: callerName) { [unowned self] in
self.callCenter.endCall(of: session, at: nil, reason: .failed)
print("Print end call inside Fakecall")
completion()
}
}
Added following check for all of the call events (reject, cancel)
if !callCenter.hasActiveCall(of: channelName) {
print("No Active calls found. Initiating Fake call.")
showFakeCall(of: channelName, callerName: callerName, completion: completion)
return
}
Extra tip: You have to reinstall (uninstall first), if you made any changes to CXProvider/CXCallController configurations.
PushKit notification arriving after call has ended
In iOS 13 you must report an incoming call before you leave the PushKit completion handler, otherwise your app will be blocked from receiving further PushKit pushes. Obviously, this is a slight problem when you have a case such as you described where the call no longer exists.
This scenario, and others, were discussed in an Apple Developer Forums thread
The specific advice for your case is:
- While you must report an incoming call immediately, you are still free to decide the call has failed and tell the system later, asynchronously. To do this, call reportCallWithUUID:endedAtDate:reason:. This will tear down the incoming call UI even if the user has not answered the call.
If the user is particularly fast at tapping the accept call button, or if the network conditions are poor or there is otherwise a lot of latency, then you should simply wait until the necessary handshakes are complete before calling fulfill on the CXAnswerCallAction. To the user, it simply appears that the call is taking some time to connect, which is a common experience even with standard phone calls.
Note that the system takes a few seconds for the incoming call UI to animate in, during which the app has the opportunity to complete this handshake, so this will only have a user-visible impact if it takes a significant time for the handshake to complete.
So, what you need to do is report the call, then when you determine that the call is no longer valid (which shouldn't take long at all), immediately end the call.
Related Topics
Disabling Allowsbackgroundlocationupdates (Cllocationmanager) Doesn't Work After Is Was Enabled
My App Is Getting Crashed on UIdocumentpickerviewcontroller
iOS Add Button to Widget Extension
Cannot Call Value of Non-Function Type 'Module<Firebase>'
I Get Nil When Using Nsdateformatter in Swift
Converting Nsdictionary to Xml
Converting Nsdictionary to Xml
How to Sort Core Data Array Based on Date Entered
Upgrading to Swfit 3: Cannot Override 'Init' Which Has Been Marked Unavailable
Indexpathforrowatpoint Returns Nil Only for First Cell in a UItableview
Xcode UI Test Uikeyinput Typetext
Calculate Controlpoints While Drawing in iOS
Uitableviewcells Initial Load View/Display Issue
How to Add Pagination in UItableview.
Selecting All The Items in UIcollectionview iOS, Even The Cells That Are Not Visible