84 votes

UIApplication.shared.delegate équivalent pour SceneDelegate xcode11 ?

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

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

UIApplication.shared.delegate

puis casting et spécification du nom de la propriété...

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

69voto

rmaddy Points 79279

A partir d'iOS 13, UIApplication a le connectedScenes qui est Set<UIScene> . Chacune de ces scènes a une delegate qui est un UISceneDelegate . Vous pouviez 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 l'adresse de la fenêtre UIScene de son windowScene propriété.

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

En bref, depuis un contrôleur de vue, vous pouvez faire :

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

Cependant, il arrive souvent qu'un contrôleur de vue n'ait pas de fenêtre. Cela se produit lorsqu'un contrôleur d'affichage présente un autre contrôleur d'affichage en plein écran. Cela peut se produire lorsque le contrôleur de vue se trouve 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 la chaîne des répondeurs et de la hiérarchie du contrôleur de vue jusqu'à ce que vous trouviez un chemin qui mène à la scène.

L'extension suivante vous permettra (peut-être) d'obtenir une 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
    }
}

Cette fonction peut être appelée 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 la fonction 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 des contrôleurs 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ôleurs de vue et que, quelque part dans cette hiérarchie, il est attaché à une fenêtre.


Voici une implémentation en 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

54voto

JoeGalind Points 885

J'ai réussi à le faire fonctionner en utilisant ceci :

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

0 votes

J'ai la logique de gestion des rappels OAuth dans le délégué de scène et une fermeture à laquelle je voulais accéder dans le ViewController lorsque le rappel se produit. Ce code me permet parfaitement d'accéder au délégué de scène dans le ViewController pour accéder à cette fermeture. +1

4 votes

Cela n'est valable que si votre application ne comporte qu'une seule scène. L'avantage de la prise en charge des scènes est que vous pouvez avoir plus d'une scène. Un code comme celui-ci ne fonctionnera pas correctement s'il y a plus d'une scène.

0 votes

Oui. Bien sûr. Il s'agit d'un "raccourci" pour un projet à une scène. Vous devrez passer par le tableau connectedScenes si vous souhaitez accéder à plus d'une scène.

29voto

rkamiya87 Points 301

JoeGalind Merci. J'ai également résolu le problème 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 */
}

8voto

Sam Soffes Points 8034

Vous n'avez pas besoin de faire référence SceneDelegate si vous essayez seulement de trouver la fenêtre :

(UIApplication.shared.connectedScenes.first as? UIWindowScene)?.windows.first

Vous ne devez utiliser cette approche que si votre application ne comporte qu'une seule scène et une seule fenêtre.

7voto

bsod Points 3192

iOS 13+, Swift

Les scènes représentent des instances de l'interface utilisateur de l'application et chaque scène conserve son propre état, totalement indépendant. Et l'application elle-même, qui n'est jamais qu'une instance unique, maintient des références à toutes ces scènes connectées. Les scènes étant des classes, elles sont des types de référence. Par conséquent, il suffit de maintenir une référence à la scène elle-même lorsqu'elle est connectée, puis de trouver cette scène dans l'ensemble des scènes connectées au sein de l'application. UIApplication.shared .

// Create a globally-accessible variable of some kind (global
// variable, static property, property of a singleton, etc.) that
// references this current scene (itself).
var currentScene: UIScene?

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let scene = scene as? UIWindowScene else {
            return
        }
        // Save the reference when the scene is born.
        currentScene = scene
    }

    func borat() {
        print("great success")
    }
}

// Here is a convenient view controller extension.
extension UIViewController {
    var sceneDelegate: SceneDelegate? {
        for scene in UIApplication.shared.connectedScenes {
            if scene == currentScene,
               let delegate = scene.delegate as? SceneDelegate {
                return delegate
            }
        }
        return nil
    }
}

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        sceneDelegate?.borat() // "great success"
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X