Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UIApplication.shared.delegate equivalent for SceneDelegate xcode11?

I have defined a let property within my SceneDelegate. I would like some of the ViewControllers to be able to access that within the scene.

In UIKit I could access App Delegate properties like this:

UIApplication.shared.delegate

then casting and specifying the property name...

Is there an equivalent to get the a reference to the SceneDelegate the view controller is in from the UIViewController's instance?

like image 955
zumzum Avatar asked Jun 13 '19 21:06

zumzum


People also ask

What is the difference between AppDelegate and SceneDelegate?

AppDelegate is responsible for handling application-level events, like app launch and the SceneDelegate is responsible for scene lifecycle events like scene creation, destruction and state restoration of a UISceneSession.

What is UIApplication in Swift?

The application object maintains a list of open windows ( UIWindow objects), which it can use to retrieve any of the app's UIView objects. The UIApplication class defines a delegate that conforms to the UIApplicationDelegate protocol and must implement some of the protocol's methods.


3 Answers

As of iOS 13, UIApplication has the connectedScenes property which is Set<UIScene>. Each of those scenes has a delegate which is a UISceneDelegate. So you could access all of the delegates that way.

A scene can manage one or more windows (UIWindow) and you can get a window's UIScene from its windowScene property.

If you want the scene delegate for a specific view controller then note the following. From a UIViewController you can get its window from its view. From the window you can get its scene and of course from the scene you can get its delegate.

In short, from a view controller, you can do:

let mySceneDelegate = self.view.window.windowScene.delegate

However, there are plenty of times where a view controller has no window. This happens when a view controller presents another full screen view controller. This can happened when the view controller is in a navigation controller and the view controller is not the top, visible view controller.

This requires a different approach to finding the view controller's scene. Ultimately you need to use a combination of walking the responder chain and the view controller hierarchy until you find a path that leads to the scene.

The following extension will (may) get you a UIScene from a view or view controller. Once you have the scene, you can access its delegate.

Add UIResponder+Scene.swift:

import UIKit

@available(iOS 13.0, *)
extension UIResponder {
    @objc var scene: UIScene? {
        return nil
    }
}

@available(iOS 13.0, *)
extension UIScene {
    @objc override var scene: UIScene? {
        return self
    }
}

@available(iOS 13.0, *)
extension UIView {
    @objc override var scene: UIScene? {
        if let window = self.window {
            return window.windowScene
        } else {
            return self.next?.scene
        }
    }
}

@available(iOS 13.0, *)
extension UIViewController {
    @objc override var scene: UIScene? {
        // Try walking the responder chain
        var res = self.next?.scene
        if (res == nil) {
            // That didn't work. Try asking my parent view controller
            res = self.parent?.scene
        }
        if (res == nil) {
            // That didn't work. Try asking my presenting view controller 
            res = self.presentingViewController?.scene
        }

        return res
    }
}

This can be called from any view or view controller to get its scene. But note that you can only get the scene from a view controller only after viewDidAppear has been called at least once. If you try any sooner then the view controller may not yet be part of the view controller hierarchy.

This will work even if the window of the view of the view controller is nil as long as the view controller is part of a view controller hierarchy and somewhere up that hierarchy, it is attached to a window.


Here is an Objective-C implementation of the UIResponder extension:

UIResponder+Scene.h:

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface UIResponder (Scene)

@property (nonatomic, readonly, nullable) UIScene *scene API_AVAILABLE(ios(13.0));

@end

NS_ASSUME_NONNULL_END

UIResponder+Scene.m:

#import "ViewController+Scene.h"

@implementation UIResponder (Scene)

- (UIScene *)scene {
    return nil;
}

@end

@implementation UIScene (Scene)

- (UIScene *)scene {
    return self;
}

@end

@implementation UIView (Scene)

- (UIScene *)scene {
    if (self.window) {
        return self.window.windowScene;
    } else {
        return self.nextResponder.scene;
    }
}

@end

@implementation UIViewController (Scene)

- (UIScene *)scene {
    UIScene *res = self.nextResponder.scene;
    if (!res) {
        res = self.parentViewController.scene;
    }
    if (!res) {
        res = self.presentingViewController.scene;
    }

    return res;
}

@end
like image 73
rmaddy Avatar answered Oct 09 '22 08:10

rmaddy


I was able to get it to work using this:

let scene = UIApplication.shared.connectedScenes.first
if let sd : SceneDelegate = (scene?.delegate as? SceneDelegate) {
    sd.blah()
}
like image 45
JoeGalind Avatar answered Oct 09 '22 06:10

JoeGalind


JoeGalind Thank you I also solved in a similar way.

// iOS13 or later
if #available(iOS 13.0, *) {
    let sceneDelegate = UIApplication.shared.connectedScenes
        .first!.delegate as! SceneDelegate
    sceneDelegate.window!.rootViewController = /* ViewController Instance */

// iOS12 or earlier
} else {
    // UIApplication.shared.keyWindow?.rootViewController
    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    appDelegate.window!.rootViewController = /* ViewController Instance */
}
like image 30
rkamiya87 Avatar answered Oct 09 '22 08:10

rkamiya87