1

It seems that since iOS 9 if a user has already been redirected to an app from a website, then future links to that website automatically open the app.

How can I detect inside the iOS app what the link was that the user clicked so that I can serve the correct content?

In my case, a user has already installed the app, does a forgot password, and clicks the link in the email. On a desktop it works fine, but on iOS it just takes them to the app and my app does not know the context.

It is a Swift app that is mostly a webview serving the mobile version of our web app.

UPDATE:

Here are some details about the situation:

  • The iOS app has a URL Scheme defined (ourUniqueOrganizationName)
  • When a user logs in, I use js to check if they are on iOS and if so, redirect them to a page that provides a link to download the app (which is served by our own web server, not via the app store, via a link like itms-services://?action=download-manifest&url=https://www.etcetcetc.org/apps/name-of-the-app/manifest.plist
  • Once they click the download button, iOS asks the user if they want to download the app, and if they say yes, it does so in the background and they are left sitting on the same webpage.
  • On that page, I display instructions on how to "Trust" our organization in the settings of their phone (it is an Enterprise app, so this annoying step is required)
  • The last thing on the page is a link to "Open the App", which points to ourUniqueOrganizationName://go

Not sure if this is relevant, but I actually have a second URL scheme defined, app, which is used to by the js of the website (when the site is loaded in a webview) to send data to the app like:

window.location = "app://do/some/thing"

which the app can catch via func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {}

To be clear, inside that shouldStartLoadWith I am only testing that request.url?.scheme equals "app". This function does not get involved with the other URL scheme, ourUniqueOrganizationName, as I understand that just having that defined in the info.plist is enough for websites to open the app using that as the scheme.

UPDATE # 2:

I finally realized that I did, in fact, have Universal Links on so the behavior I was seeing should have been expected!

Jason Galuten
  • 1,042
  • 2
  • 11
  • 20
  • this might help: https://medium.com/@abhimuralidharan/universal-links-in-ios-79c4ee038272 – JDM Apr 12 '18 at 18:49
  • What code do you have for this specific scenario? – staticVoidMan Apr 12 '18 at 18:55
  • @JDM thanks, I've seen that link. I'm not wanting to link them to the app using a Universal Link. I want to know what link they clicked when iOS automatically redirects them to the app. – Jason Galuten Apr 12 '18 at 19:20
  • @staticVoidMan I don't have any code to address this issue yet. When someone is redirected to my app, my app is not aware that they were sent to it when clicking a regular URL. – Jason Galuten Apr 12 '18 at 19:22
  • My preference would be for them NOT to be redirected to the app in this scenario, but I don't think I have control of that so the next best thing is to handle it within the app. – Jason Galuten Apr 12 '18 at 19:24
  • @JasonGaluten Well, universal link is not the solution. Infact it's the [URL Scheme](https://stackoverflow.com/a/25883274/2857130) concept in work here. Check my answer and let me know if you need more help :) – staticVoidMan Apr 12 '18 at 19:44

2 Answers2

1

It looks like you your site is supporting Universal Links, in this case your AppDelegate should implement a method:

func application(_ application: UIApplication, 
             continue userActivity: NSUserActivity, 
   restorationHandler: @escaping ([Any]?) -> Void) -> Bool

When iOS launches your app after a user taps a universal link, you receive an NSUserActivity object with an activityType value of NSUserActivityTypeBrowsingWeb. The activity object’s webpageURL property contains the URL that the user is accessing. The webpage URL property always contains an HTTP or HTTPS URL. Source: https://medium.com/@abhimuralidharan/universal-links-in-ios-79c4ee038272

In case of URLScheme, which is less probable, you should implement in AppDelegate method:

iOS < 9

func application(_ application: UIApplication, handleOpen url: URL) -> Bool

iOS >= 9

func application(_ app: UIApplication, 
                     open url: URL, 
                  options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool

For more details check Documentation

  • This answer was correct all along but since I did not realize that my app had universal links, it took a lot of time to figure that out and I wanted to give credit to @staticVoidMan for all of the legwork and for helping me improve the question – Jason Galuten Apr 13 '18 at 22:25
1

If the link is something like:

myAppURLScheme://loremIpsumDolor

Then your app has defined a URL Scheme because of which iOS is able to open your app when the user taps on such a link.

To handle the url, you need to do 2 things in AppDelegate:

  1. Implement the application(_:open:options:) delegate
    • If your app is already running, whether in background or foreground, you will get the URL as a parameter here
  2. Also handle the case in application(_:didFinishLaunchingWithOptions:)
    • This is incase you app is not running i.e not opened or was terminated then you will get the url string in launchOptions parameter here

Solution (Swift 4):

//Extra work to handle url when app was not already running and was launched due to the user tapping on the link
func application(_ application: UIApplication, 
                 didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    //...        
    let strURL = launchOptions?[UIApplicationLaunchOptionsKey.url] as? String
    handleURL(strURL)
    //...
}

//Delegate to handle url when app is already running (background/foregroung)
func application(_ app: UIApplication, 
                 open url: URL,
                 options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
    handleURL(url.absoluteString)
    return true
}

//Our common helper function
func handleURL(_ string: String?) {
    guard let string = string else { return }

    print(string)
    //do your case handling now
}

EDIT:

If you have Universal Links enabled for your app then you only need the application(_:continue:restorationHandler:) delegate method in your AppDelegate.
You get an NSUserActivity object here that will have all sorts of information including a webpageURL which will be the url that caused your app to open.

Example:

func application(_ application: UIApplication,
                 continue userActivity: NSUserActivity,
                 restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
    print(userActivity.webpageURL?.absoluteString)
    return true
}
staticVoidMan
  • 19,275
  • 6
  • 69
  • 98
  • Awesome, thanks. This definitely seems like what I'm looking for. I'll try it as soon as I can! – Jason Galuten Apr 12 '18 at 19:47
  • @JasonGaluten Glad to help :) If it works for you then don't forget to upvote/mark as accepted. – staticVoidMan Apr 12 '18 at 19:48
  • I will! Right now I'm just dealing with upgrading XCode because it's been a while since I've needed to update the app! – Jason Galuten Apr 12 '18 at 19:50
  • Ok, I have tried what you suggested but when I test it and click the link in the email, and it opens the app, `handleURL` is never called. – Jason Galuten Apr 13 '18 at 07:02
  • @JasonGaluten Then probably your app does not have a `URL Schemes` defined and iOS by default is opening your app. So to help you further, what is the link that causes your your app to open? If the link has sensitive information then share a sanitized version of the link. – staticVoidMan Apr 13 '18 at 07:29
  • @JasonGaluten Something totally different... hm... will get back to you on this. – staticVoidMan Apr 13 '18 at 07:35
  • @JasonGaluten Are you sure universal link is not active on your app? Just check and get back – staticVoidMan Apr 13 '18 at 08:43
  • @JasonGaluten Can you also walk me through how your app ends up in this state without URL Scheme and Universal linking. An example project will also help. – staticVoidMan Apr 13 '18 at 09:39
  • I have updated my answer to include more detail about what is going on – Jason Galuten Apr 13 '18 at 16:35
  • @JasonGaluten Ok, so the thing is... if the link in your email was something like `ourUniqueOrganizationName://mywebsite.com/user_setup/blah-blah-uuid` then it's going to do the `URL Scheme` logic in which we eventually call our `handleURL(_:)` helper method. Since it's not being called, you probably have universal links active. – staticVoidMan Apr 13 '18 at 17:03
  • @JasonGaluten To verify universal links, go to the [`capabilities` section ](https://koenig-media.raywenderlich.com/uploads/2016/04/Turn_On_Capabilities-700x153.png) in your project and confirm that you have something in [`associated domains` ](https://koenig-media.raywenderlich.com/uploads/2016/04/Associated_Domains-700x254.png). Check [this link](https://www.raywenderlich.com/128948/universal-links-make-connection) for more reference. We'll take it ahead from here or you could check the other answer you got. – staticVoidMan Apr 13 '18 at 17:06
  • or also look at [How to Set Up Universal Links to Deep Link on Apple iOS](https://blog.branch.io/how-to-setup-universal-links-to-deep-link-on-apple-ios/). This is just to confirm that your app has enabled universal linking. – staticVoidMan Apr 13 '18 at 17:14
  • @JasonGaluten Anyways, updated answer for handling Universal Links. Let me know if your breakpoints hit now. – staticVoidMan Apr 13 '18 at 17:26
  • I'm away from the computer at the moment but just want to clarify that the URL in the email is not using the special scheme. It's an https request to the website and iOS is sending users to the app anyway. – Jason Galuten Apr 13 '18 at 21:21
  • well I'll be, I _do_ have Associated Domains on, and there's an entry that is applinks:[mywebsite] ... I must have done that and forgotten! – Jason Galuten Apr 13 '18 at 22:12