web-dev-qa-db-fra.com

UIApplication.shared.delegate équivalent pour SceneDelegate xcode11?

J'ai défini une propriété let dans mon SceneDelegate. J'aimerais que certains des ViewControllers puissent y accéder au sein de la scène.

Dans UIKit, je pouvais accéder aux propriétés App Delegate comme ceci:

UIApplication.shared.delegate

puis coulée et spécifiant le nom de la propriété ...

Existe-t-il un équivalent pour obtenir la référence a au SceneDelegate dans lequel le contrôleur de vue se trouve depuis l'instance de UIViewController?

26
zumzum

Depuis iOS 13, UIApplication a la propriété connectedScenes qui est Set<UIScene>. Chacune de ces scènes a un delegate qui est un UISceneDelegate. Vous pouvez donc accéder à tous les délégués de cette façon.

Une scène peut gérer une ou plusieurs fenêtres (UIWindow) et vous pouvez obtenir UIScene d'une fenêtre à partir de sa propriété windowScene.

Si vous souhaitez le délégué de scène pour un contrôleur de vue spécifique, notez ce qui suit. A partir d'un UIViewController, vous pouvez obtenir sa fenêtre depuis sa vue. De la fenêtre, vous pouvez obtenir sa scène et bien sûr de la scène, vous pouvez obtenir son délégué.

En bref, à partir d'un contrôleur de vue, vous pouvez faire:

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

Cependant, il y a de nombreuses fois où un contrôleur de vue n'a pas de fenêtre. Cela se produit lorsqu'un contrôleur de vue présente un autre contrôleur de vue plein écran. Cela peut se produire lorsque le contrôleur de vue est dans un contrôleur de navigation et que le contrôleur de vue n'est pas le contrôleur de vue supérieur visible.

Cela nécessite une approche différente pour trouver la scène du contrôleur de vue. En fin de compte, vous devez utiliser une combinaison de parcours de la chaîne de répondeurs et de la hiérarchie du contrôleur de vue jusqu'à ce que vous trouviez un chemin menant à la scène.

L'extension suivante vous fournira (peut) un UIScene à partir d'une vue ou d'un contrôleur de vue. Une fois que vous avez la scène, vous pouvez accéder à son délégué.

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

Cela peut être appelé depuis n'importe quelle vue ou contrôleur de vue pour obtenir sa scène. Mais notez que vous ne pouvez obtenir la scène à partir d'un contrôleur de vue qu'après que viewDidAppear a été appelé au moins une fois. Si vous essayez plus tôt, il se peut que le contrôleur de vue ne fasse pas encore partie de la hiérarchie du contrôleur de vue.

Cela fonctionnera même si la fenêtre de la vue du contrôleur de vue est nulle tant que le contrôleur de vue fait partie d'une hiérarchie de contrôleur de vue et quelque part dans cette hiérarchie, il est attaché à une fenêtre.


Voici une implémentation Objective-C de l'extension UIResponder:

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
24
rmaddy

J'ai pu le faire fonctionner en utilisant ceci:

let scene = UIApplication.shared.connectedScenes.first
if let sd : SceneDelegate = (scene?.delegate as? SceneDelegate) {
    sd.blah()
}
18
JoeGalind

JoeGalind Merci, j'ai également résolu de manière similaire.

// 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 */
}
3
rkamiya87

J'utilise ceci dans mon MasterViewController:

- (void)viewDidLoad {
    [super viewDidLoad];

    SceneDelegate *sceneDelegate = (SceneDelegate *)self.parentViewController.view.window.windowScene.delegate;
}

self.view.window Est nul à ce stade, j'ai donc dû atteindre le parent dont la vue a déjà été chargée et ajoutée à la fenêtre.

Si vous utilisez le contrôleur de vue fractionnée et que le délégué de scène est le délégué divisé, vous pouvez éviter complètement la fenêtre/scène et simplement faire:

SceneDelegate *sceneDelegate = (SceneDelegate *)self.splitViewController.delegate;

J'utilise ceci dans mon DetailViewController.

0
malhal