I have a SceneDelegate class which is part of Target 1 code. Target 1 is compiled as a static library.
public class SceneDelegate: UIResponder, UIWindowSceneDelegate {
public var window: UIWindow?
// SceneDidBecomeActive
// other lifecycle events
}
The SceneDelegate is programmatically assigned in the 'configurationForConnecting' of the AppDelegate in Target 1. i.e.
config = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
// Set the scene delegate
config.delegateClass = SceneDelegate.self
Now, I do 'extension SceneDelegate' in Target 2. In this extension, I have put the 'willConnectTo' function definition. Target 2 is also compiled as a static library.
extension SceneDelegate {
public func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// ...
}
// Also has sceneDidDisconnect
}
The App Target has Target 1 and Target 2 added under 'Link Binary with Libraries'.
Now when the application runs, willConnectTo is never invoked, which was in Target 2. The other code in Target 2 is invoked properly. However the other lifecycle states like SceneDidBecomeActive, sceneDidResignActive etc from Target 1 gets invoked.
Is there any limitation as such when using extensions in this manner ?
Related
This question already has answers here:
The app delegate must implement the window property if it wants to use a main storyboard file swift
(12 answers)
Closed 2 years ago.
I am creating a new project using latest Xcode (12.1). the default deployment target when I create a new project was 14.1. then I change it to be 11.0. because I want to target users from iOS 11.0 and above
then it will create a lot of warnings like this in my AppDelegate and SceneDelegate
I fix all of them so my AppDelegate and SceneDelegate will be like the code below.
the problem is.....
when I run it on my real device iOS 14.1, it will have no issue. but when I run it on my iOS 12.4.8 , my app will completely dark
I have also already turn off the dark mode in info.plist using this key
<key>Appearance</key>
<string>Light</string>
how to solve this ?
completely dark like this iOS 12.4.8 .the background colour should be custom orange
SceneDelagate.swift
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
#available(iOS 13.0, *)
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
guard let _ = (scene as? UIWindowScene) else { return }
}
#available(iOS 13.0, *)
func sceneDidDisconnect(_ scene: UIScene) {
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
}
#available(iOS 13.0, *)
func sceneDidBecomeActive(_ scene: UIScene) {
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}
#available(iOS 13.0, *)
func sceneWillResignActive(_ scene: UIScene) {
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}
#available(iOS 13.0, *)
func sceneWillEnterForeground(_ scene: UIScene) {
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}
#available(iOS 13.0, *)
func sceneDidEnterBackground(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
}
}
AppDelegate.swift
#main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
// MARK: UISceneSession Lifecycle
#available(iOS 13.0, *)
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
#available(iOS 13.0, *)
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
I have an answer from here
you have to add the following property declaration in your AppDelegate class
var window: UIWindow?
INTRODUCTION
I'm still new use Swift, for now, I have a project using Swift, not develop form start just customize the App.
PROBLEM
Now I want to create Deep Link with Parameter. So, I try to follow a few tutorials from the internet e.g: [https://medium.com/wolox/ios-deep-linking-url-scheme-vs-universal-links-50abd3802f97] and [https://www.swiftdevcenter.com/custom-url-scheme-deep-link-ios-13-and-later-swift-5/] but not work.
TRY TO DO
I want to if App detects Deep Link have a parameter, App will go to another page and show the parameter.
Add this code in scene delegate
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
guard let _ = (scene as? UIWindowScene) else { return }
}
func sceneDidDisconnect(_ scene: UIScene) {
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
}
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
if let url = URLContexts.first?.url{
print(url)
let urlStr = url.absoluteString //1
// Parse the custom URL as per your uses, this will change as per requirement
let component = urlStr.components(separatedBy: "=") // 2
if component.count > 1, let productId = component.last { // 3
self.pushToProductDetailSceen(productId: productId) // 4
}
}
}
func pushToProductDetailSceen(productId: String)
{
}
func sceneDidBecomeActive(_ scene: UIScene) {
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}
func sceneWillResignActive(_ scene: UIScene) {
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}
func sceneWillEnterForeground(_ scene[![enter image description here][1]][1]: UIScene) {
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}
func sceneDidEnterBackground(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
}
}
just put this line in safari and it will works.
testapp://productId=97
I have an old Xcode project without UISceneDelegate methods. Is it possible to migrate an old Xcode project to a new one with UISceneDelegate methods BUT still maintaining compatibility with iOS 12?
If so, how? Because I see a lot of bugs in iOS 14 for which the only workaround is using UISceneDelegate methods.
EDIT 1:
Make sure you query for windowOrientation, after View Controller's view is rendered. Typically in viewDidLoad() and viewWillAppear(_:), view.window is nil, check this answer. Just check value for windowOrientation in viewDidAppear(_:).
If you have some issues to access this value even before try the following definition
private var windowOrientation: UIInterfaceOrientation {
if #available(iOS 13.0, *) {
return UIApplication.shared.windows.first?.windowScene?.interfaceOrientation ?? .unknown
} else {
// Fallback on earlier versions
return UIApplication.shared.statusBarOrientation
}
}
I am not sure if your app uses multiple windows or not but if there is only one(since you are not creating any window programatically), the following definition should work fine.
var hasTopNotch: Bool {
return UIApplication.shared.windows.first?.safeAreaInsets.top ?? 0 > 20
}
Original Answer:
UISceneDelegate has been introduced in iOS 13.0 so no way to be compatible with iOS 12, you need to depend on UIApplicationDelegate totally. To support UISceneDelegate in for iOS 13.x, you need to add explicit availability checking to avoid compilation error.
Steps 1: Add Scene Manifest in Info.plist
Open Info.plist as Source Code and add the following
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
Step 2: Create SceneDelegate.swift file with the following content
import UIKit
#available(iOS 13.0, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
guard let _ = (scene as? UIWindowScene) else { return }
}
func sceneDidDisconnect(_ scene: UIScene) {
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
}
func sceneDidBecomeActive(_ scene: UIScene) {
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}
func sceneWillResignActive(_ scene: UIScene) {
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}
func sceneWillEnterForeground(_ scene: UIScene) {
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}
func sceneDidEnterBackground(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
}
}
Step 3: Update AppDelegate
Add UISceneSession Lifecycle methods.
// MARK: UISceneSession Lifecycle
#available(iOS 13.0, *)
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
#available(iOS 13.0, *)
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
ii) Finally refer to Apple Documentation. You may refer to https://dev.to/kevinmaarek/add-a-scene-delegate-to-your-current-project-5on for additional clean up and setup tasks.
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 :)
If I compile onto an iOS 12 device (doesn't use UIScene) and AirPlay Mirror to my Apple TV the app is mirrored as expected to the TV.
On an iOS 13 device, it seems to treat it as an external display where it's formatted to fit the screen (but I have no way to control it).
I'd prefer the old functionality of just mirroring it.
How do I accomplish mirroring on iOS 13? I'm digging around in the docs for:
application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration
And in the UISceneConfiguration there's a role property (it has UISceneSession.Role.windowExternalDisplay when I try to AirPlay Mirror) but it doesn't seem to have any value like UISceneSession.Role.windowMirror.
I've been playing around with mirroring and external displays and various possibilities exist with just the right combination of code/settings but certain functionality doesn't seem possible.
Under iOS 13 (with an app built with a Base SDK of iOS 13), you can get your app to be mirrored on an external display. But making this work prevents your app from showing different content on an external display. Basically your app only mirrors or it only shows a unique scene for an external display.
If you wish to only have your app be mirrored, then ensure the following:
Remove the application(_:configurationForConnecting:options:) from your App Delegate.
In the Info.plist, make sure there is no entry for the "External Display Session Role" under the "Scene Configuration" section of the "Application Scene Manifest".
If neither of those two things are part of your app then your app will simple mirror to any external screen when you activate Screen Mirroring on the iOS device.
Just ran into this issue myself. My solution actually came from within my UIWindowSceneDelegate class.
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
// External displays should not get assigned a window. When a window isn't assigned, the default behavior is mirroring.
guard session.role != .windowExternalDisplay else { return }
/* the rest of your setup */
}
When you don't assign a window, it seems that mirroring becomes the default option. Before that change, my external displays (screen mirroring) were given their own unique UIWindow instance.
I don't see this documented anywhere, and it is not intuitive. Because of this, I'm somewhat fearful that it will break in the future.
Hope it still helps.
I found that with Objective-C implementation, you can achieve the screen mirroring behavior by returning nil in application:configurationForConnectingSceneSession:options:.
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {
if (connectingSceneSession.role == UIWindowSceneSessionRoleExternalDisplay) {
return nil;
}
UISceneConfiguration *configuration = [[UISceneConfiguration alloc] initWithName:#"Main" sessionRole:connectingSceneSession.role];
configuration.storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
configuration.delegateClass = [SceneDelegate class];
configuration.sceneClass = [UIWindowScene class];
return configuration;
}
Be aware that this is not a documented way and may break in the future.
Edited:
In Swift, you can achieve this via method swizzling:
#UIApplicationMain
class AppDelegate : UIResponder, UIApplicationDelegate {
override init() {
_ = AppDelegate.performSceneConfigurationSwizzle
super.init()
}
private static let performSceneConfigurationSwizzle: Void = {
method_exchangeImplementations(
class_getInstanceMethod(AppDelegate.self, #selector(AppDelegate.application(_:configurationForConnecting:options:)))!,
class_getInstanceMethod(AppDelegate.self, #selector(AppDelegate.swizzle_application(_:configurationForConnecting:options:)))!
)
}()
#objc func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
fatalError("Should never reach.")
}
#objc private func swizzle_application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration? {
if connectingSceneSession.role == .windowExternalDisplay {
return nil
}
// build scene configuration as usual…
}
}
Instead of implementing the AppDelegate scene configuration method in iOS 13:
#available(iOS 13.0, *)
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
let configuration = UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
configuration.delegateClass = SceneDelegate.self
return configuration
}
I instead switched to using the Info.plist variant (and removed the above code) where you effectively specify all the above in your Info.plist instead. (For an up to date version of what's expected in the Info.plist file, simply create a New Project in Xcode and copy the contents from the new Info.plist file for the Application Scene Manifest key).
It now works perfectly and AirPlay Mirror mirrors as expected. I did try changing the role to windowApplication as iOS seemingly does with the Info.plist variant but it still doesn't work.