Open url from Firebase In-App-Messaging inside app - ios

I'm trying to override Firebase's default behavior for opening the link from the In-App-Messaging following this guide:
https://firebase.google.com/docs/in-app-messaging/modify-message-behavior?platform=ios
I set the delegate of InAppMessaging in AppDelegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
InAppMessaging.inAppMessaging().delegate = self
}
I add my logic in the Delegate method:
extension AppDelegate: InAppMessagingDisplayDelegate {
func messageClicked(_ inAppMessage: InAppMessagingDisplayMessage, with action: InAppMessagingAction) {
let topMostViewController = UIApplication.shared.topMostViewController()
//openUrl has logic for opening the url inside the app in a full screen webview FullScreenWKWebView, this works great
openUrl(action.actionURL, parent: topMostViewController)
}
When tapping the button in the InAppMessaging message the delegate method messageClicked is loaded perfectly and also the logic for opening the url inside the app works. BUT also the default logic from Firebase for opening the link continues so the url is opening inside the app and at the same time Firebase jumps out of the app and open the url in Safari.
Is there some way I can cancel the default logic from Firebase so that it only opens inside the app?

For HTTP links, you can intercept URL opening in the handoff:
// UIApplicationDelegate
func application(_ application: UIApplication, continue userActivity: NSUserActivity,
restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
// handle userActivity.webpageURL
// true, if handled
return true
}

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)
}

deeplinking not showing correct storyboard

I have an app where I want to perform some deeplinking in.
When the app is open in the background the app opens on the correct page like expected.
But when the app is closed and then opened from the link it goes to the launchStoryboard then to the main interface storyboard and then to the storyboard I want.
But in the main interface storyboard I'm calling the api and when this is finished my app goes back to the main interface storyboard but it should stay on the storyboard like it is when the app opens from the background
Any ideas on how to takle this issue?
I followed those urls to make this happen
https://www.raywenderlich.com/6080-universal-links-make-the-connection
https://medium.com/#abhimuralidharan/universal-links-in-ios-79c4ee038272
https://developer.apple.com/library/archive/documentation/General/Conceptual/AppSearch/UniversalLinks.html
I would check for different way you handle the url when handling it on
optional public func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool
vs
optional public func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool
optional public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool
About:
optional public func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool
From the documentation:
Summary
Asks the delegate to open a resource specified by a URL, and provides
a dictionary of launch options. Declaration
optional func application(_ app: UIApplication, open url: URL,
options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool
Discussion
This method is not called if your implementations return false from
both the application(:willFinishLaunchingWithOptions:) and
application(:didFinishLaunchingWithOptions:) methods. (If only one of
the two methods is implemented, its return value determines whether
this method is called.) If your app implements the
applicationDidFinishLaunching(:) method instead of
application(:didFinishLaunchingWithOptions:), this method is called
to open the specified URL after the app has been initialized. If a URL
arrives while your app is suspended or running in the background, the
system moves your app to the foreground prior to calling this method.
There is no equivalent notification for this delegation method.

How do I know if app was launched via Firebase-Deeplink (Dynamic Links) at AppDelegate's didFinishLaunchingWithOptions

We are adding Firebase-Deeplinks to our IOS-project, so that the app can be started via deeplink.
The deeplink-feature itself work fine so far, and so does the default app launch routine. But making both startRoutines work side by side gives me some headache.
What I am trying to achieve get's obvious looking at this code snippet.
func application(_:didFinishLaunchingWithOptions:) {
FirebaseApp.configure()
if "deeplink" {
return true
}
defaultAppLaunch() // no deeplink
return true
}
If there is a deeplink one of these appDelegate-functions is called:
func application(:continueUserActivity:restorationHandler:) {
handleDeeplink()
return true
}
func application(:openURL:options:) {
handleDeeplink()
return true
}
So how do I know at application(_:didFinishLaunchingWithOptions:) if I can call defaultAppLaunch()?
I know there is the launchOptions-Argument in but in my case it is always nil, at least when running the app via XCode. And also the Firebase-Documentation says nothing about launchOptions to be set by Firebase-Deeplinks.
Help is highly appreciated.
TL;DR
You can't know that your app was opened using deeplinks through App Delegate DidFinishLaunching.
Explaination:
App delegate did finish launch is always called, regardless if app was opened normally or via deeplinks. so you can't know through app delegate
Instead, you can know that app was opened through deeplinks if the following delegate function is called.
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
if let dynamicLink = DynamicLinks.dynamicLinks().dynamicLink(fromCustomSchemeURL: url) {
// Handle the deep link. For example, show the deep-linked content or
// apply a promotional offer to the user's account.
// ...
return true
}
return false
}
and you should handle the deeplinks functionality in the same function
I'm referencing the Firebase docs in handling dynamic links for iOS:
Firebase docs for receiving dynamic links
Next, in the application:continueUserActivity:restorationHandler:
method, handle links received as Universal Links when the app is
already installed (on iOS 9 and newer):
func application(_ application: UIApplication, continue userActivity: NSUserActivity,
restorationHandler: #escaping ([Any]?) -> Void) -> Bool {
let handled = DynamicLinks.dynamicLinks().handleUniversalLink(userActivity.webpageURL!) { (dynamiclink, error) in
// ...
}
return handled
}
Finally, in the application:openURL:sourceApplication:annotation: (iOS
8 and older) and application:openURL:options: (iOS 9 and up) methods,
handle links received through your app's custom URL scheme. These
methods are called when your app receives a link on iOS 8 and older,
and when your app is opened for the first time after installation on
any version of iOS.
#available(iOS 9.0, *)
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any]) -> Bool {
return application(app, open: url,
sourceApplication: options[UIApplicationOpenURLOptionsKey.sourceApplication] as? String,
annotation: "")
}
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
if let dynamicLink = DynamicLinks.dynamicLinks().dynamicLink(fromCustomSchemeURL: url) {
// Handle the deep link. For example, show the deep-linked content or
// apply a promotional offer to the user's account.
// ...
return true
}
return false
}
But you did mention that the app is currently only being run on Xcode (and I'm guessing iOS Simulator, maybe you can try it on a test device too!)

application:openURL:options: not called after opening universal link

I've set up universal links with the Branch SDK. The links are opening the app correctly, and application:continueUserActivity:restorationHandler: is called, but not `application:openURL:options:'
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
Branch.getInstance().application(app, open: url, options: options)
return true
}
The deprecated application:openURL:sourceApplications:annotation is also not called. didFinishLaunchingWithOptions and willFinishLaunchingWithOptions both return true.
What could possibly causing openURL to not be called when the app opens from tapping a universal link?
Clay from Branch here.
The application:openURL:sourceApplications:annotation function (now deprecate to application(_:open:options:)) is actually only called in response to the old Apple Linking system of standard URI schemes.
Universal links are actually handled within the application(_:continue:restorationHandler:) function.
// Respond to URI scheme links
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
// pass the url to the handle deep link call
Branch.getInstance().application(app, open: url, options: options)
// do other deep link routing for the Facebook SDK, Pinterest SDK, etc
return true
}
// Respond to Universal Links
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Void) -> Bool {
// pass the url to the handle deep link call
Branch.getInstance().continue(userActivity)
return true
}
Your deep link handling should mostly be dealt with in your handler callback:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let branch: Branch = Branch.getInstance()
branch?.initSession(launchOptions: launchOptions, deepLinkHandler: { params, error in
if error == nil {
// params are the deep linked params associated with the link that the user clicked -> was re-directed to this app
print("params: %#", params.description)
}
})
return true
}

Swift project crashing with Thread 1: EXC_BAD_ACCESS (code = 1, address = 0x0)

It looks like a bad memory access, like trying to access an object that does not exist. I tried using NSZombie to see if something came up, as far as I could tell nothing did. It is crashing at the declaration for the app delegate.
AppDelegate.swift
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate
{
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]) -> Bool
{
// Override point for customization after app launches
Parse.setApplicationId("removed on purpose", clientKey: "removed on purpose")
PFAnalytics.trackAppOpenedWithLaunchOptions(launchOptions)
PFFacebookUtils.initializeFacebook()
return true
}
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String, annotation: AnyObject?) -> Bool
{
return FBAppCall.handleOpenURL(url, sourceApplication: sourceApplication, withSession: PFFacebookUtils.session())
}
func applicationDidBecomeActive(application: UIApplication)
{
FBAppCall.handleDidBecomeActiveWithSession(PFFacebookUtils.session())
}
func applicationWillResignActive(application: UIApplication)
{
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
func applicationDidEnterBackground(application: UIApplication)
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(application: UIApplication)
{
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationWillTerminate(application: UIApplication)
{
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}
DashboardViewController.swift
import UIKit
class DashboardViewController: UIViewController
{
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view
}
}
Using breakpoints I have determined that it is not even getting past the class declaration for the app delegate. I tried checking all of the classes in my Main.storyboard file as well to make sure everything was linked properly, again as far as I can tell it is.
I ran into the same issue today. As of Xcode 6 beta 6 the auto complete suggests:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]) -> Bool {}
This crashes at startup with an EXC_BAD_ACCESS and a blank screen.
As soon as an ! is added to the last argument, everything works fine:
func application(application: UIApplication,didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]!) -> Bool {}
In current documentation the ! is missing as well:
optional func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]) -> Bool
Works with Xcode 6.1:
Try
PFAnalytics.trackAppOpenedWithLaunchOptionsInBackground(launchOptions, block: nil)
instead of
PFAnalytics.trackAppOpenedWithLaunchOptions()
Solution from OP.
Problem solved by fixing the code as below.
In all method signatures, replace:
application: UIApplication
with:
application: UIApplication!
And in application:didFinishLaunchingWithOptions:, replace:
launchOptions: [NSObject : AnyObject]
with:
launchOptions: NSDictionary!

Resources