I have an app that uses deep linking to navigate to a page when a user shares specific content in the app with another user. This is working when the second user has the app already running, but if the app is not running it simply opens the app and remains on the main screen. I know I must be missing something really simple here, but I just can't figure it out and can't find any answers regarding this on google.
My code in AppDelegate.swift:
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
let urlPath : String = url.path as String!
let urlHost : String = url.host as String!
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
if(urlHost != "example.com")
{
print("Call Not From Correct Host. Not Continuing...")
return false
}
if(urlPath == "/articles"){
let article: ArticleDetailsViewController = mainStoryboard.instantiateViewController(withIdentifier: "ArticleDetailsViewController") as! ArticleDetailsViewController
self.window?.rootViewController = article
}
self.window?.makeKeyAndVisible()
return true
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
This is correct behavior.
You should handle it in appliction(_: didFinishLaunchingWithOptions:)
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
if let url = launchOptions[.url] as? URL, let annotation = launchOptions[.annotation] {
return self.application(application, open: url, sourceApplication: launchOptions[.sourceApplication] as? String, annotation: annotation)
}
return true
}
If you are using sceneDelegate scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) function will work when app is launched after terminated state.
url for deeplinking will be available in connectionOptions.urlContexts
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
navigateToDeepLinkScreen(urlContexts: connectionOptions.urlContexts)
}
For Swift4.2
if launchOptions != nil{
let launch = launchOptions![UIApplicationLaunchOptionsKey.userActivityDictionary]! as! Dictionary <String,Any>
if ((launch["UIApplicationLaunchOptionsUserActivityKey"]! as! NSUserActivity).webpageURL != nil){
if defaults.bool(forKey: DEFAULTS_IS_FIRST_START){
print((launch["UIApplicationLaunchOptionsUserActivityKey"]! as! NSUserActivity).webpageURL)
}
}
}
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if let url = launchOptions?[.url] as? URL {
return handleWidgetUrl(url)
}
return true
}
// Custom function to handle url and do some actions
private func handleWidgetUrl(_ url: URL) -> Bool {}
For SceneDelegate:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
/* some stuff */
for activity in connectionOptions.userActivities {
if let url = activity.webpageURL {
// handle
}
}
}
Related
I am trying to implement deep links to navigate to posts on an app, it was an older project so I had to add the SceneDelegate class. The deep link implementation works only when the app is active or in background. If the app has not been loaded the deep link will not work. I've seen many posts and tutorials on this and have not found out why, has anyone had similar issues?
In the AppDelegate class I have added implementation to handle links for the following functions:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {}
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {}
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {}
In SceneDelegate I implement handling the links in the following functions:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {}
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {}
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {}
the implementation in those functions looks like this:
let navigator = Navigator()
navigator.getDesination(for: url)
func getDesination(for url: URL){
let destination = Destination(for: url)
let ivc = InstantiateViewController()
switch destination {
case .post(let postID):
ivc.openPostVC(id: postID, showComment: true, commentID: nil)
case .user(let userID):
ivc.openProfileVC(userID: userID)
default:
break
}
}
enum Destination {
case post(Int)
case user(Int)
case feed(String)
case store
case safari
init(for url: URL){
if(url.pathComponents[1] == "p"){
self = .post(Int(url.pathComponents[2])!)
} else if(url.pathComponents[1] == "user") {
self = .user(Int(url.pathComponents[2])!)
} else if(url.pathComponents[1] == "store") {
self = .store
} else if(url.pathComponents[1] == "s") {
self = .feed(url.pathComponents[2])
} else {
self = .safari
}
}
}
func openProfileVC(userID: Int){
let service = UserPool.shared.request(for: userID)
let storyboard = UIStoryboard(name: "Profile", bundle: nil)
let profileVC = storyboard.instantiateViewController(withIdentifier: "ProfileView") as! ProfileViewController
profileVC.userService = service
profileVC.shouldNavigateToHome = true
profileVC.shouldNavigateToHomeAction = {
self.loadMainStoryboard()
}
let navigationVC = UINavigationController(rootViewController: profileVC)
navigationVC.view.backgroundColor = .white
if #available(iOS 13.0, *) {
guard let sceneDelegate = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate else {return}
sceneDelegate.window?.rootViewController = navigationVC
sceneDelegate.window?.makeKeyAndVisible()
} else {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
appDelegate.window?.rootViewController = navigationVC
appDelegate.window?.makeKeyAndVisible()
}
}
The websites app-site-assocation file looks like this and have added associated domain in Xcode:
{"applinks":{"apps":[],"details":[{"appID":"{my ID}","paths":["*"]}]},"webcredentials":{"apps":["{my ID}"]}}
In iOS 13 and later with a scene delegate your app can observe the incoming universal link event at launch like this:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
if let url = connectionOptions.userActivities.first?.webpageURL {
// ... or might have to cycle thru multiple activities
}
}
If the app was already running you use this:
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
if let url = userActivity?.webpageURL {
// ...
}
}
(I have a very simple downloadable demo app, and it proves that this really does work. I do not understand the claim that it does not; perhaps the problem is a failure to understand how to test.)
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
//---------
//-------
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
for context in URLContexts {
print("url: \(context.url.absoluteURL)")
print("scheme: \(context.url.scheme)")
print("host: \(context.url.host)")
print("path: \(context.url.path)")
print("components: \(context.url.pathComponents)")
}
}
}
from apple docs:
If your app has opted into Scenes, and your app is not running, the system delivers the URL to the scene(:willConnectTo:options:) delegate method after launch, and to scene(:openURLContexts:) when your app opens a URL while running or suspended in memory.
Full example:
In Scene delegate when app is terminated:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let url = connectionOptions.urlContexts.first?.url
}
and for when app is background or foreground:
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
let url = URLContexts.first?.url
}
I didn't find an answer so I decided to work around the issue. I reverted back to AppDelegate only, in this situation Deep links only worked while the app was active or in background. To fix this I decided to store the URL in UserDefaults. So in the didFinishLaunchingWithOptions function I added the following:
if let url = launchOptions?[UIApplication.LaunchOptionsKey.url] as? URL {
UserDefaults.setURLToContinue(urlString: url.absoluteString)
} else if let activityDictionary = launchOptions?[UIApplication.LaunchOptionsKey.userActivityDictionary] as? [AnyHashable: Any] {
for key in activityDictionary.keys {
if let userActivity = activityDictionary[key] as? NSUserActivity {
if let url = userActivity.webpageURL {
UserDefaults.setURLToContinue(urlString: url.absoluteString)
}
}
}
}
Here is the UserDefaults extension I created:
extension UserDefaults {
class func setURLToContinue(urlString: String){
UserDefaults.standard.set(urlString, forKey: "continueURL")
}
class func getURLToContinue() -> String? {
return UserDefaults.standard.string(forKey: "continueURL")
}
class func removeURLToContinue(){
UserDefaults.standard.removeObject(forKey: "continueURL")
}
}
Lastly in the initial view controller's viewDidLoad function I handle the link:
if let urlString = UserDefaults.standard.string(forKey: "continueURL") {
let url = URL(string: urlString)!
let navigator = Navigator()
navigator.getDesination(for: url)
UserDefaults.removeURLToContinue()
}
Where the Navigator class decides what view controller to push on the navigation stack
Everything worked perfectly after this
My widget has several links that the user can click, the link are set up as follows:
Link(destination: URL(string: "widget://start")!)
Now I am able to detect the press in the scene delegate with the following function:
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
if let item = URLContexts.first {
UserDefaults.standard.set(item.url.absoluteString, forKey: "URL")
print(item.url)
print(URLContexts)
}
}
However, that doesn't work when the app is closed. I tried putting this block of code everywhere, scene delegate, app delegate, but I just can't find a solution on how to detect the tap when the app is closed.
Is there a way to do that?
Add this
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
if let item = connectionOptions.urlContexts.first {
UserDefaults.standard.set(item.url.absoluteString, forKey: "URL")
print(item.url)
print(URLContexts)
}
}
}
For those, who use only AppDelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if let userActDic = launchOptions?[UIApplication.LaunchOptionsKey.userActivityDictionary] as? [String: Any],
let userActivity = userActDic["UIApplicationLaunchOptionsUserActivityKey"] as? NSUserActivity {
// Do with user activity
}
}
One solution is to wait a little before load url
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
...Your code
// Load the link, but set a timeout of X seconds to fix app crashing when loading deep link while app is NOT already running in the background.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
self.handleUniversalLink(url: url)
}
}
Any idea why starting from iOS 13 app links (universal links) this defines via Apple-App-Site-Association stopped working?
I have 2 implementations in ApplicationDelegate and in SceneDelegate.
Now works only implementation in SceneDelegate and only if application is in background, if I kill app then method continueUserActivity isn't called. I have added Haptic Feedback to track this method call but it will never be invoked neither in ActivityDelegate or SceneDelegate.
// MARK: - Universal Links support
extension SceneDelegate {
func scene(_ scene: UIScene, willContinueUserActivityWithType userActivityType: String) {
print("[Scene] Will continue user activity: ", userActivityType)
let generator = UINotificationFeedbackGenerator()
generator.notificationOccurred(.success)
}
func scene(_ scene: UIScene, didFailToContinueUserActivityWithType userActivityType: String, error: Error) {
print("[Scene] Did fail to continue user activity: ", userActivityType)
}
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
print("[Scene] Application continue user activity...")
if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
if let url = userActivity.webpageURL {
if !present(url: url) { UIApplication.shared.open(url, options: [:]) }
}
}
}
And Application Delegate case
// MARK: - Universal Links support
extension AppDelegate {
func application(_ application: UIApplication, willContinueUserActivityWithType userActivityType: String) -> Bool {
print("[App] Will continue user activity: ", userActivityType)
let generator = UINotificationFeedbackGenerator()
generator.notificationOccurred(.warning)
return true
}
func application(_ application: UIApplication, didFailToContinueUserActivityWithType userActivityType: String, error: Error) {
print("[App] Did fail to continue user activity: ", userActivityType)
}
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
print("[App] Application continue user activity...")
if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
if let url = userActivity.webpageURL {
if !present(url: url) { UIApplication.shared.open(url, options: [:]) }
}
}
return true
}
App is being opened but the methods are not called and I cannot navigate to appropriate screen inside my app.
Ok I've found it you must do something like this
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
if let userActivity = connectionOptions.userActivities.first {
self.scene(scene, continue: userActivity)
}
}
For me, only solution is to delete SceneDelegate and put code inside AppDelegate and it works, whenever the app is killed or in background.
If you don use SwiftUI you can delete SceneDelegate
For the SwiftUI 2.0 users, who don't have App or SceneDelegate. My example is about Firebase universal link, which should do something once it is opened. There is a very good method called onOpenURL which you can use.
var body: some Scene {
WindowGroup {
ContentView(shouldPresentThankYouView: $shouldPresentThankYouView).onOpenURL { url in
_ = DynamicLinks.dynamicLinks().handleUniversalLink(url) { (dynamicLink, error) in
guard error == nil else{
print("Found an error! \(error!.localizedDescription)")
return
}
if let dynamicLink = dynamicLink {
shouldPresentThankYouView = true
self.handleIncomingDynamicLink(dynamicLink)
}
}
}
}
}
If the user has the app installed, once he click on the app, a Thank You View will appear. Hope it helps you when working it universal links more specifically with Firebase links.
I am trying to handle open apps from universal link click. below ios 13 its working good but for ios 13 its working only app running in background. If app not working foreground or background, clicking link opens app not called continue userActivity function. I also tried to get it in scene delegate willconnnect to delegate. But still not calling
My code is below what is wrong?
scene delegate
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
if connectionOptions.userActivities.first != nil {
self.scene(scene, continue: userActivity!)
}
}
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
continueUserActivity(userActivity: userActivity)
}
func continueUserActivity(userActivity : NSUserActivity){
if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
let url = userActivity.webpageURL!
let dataDict:[String: String] = [AppLinkManager.appLinkExtraKey: url.absoluteString]
NotificationCenter.default.post(name: .didReceiveAppLink, object: nil, userInfo: dataDict)
}
}
app delegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if let userActivityDict = launchOptions?[.userActivityDictionary] as? [AnyHashable : Any],
let userActivity = userActivityDict["UIApplicationLaunchOptionsUserActivityKey"] as? NSUserActivity {
continueUserActivity(userActivity: userActivity)
}
return true
}
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
continueUserActivity(userActivity: userActivity)
return true
}
func continueUserActivity(userActivity : NSUserActivity){
if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
let url = userActivity.webpageURL!
let dataDict:[String: String] = [AppLinkManager.appLinkExtraKey: url.absoluteString]
NotificationCenter.default.post(name: .didReceiveAppLink, object: nil, userInfo: dataDict)
}
}
Have you tried implementing continue userActivity function in SceneDelegate:
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { }
I faced the same issue when using Firebase DynamicLink and looks like UniversalLinks (and probably several other APIs) use this callback on the SceneDelegate.
So if you're targeting iOS 13 and below try implementing both for SceneDelegate and AppDelegate
im implementing deep link is iOS. I have configured the URL Scheme in Project-Setting->Info->Url type
URL Schemes : carwash role:Viewer
when I type carwash://something the browser asks for opening the application but nothing get called in Application that I handle that what action should occur .
apple documentation says you should override application(open url) in AppDelegate but deep link dosent call it and the application opens in last state
application:openURL:options:' is not being called
this is my code and dose not work
func application(_ app: UIApplication, open url: URL,
options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
fatalError()
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if let url = launchOptions?[UIApplication.LaunchOptionsKey.url] as? URL {
/// some
fatalError()
}
GMSServices.provideAPIKey("")
return true
}
In iOS 13, UIApplication application(_:open:options:) doesn't get called (at least in my case maybe).
You should override the SceneDelegate functions below instead:
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
if let url = URLContexts.first?.url{
print(url)
}
}
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// detect the URLContext in the `options:` and deal with it.
}
If the user taps the link when your app isn't running, scene(_: openURLContexts:) won't be called but scene(_:willConnectTo:options:) will be.
in iOS 13+ or scene delegate there two methods will call and willPerformHTTPRedirection method will definitely call.
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
if userActivity.activityType == NSUserActivityTypeBrowsingWeb, let url = userActivity.webpageURL {
}
}
}
func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: #escaping (URLRequest?) -> Swift.Void) {
if let url = request.url {
guard let detailDictionary = String.queryParameters(from: url) else { return }
}
}
}
Parse url by this function:
static func queryParameters(from url: URL) -> [String: String]? {
let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false)
var queryParams: [String : String]? = nil
if let components = urlComponents?.queryItems {
queryParams = [String: String]()
for queryItem in components {
if queryItem.value == nil {
continue
}
queryParams?[queryItem.name] = queryItem.value
}
}
return queryParams
}
You should use application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool
Note that you have no return statement in your method
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
print("Got from URL: \(url)"
return true
}
Links in simulator best to be done through terminal
xcrun simctl openurl booted “carwasher://deeplink”
For me in iOS13+ the following works (in my SceneDelegate):
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Set up everything you need
// ...
// Handle deep link on cold start
if let url = connectionOptions.userActivities.first?.webpageURL {
handle(url: url)
}
}
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
if let url = userActivity.webpageURL {
// Handle deep link when the app is running
handle(url: url)
}
}
You should configure your deeplinks in Info.plist file
Example:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLName</key>
<string>carwash</string> // your identifier
<key>CFBundleURLSchemes</key>
<array>
<string>carwash</string> // schema
</array>
</dict>
</array>