Quick Reply: NSURLSessionDataTask from "userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:" - ios

I have Remote Notifications set up successfully for my Messaging App and want to incorporate the "Quick Reply"-functionality of iOS, better known as UNTextInputNotificationAction of UNUserNotificationCenter now.
I added the Text Input Action to my incoming message notifications and it is working as expected.
My Problem lies in the UNUserNotificationCenterDelegate function userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:.
No matter how I set up the NSURLSessionDataTask to POST the input response userText of UNTextInputNotificationResponse, the Request always times out. It does work fine when I am "Quick replying" to a notification received while the app is in the Foreground.
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler {
if ([response isKindOfClass:UNTextInputNotificationResponse.class]) {
if ([response.actionIdentifier isEqualToString:#"REPLY_MESSAGE"]) {
[ApiManager chatWithId:#"123456789abcdefg" postTextMessage:[(UNTextInputNotificationResponse *)response userText] completion:^(Message *message) {
if (!message) {
NSLog(#"Sending failed");
}
completionHandler();
}];
}
}
}
The Data Task within ApiManager is built atop a [NSURLSession sharedSession] - Note that this did not work, even after changing it to backgroundSessionConfigurationWithIdentifier: and implementing the needed Delegate methods.
The Log "Sending failed" can be seen through the Console, if I check the error of the DataTask, it shows a "Timeout" error.
I suspect I have something wrong with my background networking, but I cannot wrap my head around it, and nowhere I looked was an answer to be found. The documentation is very sparse here too.

For anyone stumbling about this issue in the future: I was able to solve it by adding content-available:1 to my push notification payload. I am still using a data task and a shared url session here. No delegate either, working with completion blocks, for anyone interested.
I initially did not think this would work, adding the content available flag actually had another reason, so it’s nice that this solves the initial problem I had. Why did I think this wouldn’t work? Easy, because the content available, if we believe the documentation, indicates to the app, that there is content available to download. There is a special application delegate method being called for specifically this situation:
application(_:didReceiveRemoteNotification:fetchCompletionHandler:)
It is a nice side effect that the method which responds to user actions on notifications also gets more time to process.
Neat bonus: I also found out that, without adding the content-available flag, the quick reply would work from the Apple Watch, as, I guess, the OS allows the app to perform longer background operations when responding to a request from the Apple Watch.

Related

iOS - Can I open my VoIP app on answering call using Callkit?

I'm planning to create an iOS VoIP app(not made any iOS app before). I was reading about Callkit in IOS by which one can make his app receive phone call through iPhone native call screen.
I read Callkit api here where it is mentioned that one can know if a call is answered.
Going through this tutorial and here is the code which detects the call is answered:
-(void)reportIncomingCallWithHandle:(NSString *)handle
success:(void (^)())success
failure:(void (^)(NSError * error))failure {
CXCallUpdate *update = [self newCallUpdateWithHandle:handle];
self.callId = [NSUUID UUID];
[self.provider reportNewIncomingCallWithUUID:self.callId update:update completion:^(NSError * _Nullable error) {
if (error) {
if (failure) failure(error);
} else {
if (success) {
success();
}
}
}];
}
See the success block. So is there is a way to open my app when this success block executed? Or can I override default buttons on caller screen to open my app?
I know there is no way to open an app on receiving any kind of notification, or event trigger. So thought may be there is some way if I can do the same using Callkit
I Googled everything but found no clue regarding my above queries. Please help me if it is possible or not.
I encountered the same issue. The behavior varies depending on if the device is locked or not.
Locked: System calling screen appears. You can run the app in the background including view transitions. However, the user will only see the system calling screen although your app is kind of presented underneath the view. As the device is locked, deep links does not work as well.
Unlocked: Calling screen is the same but once the user answers the call, the app will be presented.
As you may know, we can change the icon of the button on the calling screen which opens the app, and that's the best we can do as of now.
You can not open your own VoIP app or custom UI of your App from CallKit. Use can use it in a way as Whatsapp does.
Means you can awake your app from background without using local notification. And OS will show the default incoming screen. You need not to handle anything during call. CallKit is specially made for enhancing VoIP apps by receiving calls in background, by making outgoing calls, by managing Call directory and blocking of users.

Monitoring NSURLSession after app restart

I have a prototype single-view app which monitors download tasks.
I'm trying to deal with following use case:
Downloads are initiated via NSURLSession while app is in foreground. NSURLSession is created with background configuration.
I kill the app with "Xcode Stop", so that app continues download in background. While app is alive, I orderly receive progress callbacks from NSURLSession.
I manually start the app (not by Xcode, but tapping the launch icon), when the downloads have not been completed yet.
I don't receive any URLSession delegate calls for tasks started in previous app's life. The only thing that gets called is handleEventsForBackgroundURLSession but that's called on AppDelegate by the OS (different case than NSURLSession delegate calls).
I want to show progress of ongoing download tasks. Can this be done after app relaunch (when app was terminated by the system, not manually!)?
After app relaunch, NSURLSession is initialized with same identifier, new delegate object, so I figured delegate will continue to receive calls for session's tasks (because session identifier is the same), but apparentely that's not the case.
There is a note in Apple's documentation:
The session object keeps a strong reference to the delegate until your app explicitly invalidates the session. If you do not invalidate the session, your app leaks memory.
but I guess this only applies to case when app is alive. When the app is terminated, then all app's objects are gone.
Make sure NSURLSession is properly initialised when app launches. That's what the problem was in my case. I had TransferManager which initialised session as lazy getter which was not getting invoked...
Now that the NSURLSession is properly initialised, callbacks are fired regularly.
Stupid error, but there it is.
You goal seems to be in number 4, trying to receive URLSession delegate callbacks while in the background. I've been struggling with that myself and wasn't able to find a great solution, however I did realize that whenever I performed any actions (even simply calling the completionHandler() callback) in handleEventsForBackgroundURLSession: I received a call to
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error {
Seems like performing operations might wake up the delegate that was specified when creating your NSURLSession.
That is the correct method to perform any UI updates (such as showing progress) since that's the only location you'll know the background task is complete. Just make sure to call the completion handler callback after you are done!
Also this may be of some help to you: My NSURLSessionDelegate methods are not getting called during a background download

How do I reply to a GKTurnBasedExchange? GKLocalPlayerListener delegate receivedExchangeReplies is called intermittently

There are a handful of posts discussing how Game Center's push notifications were fairly unreliable in the sandbox. However, the sandbox is obfuscated with iOS 9 so, I'm not sure why my Game Center push notifications are so unreliable.
When I reply to the active exchange, the sender is rarely notified.
[exchange replyWithLocalizableMessageKey:#"EXCHANGE_REPLY" arguments:#[] data:data completionHandler:^(NSError *error) {
if (error)
{
NSLog(#"");
}
}];
On the senders device, if I refresh the match data, I'll see a pending reply. If I process the reply, everything works.
The same goes for this method:
- (void)sendExchangeToParticipants:(NSArray<GKTurnBasedParticipant *> *)participants
data:(NSData *)data
localizableMessageKey:(NSString *)key
arguments:(NSArray<NSString *> *)arguments
timeout:(NSTimeInterval)timeout
completionHandler:(void(^__nullable)(GKTurnBasedExchange *exchange, NSError *error))completionHandler
At this point, I'm thinking my best option is to run my own push notification logic to trigger updating match data. That or I've read that sending reminders is more reliable though I believe there are throttling limits around that.
Update
I've tried using only devices and not the simulator. Same issue. Looks like it's a pretty well known problem though. It's even noted in this book on page 766.
Update
Sending reminders didn't help.
Update
Often when replying to an exchange, I'll get this error from GameKit.
The connection to service named com.apple.gamed was interrupted, but the message was sent over an additional proxy and therefore this proxy has become invalid.
Exchanges has until Oct 2020 never actually worked as needed, nor as specified, due to a bug in the Apple backend. Now however, an Apple engineer seem to suggest it has been fixed - asking me to verify that it works. Which I intend to do ASAP (I just need to update Xcode) using my public project: https://github.com/Gatada/TurnBasedGameFlow
FURTHER DETAIL
A turn based exchange relies on the turn holder being notified when the exchange is completed, so the turn holder can resolve it (submit it to Game Center). This notification however, was never pushed to the turn holder.
As a result of this bug, the games we made had to rely on the turn holder re-loading the game after the exchange completes, and our code had to gracefully handle the turn submission failing due to game data being out-of-sync (caused by the completed exchange).
I had a one-on-one Game Center session with Apple during WWDC 2020, where I reported this issue with hard evidence (after all, this bug had been around since 2010) which convinced the Apple engineer. It took them 3 months to get back to me, and another 3 months for me to get back to them - hehe, bringing us to now.

Continue a NSURLConnection after the App has been terminated

I would like to know how it is possible to continue a async NSURLConnection, which has been started in the foreground, when the app will be terminated.
Currently I am starting a NSURLConnection when the app goes in the background. This works fine as long as the user is slower than the connection, when he wants to terminate the app. But when the user is quicker than it, the connection can't be established.
Here is my code:
// AppDelegate.m
- (void)applicationDidEnterBackground:(UIApplication *)application
{
AnObject *newObject = [[AnObject alloc] init];
[newObject InactiveApp];
}
// AnObject.m
- (void)InactiveApp
{
self.backgroundTaskID = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:NULL];
// setting up the NSURLRequest
// [...]
dispatch_async(dispatch_get_main_queue(), ^
{
[NSURLConnection connectionWithRequest:request delegate:self];
});
}
// delegate functions, endBackgroundTask-closing, etc. is following
Unfortunately this is not working and I would like to know whether someone knows another way to fix it. There has to be a way which is similar like Snapchat or WhatsApp is doing it, because when you write an message and terminate the app right after pressing send, the message will be delivered.
The only way I could imagine is to do it with a background fetch but I think that is not the best solution, due to the fact that I just want to make one single connection when the App is send to the background.
I agree with Andy, that you should pursue NSURLSession and a background NSURLSessionConfiguration. See downloading content in the background section of the App Programming Guide for iOS: Background Execution.
By the way, the idea in your question will work fine (especially if you need support for iOS versions prior to 7.0, where NSURLSession and its background sessions are not available). Two observations regarding your code snippet:
The way you've written it, would appear that your AnObject would be prematurely deallocated when it falls out of scope and your app would therefore fail when it tried to call the delegate methods. Make sure to maintain a strong reference to AnObject.
Don't forget to call endBackgroundTask when the download is done. Likewise (and more subtly), the timeout handler should end the background task, too. See the Executing Finite Length Task section of the aforementioned App Programming Guide.
By the way, you mention requests continuing after the app is terminated. If a user manually terminates an app, that kills both background tasks contemplated in your question as well as background NSURLSession tasks. These are intended to gracefully handle continuing tasks if the app leaves foreground, not if the user manually terminates the app. The NSURLSession approach gracefully handles terminations due to memory pressure, but not manual termination.

Handling timeout with Alert View - iOS

Is it possible to handle a request timeout with a UIAlert? I would like to inform the user that there has been a time out. I set 0.0 just for testing to see if it would occur. The log does not print out so i do not believe i am handling correcting
request.timeoutInterval=0.0;
and to handle it:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
if(error.code == NSURLErrorTimedOut){
NSLog(#"Time out");
}
}
I am using NSURLConnectionDelegate and NSURLConnectionDownloadDelegate
Thanks.
You can find a list of all the relevant error codes here. Note that -1009 is kCFURLErrorNotConnectedToInternet. To force a timeout, you need to have the ability to suppress the actual sending of the URL, or find one that just goes into a black hole.
Apple bundles Network Link Conditioner with Xcode (its in the networking tools I believe). There is a great article on this tool on NSHipster.
Another way (I believe) to get a timeout is to immediately after sending a request is to switch to another app, putting yours in the background. Wait 5 minutes, switch back, and look at the log. What you can do is in a demo app continually send out NSURLConnections - that is, once the first returns, send another. so there is always one outstanding. Now switch your app out - switch to another app in the simulator - wait, then return. You should be able to catch the condition this way, you can see the affect of changing the timeoutinterval value.
There was a long thread about timeouts in the private Apple forums for iOS networking - you may be able to find it. In the end, the system enforces a reasonably long minimum (as I recall), and you may be able to make it longer but not shorter. This hidden behavior has for sure baffled many people. The responder to the question was Quinn "The Eskimo", one of Apple's most experienced networking engineers.

Resources