Is scene(_:openURLContexts:) called when app is launched by an airdropped file? - ios

In the old versions of my app, the scene(_:openURLContexts:) method is always called in these two cases:
The app isn't running. Then It's launched by an airdropped file.
The app is already running. Then a file is airdropped.
I created a separate app to work on the new version and made a lot of code changes. None of the changes should affect the above behavior, but I find the case 1 doesn't work. If the app isn't running, the airdropped file launches it successfully, but scene(_:openURLContexts:) isn't called.
More information:
To verify scene(_:openURLContexts:) is really not called, I added logs in the method and used Console app to monitor the logs.
I tested the old app on the same phone, which runs iOS 16.0.2. It works fine in both cases. So this isn't an OS issue.
Both of the apps are SwiftUI apps.
I searched on the net and found this workaround. It works for me.
I've discovered that the code below is need in willConnectTo session:
self.scene(scene, openURLContexts: connectionOptions.urlContexts)
The Apple's doc is very concise on these APIs. It's not clear to me what's the expected behavior and why my two apps have different behavior. Does anyone know if scene(_:openURLContexts:) is expected to be called when when app is launched by an airdropped file?
UPDATE: from this answer, scene(_:openURLContexts:) is not expected to be called when when app is launched by an airdropped file. If so, I don't understand why my old app works (from my search on the net, I'm not the only one who observed the different behaviors).

Related

Differences between normal app startup and a wakeup from WatchKit's sendMessage?

According to the docs, sending sendMessage from the WatchKit app will wake the iPhone app if it is inactive.
I would like to know the differences between such a wakeup app startup and a normal one. Surely a background process has a lot of differences to a normal foreground app. However, I could not find a thorough documentation on the subject.
Here's what I found so far:
Since such a "wakeup" is not a normal app startup, I expect didFinishLaunchingWithOptions to receive a special launchOptions key. After all, the user did not start the app on the home screen, so there should be an indication in launchOptions. But when I tried it out, launchOptions was nil. Doesn't that contradict the docs?
I also thought there should be differences because there is no UI present in a background process. So perhaps [UIScreen mainScreen] might be nil. But there seems to be no difference in mainScreen in the background process. In my AppDelegate.m, the code that creates the main window seems to run exactly the same way as in the foreground process.
I also expect that there are limits to the background process. Since the user did not actively start the process, I'm pretty sure that it cannot run for an infinite amount of time. Maybe there are also stricter memory limits.
Is there a way I can debug such a background process in XCode? Can I tell XCode "wait until the app runs on the iPhone", such that the debugger gets attached as soon as the app runs?
I also tried this in a React Native app. The Objective-C code in AppDelegate.m seemed to run exactly the same way, regardless of background or foreground process. However, the whole Javascript part of the app did not run (which is kind of expected, because in a background process, we do not need any React UI). But there must be a difference in the process that causes the Javascript part to not run.
Please don't consider this question to be about "more than one question". The question of this post is quite clear: I want to know all the differences between a didReceiveMessage background process and a normal one. In the enumeration above, I just listed all the differences I would expect or that I have encountered so far, and the lack of documentation on those topics.
I think the background mode is just a UIKit concept. The app is started (thanks to the UIApplicationMain function) as a regular one but your app UI is not rendered.
It is just a warning: this is a transition state, your app can be suspended at any moment, be concise. The documentation is clear.
Regular UIKit APIs are available (if it was not the case, imagine all the potential crashs). But you won't receive any external events like touches.
Some external tasks like asking permissions, launching audio sessions etc would probably not be available too.
You can wait for the app to be launched by using the wait for the executable to be launched option in the scheme panel.
But when I tried it out, launchOptions was nil. Doesn't that contradict the docs?
Unfortunately LaunchOptions doesn't cover all ways an app gets launched. So if you see it nil then your case is one them too.
But there seems to be no difference in mainScreen in the background process.
That's true and expected. Things all get launched using the main thread. See here
Is there a way I can debug such a background process in XCode?
GaetanZ Has already answered this. Additionally you can use os.log and console together. That gives you a more realistic approach. Xcode interruptions are not fun to deal with. The wait for the executable to be launched scheme change often makes debugging super slow or often Xcode just disconnects or even throws you weird errors and the app gets killed without even giving you the option to attach to the debugger again.
I often use reboot the phone entirely. And then use oslog to see what happens to my app without ever having Xcode connected. For more on that see here.
A reboot is different to user-termination because often things don't get launched after-user termination. That being said I don't think the OS restricts app launch if user engages with their watch — even after a user-termination. Because user-engagement trumps everything.
However, the whole Javascript part of the app did not run (which is kind of expected, because in a background process, we do not need any React UI).
You then also said "which is kind of expected, because in a background process, we do not need any React UI"
I know very little of React, but also I'm confused what your question is about if you say it's expected.
But the part that the AppDelegate goes through all it's life-cycle is expected as previously mentioned.
You need to add a gazzilion amount of logs to see where you have a different code-path.
Most developers don't know that launching the app into the background will go all the way till the root viewController of your app and all its child viewcontrollers and stuff. But you learned it the hard way. Just as I did. Congratulations!
But once you know of that then the next thing is making sure your app doesn't segue into a different code-path i.e. on a normal launch you get WillBecomeActive and willEnterForeground, for a launch into the background you get something else. I'm not sure what it is right now. I think it's WillBecomeActive & didEnterBackground. Not sure.
Or like you may not be setting the delegate of your webview before things fire off and you miss its callbacks.
Remember app being in a background state doesn't mean things get executed on background queue. everything gets executed on the same queue/thread as it would in a foregrounded app. The only thing different is that the OS will often restrict network calls when an app isn't provisioned for them.
Likely that's what's happening. Like the OS doesn't want a webview to make network calls when it hasn't informed the app to be using and background Tasks as mentioned here or here

iOS 13 Background Tasks not working when App terminated?

i'm trying to get the new BackgroundTask framework to work in my app. I'm testing on a real device running iOS 13.4.
Everything works as expected (minimal setup, like is described here: https://developer.apple.com/documentation/backgroundtasks/bgtaskscheduler )
Unfortunately the task gets only called if the app is still in the background. If applicationWillTerminate() is called (e.g. when the user closes the app from the multitasking manager), the task will not get executed.
I have not found anything on this topic in the docs. Also i think this behaviour was different with the now deprecated Background Modes (see also: iOS - Background Services when app is terminated )
Can anyone clarify/confirm/correct on this?
Thanks in advance!

NSURLSessionDelegate methods not called in TestFlight installs

I am building an app targeting iOS 8.0 that needs to download files that are up to 250Mb. I had a version of the download code roughly working using Alamofire but I recently replaced that with a pure NSURLSession implementation. This new implementation is working as expected in the simulator and on my iPhone 5S running iOS 9.2 (13C75). It works on my phone whether I install through XCode and a physical connection or through TestFlight. I have deleted the app, restarted the phone, and reinstalled and it always completes downloads correctly. One of my colleagues pulled the code and was able to successfully simulate it from XCode.
However, downloads fail on all of my collaborators' devices. They are installing the app through TestFlight and are set up as internal testers. One collaborator also has a iPhone 5S running iOS 9.2 (13C75). The Alamofire-based implementation worked as intended when deployed through TestFlight to those same devices and very little other code has changed.
I have added some remote logging and I can see that on the failing devices, downloads are correctly triggered and the download tasks I create each have a taskIdentifier which I can log. However, none of the NSURLSessionDelegate or NSURLSessionDownloadDelegate methods are called.
What suggestions do you have for troubleshooting?
Is it possible that this could be related to a TestFlight problem? My current next step is to try an alternative to TestFlight. Ultimately, I would like to be able to deploy betas through TestFlight if possible.
I'm using Swift 2.1.1 in XCode 7.2 (7C68).
The problem turned out to be that I had set the NSURLSessionConfiguration discretionary property to true. The docs state the following which sounded good to me:
When transferring large amounts of data, you are encouraged to set the
value of this property to true.
But I failed to appreciate the consequences of the rest of the text:
For example, the system might delay transferring large files until the device is plugged in and connected to the network via Wi-Fi.
The reason that installations through XCode were working was presumably that the phone was either being charged because it was connected to my computer when I started a download or had just been charged.
I initially suspected that the problem came from either TestFlight or something about the state of the phone. Remote logging with Sentry (which I already use for the backend) was very helpful for getting some insights into what was different when my collaborators ran the app. I eventually added in implementations for all the NSURLSessionDownloadDelegate methods to log which ones were called. This led me to see that didReceiveChallenge was the only delegate method being called and I then spent some time thinking that it could be an auth-related problem and troubleshooting in that general area.
Finally, I started noticing that my own install would fail sometimes and one collaborator had a successful download. That made me start thinking about the kind of phone state that could prevent a download. I went back to the NSURLSession configuration that I had and that's when I read about the discretionary property.
In retrospect, carefully checking the configuration would have been a good step to do earlier. Another good thing to do would have been to log the state property of the download tasks that were created.

PFQuery findObjectsInBackgroundWithBlock: in WatchKit app works in simulator, not on watch unless iOS app is in foreground

I'm writing a WatchKit app which needs to make a query to our Parse server. Because this is a network call, I follow the following procedure:
I make a call from WatchKit to the iOS app using openParentApplication:reply:
Inside of handleWatchKitExtensionRequest:userInfo:reply:, I start a background task with a UIBackgroundTaskIdentifier to ensure the iOS app isn't terminated immediately
I set up my PFQuery and call findObjectsInBackgroundWithBlock:
In the block I pass to the PFQuery, I reply the relevant information to the watch
I wrote this app maybe a month ago before the Apple Watch was out, and it ran just fine in the Watch simulator. Yesterday I received my Apple Watch and I was able to get the app running on the watch, but it doesn't quite work - I managed to figure out that when the iOS app is actively running in the foreground on the iPhone, then everything is great; but if the app is not active (e.g. backgrounded or terminated), then findObjectsInBackgroundWithBlock: never actually calls the block, and execution seems to just stop.
Does anyone have any ideas of what might be going on? My initial thought was that the iOS app is getting suspended/killed before it can complete the query, but I was pretty sure the background task would prevent that.
PS. If anyone stumbles upon this question because they were looking for how to get a WatchKit app running on an actual watch, I found this post extremely helpful in getting it working.
Turned out that I had several problems related to openParentApplication:reply:, and they all seem to have been resolved by using the technique from this post:
http://www.fiveminutewatchkit.com/blog/2015/3/11/one-weird-trick-to-fix-openparentapplicationreply
I had started a background task for my own app, but I didn't use the "bogus workaround" background task described in this post, because it wasn't necessary for me in the simulator. On the watch, though, it apparently was.

WatchKit : handleWatchKitExtensionRequest multiple instances

So I have a parent app that logs in to a server, creates a user etc. I then use handleWatchKitExtensionRequest from the WatchApp to access the parent app but everything is null.
My question is, does handleWatchKitExtensionRequest get run in a different instance to the parent app? I read somewhere it does but I just need this confirmed.
Thanks
If handleWatchKitExtensionRequest returns nothing it means that the passed reply is never called. This is most probably due to application.didFinishLaunchingWithOptions runs into an error when the app is launched in background mode.
I had a similar issue with my app where I did some initializations on the app's navigationController. It turned out that the navigationController is not accessible when the app is launched in background mode.
I would suggest to completely comment out the code in didFinishLaunchingWithOptions and see if handleWatchKitExtensionRequest calls its reply. If so, uncomment the code in didFinishWithLaunchingWithOptions step by step to detect the faulty code.
This may be really hard to track down because one cannot attach the debugger to the iPhone app at this time of launching.
Another pitfall:
The function signature of handleWatchKitExtensionRequest has changed in Swift 1.2 without giving any compiler errors. It took me hours to figure that out.

Resources