As per my App project setup,
I have following function calls with same code to instantiate rootVCs in SceneDelegate and AppDelegate respectively
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
}
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]?)
-> Bool {
}
In order to Implement Universal Links, I have the following callback function in my App Delegate
func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
//code to capture and setup universal link
}
This function from AppDelegate is only called in less than iOS 13 devices.I looked for similar callback equivalent for SceneDelegate, The closest I could find was this function.
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
//code to capture and setup universal link
}
Configuration: Xcode Version 11.5 target iOS 10+ devices.
Problem: This particular callback is only called when there is an instance of the app running before Link is clicked. i.e. Once the App instance is killed, this function from SceneDelegate is not called and universal links Do not work for iOS13+ Devices.
I tried following this Xcode 11 - Opt out of UISceneDelegate/SwiftUI on iOS 13 to remove the Scene Delegate altogether, However ended up with only Black Screen.
Question: What am I doing wrong and what is the possible fix?
I had the same problem, the problem is that you have a SceneDelegate, because of this the AppDelegate methods are not called. So you are missing one method in your SceneDelegate, the first one you left empty will handle Universal links when the app has not been launched yet.
Implement the following methods in your SceneDelegate to handle Universal Links when the app is already running and when it is not launched yet:
//This method is called when app is NOT running in the background.
//Easy to test with fatalError()
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
if let userActivity = connectionOptions.userActivities.first {
debugPrint("userActivity: \(userActivity.webpageURL)")
fatalError()
}
}
//This method is called when app is running in the background.
//Easy to test with debugPrint
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
debugPrint("userActivity: \(userActivity.webpageURL)")
}
From there, do whatever you need to handle the links.
Hope it helps :)
Related
I've implemented Universal Links in my app and it opens when specific links are clicked in emails, messages etc.
Now I'm trying to handle this events but with no success at this point.
Any ideas what am I doing wrong?
Here's a part of what I've tried so far in my AppDelegate:
import Firebase
import FirebaseMessaging
import UserNotificationsUI
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate, NSUserActivityDelegate {
// ... register for notifications
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool
{
print("Test 1") // Doesn't print anything
return true
}
func application(application: UIApplication, continueUserActivity userActivity: NSUserActivity, restorationHandler: ([AnyObject]?) -> Void) -> Bool
{
print("Test 2") // Doesn't print anything
return true
}
}
extension AppDelegate: UNUserNotificationCenterDelegate {
// ... handle notifications
}
P.S. I'm using Swift 5 on xCode 14
I had the same problem, the problem is that you have a SceneDelegate, because of this the AppDelegate methods are not called.
Implement the following methods in your SceneDelegate to handle Universal Links when the app is already running and when it is not launched yet:
//This method is called when app is NOT running in the background.
//Easy to test with fatalError()
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
if let userActivity = connectionOptions.userActivities.first {
debugPrint("userActivity: \(userActivity.webpageURL)")
fatalError()
}
}
//This method is called when app is running in the background.
//Easy to test with debugPrint
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
debugPrint("userActivity: \(userActivity.webpageURL)")
}
From there, do whatever you need to handle the links.
Hope it helps :)
If I never actually leave the current app for another app - and the screen turns off or the device gets locked.
Will lifecycle methods like applicationDidEnterBackground, applicationWillEnterForeground, etc still get called?
Basically my question is: does screen lock/device sleep behave the same way as switching to another app in terms of lifecycle methods.
Yes, except that in a modern app all of that stuff has been moved to the scene delegate. If the screen is locked and you are frontmost, you will get:
sceneWillResignActive(_:)
sceneDidEnterBackground(_:)
And then when the screen is unlocked you will get:
sceneWillEnterForeground(_:)
sceneDidBecomeActive(_:)
FWIW, if you want to diagnose these sorts of app lifecycle events yourself in the future, you can use Logger or os_log and watch these iOS events on your macOS Console app.
Note, I am not relying on the Xcode debugger, because having an app attached to the debugger alters the app lifecycle. I am explicitly not running the app through the Xcode debugger, but rather running it directly on the device having previously quit Xcode on my Mac.
In short, yes, simply turning off a phone (but not powering it down completely) puts your app into background mode. Here is the log where I launched the app, and then tapped on the power button on the side of my iPhone to turn it off:
This behavior is very similar to the following log where I just swiped up to return to the home screen:
But if you completely power down the device while the app is running, you will see it sceneWillResignActive, but unsurprisingly, it never gets to sceneDidEnterBackground:
Nor if you force-quit while the app is active:
For more information about logging an app and observing these from the macOS Console, see WWDC 2020’s Explore logging in Swift or WWDC 2016 Unified Logging and Activity Tracing for these logging options.
FWIW, here is my logging:
// AppDelegate.swift
import UIKit
import os
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "AppDelegate")
#main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
logger.debug(#function)
...
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
logger.debug(#function)
...
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
logger.debug(#function)
...
}
}
And:
// SceneDelegate.swift
import UIKit
import os
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "SceneDelegate")
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
logger.debug(#function)
...
guard let _ = (scene as? UIWindowScene) else { return }
}
func sceneDidDisconnect(_ scene: UIScene) {
logger.debug(#function)
...
}
func sceneDidBecomeActive(_ scene: UIScene) {
logger.debug(#function)
...
}
func sceneWillResignActive(_ scene: UIScene) {
logger.debug(#function)
...
}
func sceneWillEnterForeground(_ scene: UIScene) {
logger.debug(#function)
...
}
func sceneDidEnterBackground(_ scene: UIScene) {
logger.debug(#function)
...
}
}
If you are using os_log instead of Logger (the latter introduced in macOS 11 and iOS 14), the process is very similar. You will see os_log messages in the console in the same manner.
When I tap on the result of the Core Spotlight search, the app launches, but the function in AppDelegate.swift doesn't get called:
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
print("called")
}
which contains a logic for navigating to a specific view controller and loading data.
Some people here seem to recommend using SceneDelegate.swift:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
print("called"
}
but doesn't get called from Core Spotlight, either.
The only functions that seem to be being called are:
func sceneDidBecomeActive(_ scene: UIScene) {}
func sceneWillEnterForeground(_ scene: UIScene) {}
in SceneDelegate.swift, but doesn't have the needed userActivity parameter.
I have implemented custom URL schema in my project and working fine till iOS12.But not in iOS13.
For iOS13 I have added class SceneDelegate.swift and implemented below methods:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// added navigation functionality for iOS13
}
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
print("url:\(URLContexts.first?.url)")
}
for below ios13:
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
}
Please help me to solve this issue.
Thank you.
Have you checked the LSApplicationQueriesSchemes in your Info.plist? Your custom URL schema must be mentioned there.
Maybe you can share with us your test project with this issue so we can run it?
performActionFor shortcutItem gets called in my SceneDelegate if the app has already launched, but it doesn't get called if the app actually launches from a shortcut item. Why is this?
You can get the ShortcutItems from willConnectTo function in sceneDelegate when the app is launched from shortcut item (and when there is no instance of app is in background)
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
if let shortcutItems = connectionOptions.shortcutItem{
}
}