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.
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 a problem with one of my apps. I noticed, that my Controller is still updating while the app is in the background. The reason for this is that the methods of the AppDelegate are not being called and I can not find out why.
Here is my code:
import UIKit
import Harmony
#UIApplicationMain
class AppDelegate : UIResponder
{
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
{
return true
}
}
extension AppDelegate : UIApplicationDelegate
{
func applicationDidBecomeActive(_ application: UIApplication)
{
Swift.print("applicationDidBecomeActive")
Notifications.post(UIApplication.didBecomeActiveNotification.rawValue)
}
func applicationWillResignActive(_ application: UIApplication)
{
Swift.print("applicationWillResignActive")
Notifications.post(UIApplication.willResignActiveNotification.rawValue)
}
func applicationWillEnterForeground(_ application: UIApplication)
{
Swift.print("applicationWillEnterForeground")
Notifications.post(UIApplication.willEnterForegroundNotification.rawValue)
}
}
I tried the following:
Use real device and simulator.
Restart Xcode, clean build folder.
Clean Developer folder
Do not use the extension.
Debug or Release scheme
Nothing had an effect, the methods are still not being called and I am wondering why?
If you are using ios 13 with scene delegate .. all functions are shifted in scene delegate instead of AppDelegate
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.
}
}
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.
I implemented branch.io links and it is working fine for WhatsApp and from Notes, but not Facebook Messenger or any other place with in-app browser.
As per documentation:
To work around this limitation, your links must have deepviews or something similar enabled, with a call-to-action link/button that has a Universal Link behind it. This way, clicking a link from the app feed will open a webview containing your deepview page, and the user can then click the link/button to launch your app.
For example Facebook Messenger should open in-app browser with deepview of my link, and if user tap button - my app should be opened, it is fine for me, but doesn't work like this.
In my case facebook is opening in-app browser with my deepview and automatically (I didn't tap button) redirects me to... AppStore :(. But if I open my app manually, I'm getting callback and app is redirecting me to proper place.
It is recommended to use forced redirections for links shared on Facebook since Universal Links work conditionally. You can force Facebook to open your app by appending $uri_redirect_mode=2 as a query parameter to your Branch link.
eg: https://example.app.link/83jlowd0?$uri_redirect_mode=2
You can learn more about forced redirections here- https://docs.branch.io/pages/links/integrate/#forced-redirections
If you still face issues, please write to support#branch.io and someone from the Branch team will be able to assist you with this.
I had the same problem. the branch object returned from Branch.io SDK was returning an object without all parameters.
All I did was follow the great document provided by Branch.io
document provided by Branch.io
So as I was using the Scene in my app. Calling these methods in my sceneDelegate solved the issue.
import UIKit
import Branch
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 }
// workaround for SceneDelegate continueUserActivity not getting called on cold start
if let userActivity = connectionOptions.userActivities.first {
BranchScene.shared().scene(scene, continue: userActivity)
}
}
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.
}
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
BranchScene.shared().scene(scene, continue: userActivity)
}
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
BranchScene.shared().scene(scene, openURLContexts: URLContexts)
}
}