Navigate from userActivity handler to other view - ios

I want to navigate to a view from a Siri suggestion. The problem is that I need the normal app flow. So first I load the normal view, which redirects and it that view I need to do an action.
But how do I know that it is called from a suggestion? I did try to set an UserDefaults key. But sometimes that property is not set (it's not set when I check the value because sync issues).
Also the notifications center is not working as I can't set the observer at the right time.
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool
{
if userActivity.activityType == "myId" {
UserDefaults.standard.set(true, forKey: "openViaSiri")
NotificationCenter.default.post(name: Notification.Name("openViaSiri"), object: nil)
}
return true
}
What is a good solution for this?

Related

How to wait for didFinishLaunchingWithOptions to finish before handling universal link?

In my Swift iOS app I asynchronously set up the initial view controller in the AppDelegate inside didFinishLaunchingWithOptions. Now I'm adding a Universal Link such that if you go to the corresponding URL on your phone it will automatically open the right page in the app (in this case it's a password reset URL that, if correct, will open the password reset form inside the app).
I am handling the linking inside application(_:continue:restorationHandler:) (continueUserActivity) in my AppDelegate.
It works if the app is already running but otherwise the link handling happens before my app has finished setting up the initial view controller and the view controller for the deep link gets dismissed when didFinishLaunchingWithOptions returns.
How can I ensure the app is finished setting up before I present view controllers from continueUserActivity? I can't/shouldn't prevent didFinishLaunching from returning until the view controllers are set up, so the method returns before the initial view controller is presented.
I know I can check the user activity dictionary inside didFinishLaunchingWithOptions but continueUserActivity is still called (it would be much simpler if it was only called if the app was running, like how didReceiveRemoteNotification works). Is there a condition to check inside this method to defer the link handling to didFinishLaunching?
Some simplified UIApplicationDelegate code to help explain:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
doSomeAsyncSetup(
completion: {
presentSomeViewController()
}
)
return true
}
func application(_: UIApplication, continue userActivity: NSUserActivity, restorationHandler _: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
if userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL,
let components = NSURLComponents(url: url, resolvingAgainstBaseURL: true),
let params = components.queryItems,
components.path == pathThatIExpect
{
handleUniversalLink(params)
}
return true
}
Note there seems to be a slightly different distinction when the app has opted into Scenes but my app is not using that (https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app).
A DispatchGroup should be able to handle this for you.
private let dispatchGroup = DispatchGroup()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
self.dispatchGroup.enter()
doSomeAsyncSetup(
completion: {
presentSomeViewController()
self.dispatchGroup.leave()
}
)
return true
}
func application(_: UIApplication, continue userActivity: NSUserActivity, restorationHandler _: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
if userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL,
let components = NSURLComponents(url: url, resolvingAgainstBaseURL: true),
let params = components.queryItems,
components.path == pathThatIExpect
{
self.dispatchGroup.notify {
handleUniversalLink(params)
}
}
return true
}
In the case where your app is being launched, the dispatch group is entered in didFinishLaunching and it is left once the view controller is presented.
Once the dispatch group is empty, the notify will execute the user activity code; so only after your view controller is presented.
In the case where your app is already launched, the dispatch group will be empty since didFinishLaunching isn't called and so the notify will execute immediately
You can use serial queue for that. For example:
serialQueue.async {
self.presentSomeViewController()
}
And
serialQueue.async {
self.handleUniversalLink(params)
}

iOS - AppDelegate not retrieving userActivity when Siri intent is launched

Actually making an app in swiftUI and trying to handle Siri intents to start a call in my app. I made a extension target in my project, added Siri capability and App groups in main and extension targets, my intent handler trigger the Siri request correctly at all, starting Siri and asking to make a call to some contact works perfectly, my apps gets launched and at this point is supposed to receive an activity from the intent, but nothing happen...
My intent handler looks like this:
import Intents
class IntentHandler: INExtension, INStartCallIntentHandling {
func handle(intent: INStartCallIntent, completion: #escaping (INStartCallIntentResponse) -> Void) {
let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartCallIntent.self))
guard intent.contacts?.first?.personHandle?.value != nil else {
completion(INStartCallIntentResponse(code: .failureContactNotSupportedByApp, userActivity: userActivity))
return
}
let response = INStartCallIntentResponse(code: .continueInApp, userActivity: userActivity)
completion(response)
}
func resolveContacts(for intent: INStartCallIntent, with completion: #escaping ([INStartCallContactResolutionResult]) -> Void) {
var contactName = ""
if let contacts = intent.contacts {
contactName = contacts.first?.displayName ?? ""
}
//This shared method is used to get contact data for completion
DataManager.sharedManager.findContact(contactName: contactName, with: {
contacts in
switch contacts.count {
case 1: completion([.success(with: contacts.first ?? INPerson(personHandle: INPersonHandle(value: "1800-olakase", type: .phoneNumber), nameComponents: nil, displayName: "ola k ase", image: nil, contactIdentifier: nil, customIdentifier: INPersonHandle(value: "1800-olakase", type: .phoneNumber).value))])
case 2...Int.max: completion([.disambiguation(with: contacts)])
default: completion([.unsupported()])
}
})
}
func confirm(intent: INStartCallIntent, completion: #escaping (INStartCallIntentResponse) -> Void) {
let userActivity = NSUserActivity(activityType: NSStringFromClass(INStartCallIntent.self))
let response = INStartCallIntentResponse(code: .ready, userActivity: userActivity)
completion(response)
}
}
Added INStartCallIntent to IntentsSupported in Info.plist
So, when the code in func handle(intent: INStartCallIntent, completion: #escaping (INStartCallIntentResponse) -> Void) did complete is supposed to send the NSUserActivity to my app delegate directly to func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool ... but is not triggering that function. At the moment my app can't make new calls because is not getting any userActivity to retrieve contact data.
Also I try with func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Void) -> Bool but is not working too.
To being more specific I follow this tutorial but changing the
INStartAudioCallIntent for INStartCallIntent because is deprecated. I don't know if swiftUI is the problem or not, I used #UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate to add my app delegate to the swiftUI life cicle
And yes, my app has request Siri authorization and enabled. Any suggestion? I'm missing something?
Ok, I figured it out: The problem was that I was trying to get the NSUserActivity from the method func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool from my AppDelegate that never gets triggered, forget about that and don't follow the documentation from apple if you are using SwiftUI because is not working anymore.
For SwiftUI you must implement .onContinueUserActivity() modifier to fetch the NSUserActivity, and from here you can do whatever you need to do. Example code:
WindowGroup {
ContentView().onContinueUserActivity(NSStringFromClass(INStartCallIntent.self), perform: { userActivity in
//do something
})
}

NSUserActivity: Which method will be called when user will tap (or will make call ) from recent log of my app?

I am implementing CallKit in swift and I'm facing a problem with NSUserActivity while making a call directly from the recent log.
While tapping on my app entry in the recent log, my app is launching but any method of AppDelegate is not calling.
I have turned on Siri from application capabilities and have added NSUserActivityType in info.plist. I've added INStartAudioCallIntent and INStartVideoCallIntent inside NSUserActivityType. The method I've implemented inside AppDelegate class is the following:
func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: #escaping ([Any]?) -> Void) -> Bool {
print("Restoration")
return true
}
When tapping on any entry of my app from the recent log the following line is printed in the debugger console.
AppDelegate-BAA416CD-ED84-4161-A054-B2192AFAD47A application:continueUserActivity:restorationHandler:] has an interaction attached but it is not handled
Change the signature of the function, i.e. the restorationHandler parameter type from ([Any]?) -> Void to ([UIUserActivityRestoring]?) -> Void
func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
//if its a call
guard let handle = userActivity.startCallHandle else {
print("Could not determine start call handle from user activity: \(userActivity)")
return false
}
}
For more details:
https://github.com/Lax/Learn-iOS-Swift-by-Examples/blob/master/Speakerbox/Speakerbox/AppDelegate.swift
I changed the signature and it worked for me.

How to know user is coming into app by using of Universal links

I using Universal linking in my application.
When the user pick on link directly move into root view controller.
But I want to change the rootviewcontroller when user entering through Universal link.
please help to those delegates methods.
There is 2 delegate method that will fire when application is open from link or user tap link as per below.
Swift
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
// handler for URL Scheme "This will called for URL schema"
return true
}
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
// handler for Universal Links "This will called for Universal Link"
return true
}
Use this delegate method:
func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: #escaping ([Any]?) -> Void) -> Bool

NSUserActivity vs. Core Spotlight deep link

I am using both NSUserActivity and Core Spotlight to index content on my app.
The reason I am using Core Spotlight is because I want to index this content before the user access (opens) it.
The problem I am facing is in the AppDelegate method:
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Void) -> Bool
Basically userActivity ONLY contains the correct information when I do this in my viewController:
userActivity = myUserActivity
But it does not work if I do:
CSSearchableIndex.default().indexSearchableItems(searchableItems).
By using the second method (Core Spotlight) I can see all my results in spotlight search, but when I tap the item, I don't have any information in the AppDelegate method.
The worst part is this:
po userActivity.activityType
"com.apple.corespotlightitem"
If I print the activityType, instead of getting my own, I get this one...
Any suggestions?
When your app is launched from a CoreSpotlight result you get a CSSearchableItemActionType.
You need to handle this type of action by retrieving the CSSearchableItemActivityIdentifier - This value will match the identifier you provided for your searchable item when you submitted it to the index.
Something like:
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Void) -> Bool {
if userActivity.activityType == CSSearchableItemActionType {
guard let selectedItem = userActivity.userInfo?[CSSearchableItemActivityIdentifier] as? String else {
return
}
// Do something with selectedItem
}
}

Resources