Universal links: how to go back programmatically? - ios

I have a first iOS native app (let's say the "caller app") that opens a second one (let's say "callee app") with an URL of the form https://calleeapp.com (Universal Link). I want the user to perform some task in the "callee app", and when finished then I want to go back automatically to the "caller app" providing the result of such task (URL would be then something similar to https://callerapp.com/check?result=xxxx).
What should be the best/appropriate way to handle this?

We'll, you'd need to be able to have code on both the callee and caller app then to do this. (Unless a third party app implements some sort of API for callback URLS).
On the caller app though you'd want to look at the following AppDelegate method:
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Void) -> Bool {
// if it opened the app from some link
if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
// the query will give you "result=xxxx"
if let query = userActivity.webpageURL?.query {
// code that pulls out the result from the string you get back
}
...
You also have to actually support the universal link by making sure that your AppName.entitlements has the an Associated Domains key with one of the values being applinks:callerapp.com so that the caller app knows it can open urls in that scheme. (And vice versa on the callee app if you're also implementing that)

Related

"continue userActivity" method not called, despite Firebase deep link successfully opening app

I'm using Firebase's deep linking to try and provide a certain function in my app when a user clicks a link that was sent to them. The deep link is setup properly on the Firebase web portal.
Clicking the link sent to the user DOES open my app, but does not trigger the continue userActivity method in my App Delegate, as none of the code contained in there is every executed.
I've tried the solution suggested in this StackOverflow post but no success, that's switching [UIUserActivityRestoring] to [Any] in the method declaration.
In Xcode, I have setup the associated domain to match what I set on Firebase: applinks:myappname.page.link; and I have added a "URL type" with identifier "Bundle ID", and URL scheme of ca.mycompany.myappname, with role editor.
All this is running on-device, of course, as I don't expect this to work in the simulator.
This is the method in my app delegate which should be called, however is not.
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Void) -> Bool {
print ("The link worked")
}
I made sure didFinishLaunchingWithOptions returns true always as well.
I expect the "The link worked" to actually print in debugging console, but it does not. Breakpoints indicate the method isn't being called at all, as the breakpoints are never reached.
There is a good change that somebody facing this problem deals with iOS13 / SwiftUI, too. Just like me.
I have found this article which solved all my problems.
Basically you have to be aware that some methods from the new SceneDelegate replace what was called in AppDelegate before iOS 13. Additionally there seem to be edge cases where methods from the AppDelegate still might be needed to handle deeplinks processing.
For anyone who needs it:
you need to use the below code to handle deep links from the link to the app that is already installed(other func application are for different things)
func application(_ application: UIApplication, continue userActivity: NSUserActivity,
restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
let handled = DynamicLinks.dynamicLinks().handleUniversalLink(userActivity.webpageURL!) { (dynamiclink, error) in
// ...
}
return handled
}
https://firebase.google.com/docs/dynamic-links/ios/receive #6
For scene application try to add your logic as part of
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
let handled = DynamicLinks.dynamicLinks().handleUniversalLink(userActivity.webpageURL!) { (dynamiclink, error) in
//...
}
}

Why is the NSUserActivity contentAttributeSet nil when opening via Spotlight?

I'm computing a NSUserActivity for when I view a specific screen in my app. (Doesn't matter what, imagine a Location)
I set a lot of information about this activity/location using the contentAttributeSet.
When I visit this screen over and over, I set the viewController's userActivity property to this derived activity, and also call becomeCurrent() on it.
Eventually Siri will suggest that shortcut in Spotlight. As expected and desired. I tap on that, it launches my app and calls:
func application( _ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool
however, the .contentAttributeSet property will be nil.
Why? Have I not understood something here? I guess any data I need to access later should be in the userInfo dictionary? That works (as long as I don't set webpageURL)

VOIP outgoing call from native call UI

Whenever I receive incoming call in my VOIP application I can see the logs on my native iPhone call UI.
I wanted to make outgoing call from native iPhone callog of UI by clicking last incoming call. Like it works for WhatsApp , Skype , hangout etc.
How is it possible for outgoing call ?
Below are the methods I wrote for incoming call :
-(void)reportIncomingCall:(NSUUID*)UDID handle:(NSString*)handle;
-(CXCallController*)startCall:(NSUUID*)UDID handle:(NSString*)handle
-(void)connectCallWithCallController:(CXCallController*)controller
I know there is one more method for outgoing call.But I don't know when to call this:
- (NSUUID *)reportOutgoingCallContactIdentifier:(NSString *)identifier destination:(NSString *)name telNumber:(NSString *)telnum
When tapping on an item in the native iOS Call log, the application delegate's continueUserActivity function is called. My implementation looks something like this in Swift:
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Void) -> Bool {
self.startCallHandle = userActivity.startCallHandle
// Cache call handle here, and make call using cached call handle
// when applicationDidBecomeActive is called
return true
}
startCallHandle is defined in file NSUserActivity+StartCallConvertible.swift, as seen in the SpeakerBox sample project.
In your app's Info.plist, you must have INStartAudioCallIntent and/or INStartVideoCallIntent. Once again, see the SpeakerBox example app for details.

What happens when you return `false` from `application(_:, continue:​, restoration​Handler:​)`?

I have enabled Universal Links in my app. The corresponding delegate call to handle those links is
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Void) -> Bool {
if canHandle(userAcitivity) {
// Handle the universal link.
}
else {
// ⛔️ Don't handle the universal link.
return false
}
}
No I wonder what exactly happens when I return false from this method. In the beginning I thought that Safari would simply open the link instead as it would without universal links enabled. However, I figured that my app is still opened and the documentation states:
If you do not implement this method or if your implementation returns false, iOS tries to create a document for your app to open using a URL.
What exactly does this mean?
What kind of document is created and how is my app notified about that?
Actually, for continueUserActivity the description of the return value is:
Returns true to indicate that your app handled the activity or false to let iOS know that your app did not handle the activity.
If you return YES from this function the OS will understand that no further processing of the NSUserActivity will be required - your app has done everything that needs to be done. If you return NO from this function, the OS will understand that an activity requiring OS processing may have occurred and may need to be handled.
You can get more background on all this in Apple's Handoff docs, here: https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/Handoff/HandoffFundamentals/HandoffFundamentals.html#//apple_ref/doc/uid/TP40014338

How do I tell if the app is launched from a URL scheme or a normal launch?

Basically, whenever the app launches or opens I have a check to see if there's a link on the clipboard, and if so I ask the user if he/she wants to add it.
However, I recently added the ability to use x-callback-urls to add URLs into the app. If they use an app like LaunchPad to open a URL in my app, the "do you want to add the URL from your clipboard?" notification still opens, which doesn't make much sense as they've already launched it to add that URL.
Problem is, the openURL: method in my AppDelegate is called to handle the URL scheme after appDidFinishLaunching and appWillEnterForeground, so I can't put a check in those to see if the openURL method was already called. I could use a dispatch_after, but that seems lazy and hacky.
Is there a way to check how the app was launched, i.e.: via a URL scheme or via a manual open?
Don't implement applicationDidFinishLaunching:. Use application:didFinishLaunchingWithOptions: instead. Likewise don't use application:handleOpenURL:, use application:openURL:sourceApplication:annotation: instead.
If you are launched from a URL the options dictionary will contain a UIApplicationLaunchOptionsURLKey key, and the value for that key will be the URL.
However, if your app is already running but is in the background and the user invokes a URL that re-opens your app, you will get a application:openURL:sourceApplication:annotation: message instead.
What you really need to do is implement a handleURL method, and then call that method from both application:didFinishLaunchingWithOptions: and application:openURL:sourceApplication:annotation:
EDIT:
Note that in iOS 9, Apple deprecated application:openURL:sourceApplication:annotation: and added the new method application:openURL:options:.
If your app is iOS 9 and later only, you should implement the new application:openURL:options: method. If you need to support iOS 9 and earlier versions, you should probably implement both application:openURL:sourceApplication:annotation: and the new iOS 9 method application:openURL:options:. The OS will call the correct version for the OS version you are running. I would then create a common method that both of those call. That way you get called from both OS versions, but your code to handle opening an URL is only in one place.
applicationDidBecomeActive is called after openURL and continueUserActivity (and after appWillEnterForeground), so is a good place to determine how the app was opened.
Cache the url in openURL and continueUserActivity (depending on the source, deep links may come from continueUserActivity. Anecdotally, I always see that method called when opening a link from Messages. Then check that url in applicationDidBecomeActive
private var launchURL:URL?
static func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Void) -> Bool {
launchURL = userActivity.webpageURL
// Do something with userActivity
return true
}
static func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool {
launchURL = url
// Do something with URL
return true
}
static func applicationDidBecomeActive(_ application: UIApplication) {
defer {
launchURL = nil // Just cleaning up
}
let didLaunchFromURL = launchURL != nil
}

Resources