[SWIFT] AppDelegate, Push et redirection sur la bonne vue ?

Bonjour tout le monde,

Je bloque sur un petit problème, ça m'a pas l'air compliqué mais je pense que j'ai juste pas la bonne façon de faire, la bonne logique..

En gros, je reçois une notification push avec des données dedans..
Lorsque je reçois une notification "NouveauMessage" :

  • Si je suis sur le bon controller, j'actualise les nouveaux messages (pas de soucis, ça fonctionne bien ^^)
  • Si je ne suis pas sur le bon controller, j'ai une bannière qui descend, au tap sur ma bannière, je souhaiterai rediriger vers le bon contrôle (ma discussion)

En cherchant un peu, je me retrouve avec ça (dans AppDelegate)
Lorsque je clic sur ma bannière qui descend pour prévenir qu'il y a un nouveau message :

let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil) // j'appel mon storyboard
let DiscussionController = mainStoryboard.instantiateViewController(withIdentifier: "Discussion") as! DiscussionController
self.window?.rootViewController = DiscussionController
self.window?.makeKeyAndVisible()

J'arrive bien à afficher la bonne conversation sauf que je n'ai plus ma barre de navigation en haut, j'ai seulement le controller..
Comment faire pour afficher aussi la barre de navigation ?
Je me doute qu'il me manque une ligne de code quelque part mais je ne sais pas quoi mettre et vu que c'est la première fois que je test ça, je bloque un peu..

Merci de votre aide :)

Réponses

  • Bon du coup, j'ai réussi à récupérer la barre de navigation comme ça :

    let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
    let DiscussionController = mainStoryboard.instantiateViewController(withIdentifier: "DiscussionController") as! DiscussionController
    DiscussionController.IdGroupeDeDiscussion = "17" // passage des paramètres
    let navigationController = UINavigationController(rootViewController: DiscussionController)
    self.window?.rootViewController = navigationController
    self.window?.makeKeyAndVisible()
    

    Sauf que je perds une action assez importante dans cette barre de navigation..
    Le bouton "retour"

    Le code de mon bouton retour est juste un :

    dismiss(animated: true, completion: nil)

    Vu que je n'arrive plus par le chemin "normal" (via le segue), que j'affiche cette vue via la notification, c'est normal qu'il n'ai rien à "dismiss"..

    Du coup, je pense que je m'y prends mal quelque part..

    Une idée ?

  • Plutôt que de remplacer le controller principal de la fenêtre, cela me semblerait plus judicieux de présenter la nouvelle discussion au dessus du contexte actuel (une modal sheet par ex). Si tu remplaces le controller principal, dismiss ne pourra pas fonctionner, car le receveur de cette fonction doit être un controller "présenté".

    Donc plutôt que:

    let navigationController = UINavigationController(rootViewController: discussionController)
    self.window?.rootViewController = navigationController
    self.window?.makeKeyAndVisible()
    

    Tu peux faire:

    let navigationController = UINavigationController(rootViewController: discussionController)
    self.window?.rootViewController.present(navigationController, animated: true)
    

    Et pour voir ton bouton back, il faut en effet le définir à la main car tu es hors du "flux normal de navigation" (tu en crées un nouveau en fait, donc normal de ne pas avoir de back pour le premier élément).

    discussionController.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Close", style: .plain, target: self, action: dismissCurrent(_:))

    Et je te laisse l'implémentation de dismissCurrent comme exercice ;)

    Attention aux conventions: les noms d'instances doivent commencer par des minuscules.

  • CéroceCéroce Membre, Modérateur

    Quand on lance l'appli, il y a un système qui fait que le storyboard principal ("Main.storyboard" par défaut) est chargé et le View Controller mis en "Initial View Controller" est instancié et placé dans window.rootViewController.

    Dans ton cas, tu as dû mettre le Navigation Controller en "Initial View Controller", et le DiscussionController pour son contenu.

    Pourquoi ce que tu fais ne marche pas: parce que tu instancies un nouveau DiscussionController et que tu demandes à ce qu'il devienne window.rootViewController.
    Ce qui faut faire: obtenir des références vers les objets qui ont été instanciés:

    var navigationController: UINavigationController!
    var discussionController: DiscussionController!
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {        
    
        navigationController = window.rootViewController
        discussionController = navigationController.viewControllers[0]
    
        window.makeKeyAndVisible()
    
        return true
    }
    

    Cette manière de faire — accéder aux objets en connaissance de leur hiérarchie — est loin d'être élégante mais c'est bien ainsi qu'Apple propose de faire.

  • Plutôt que de remplacer le controller principal de la fenêtre, cela me semblerait plus judicieux de présenter la nouvelle discussion au dessus du contexte actuel (une modal sheet par ex).

    C'est ce que je me suis dit aussi et j'avais testé cette façon de faire avec :

    let navigationController = UINavigationController(rootViewController: discussionController)
    self.window?.rootViewController.present(navigationController, animated: true)
    

    Sauf que je me retrouve avec :

    Warning: Attempt to present on <Projet.LoginController: 0x101a0bd50> whose view is not in the window hierarchy!

    Ce qui est bizarre c'est qu'il parle de LoginController alors que je suis plus dessus mais sur mon HomeController

    @Renaud Et pour voir ton bouton back, il faut en effet le définir à la main car tu es hors du "flux normal de navigation" (tu en crées un nouveau en fait, donc normal de ne pas avoir de back pour le premier élément).

    @Céroce Pourquoi ce que tu fais ne marche pas: parce que tu instancies un nouveau DiscussionController et que tu demandes à ce qu'il devienne window.rootViewController.

    Ouai c'est ce que je me suis dit, je suis "hors du flux normal de navigation" et que j'partais n'importe comment.. d'où mon post ici ^^

    Ce qui faut faire: obtenir des références vers les objets qui ont été instanciés:

    Ok, j'ai donc mis ça dans mon AppDelegate pour garder une reference dessus :

    var navigationController: UINavigationController!
    var discussionController: DiscussionController!
    

    J'ai pas compris pourquoi tu met le reste dans didFinishLaunchingWithOptions, je suppose que c'est juste pour l'exemple (où j'ai loupé un truc) ?

    J'ai fais le test en le mettant sur le tap sur ma bannière qui descend quand il y a un nouveau message et je me retrouve avec cette erreur :

    Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file /CheminDuProjet/AppDelegate.swift, line 260

    et la ligne 260 est :

    self.DiscussionController = self.navigationController.viewControllers[0] as? DiscussionController
    

    Je patauge :/

  • CéroceCéroce Membre, Modérateur

    @Insou a dit :

    Sauf que je me retrouve avec :

    Warning: Attempt to present on <Projet.LoginController: 0x101a0bd50> whose view is not in the window hierarchy!

    Nous n'avons pas ton code et nous ne savons pas comment sont organisés tes View Controllers.

    Ce qui faut faire: obtenir des références vers les objets qui ont été instanciés:

    Ok, j'ai donc mis ça dans mon AppDelegate pour garder une reference dessus :

    var navigationController: UINavigationController!
    var discussionController: DiscussionController!
    

    J'ai pas compris pourquoi tu met le reste dans didFinishLaunchingWithOptions, je suppose que c'est juste pour l'exemple (où j'ai loupé un truc) ?

    Non, c'est parce qu'il faut le faire à un moment où on sait que le storyboard a été lu et ses objets instanciés.
    Pour faire un parallèle: on accède aux outlets dans viewDidLoad() parce qu'on est sûr à ce moment que la vue est chargée ainsi que ses vues enfants, et que les outlets pointent bien vers les objets.

    J'ai fais le test en le mettant sur le tap sur ma bannière qui descend quand il y a un nouveau message et je me retrouve avec cette erreur :

    Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file /CheminDuProjet/AppDelegate.swift, line 260

    et la ligne 260 est :

    self.DiscussionController = self.navigationController.viewControllers[0] as? DiscussionController
    

    Encore une fois, je ne connais pas la hiérarchie de tes view controllers. J'ai supposé que le navigationController était le rootViewController. Forcément, si ce n'est pas le cas et que tu écris:

    navigationController = window.rootViewController as? UINavigationController
    

    et que le rootViewController n'est pas un UINavigationController, alors ça renvoie nil.

  • InsouInsou Membre
    novembre 2019 modifié #7

    Non, c'est parce qu'il faut le faire à un moment où on sait que le storyboard a été lu et ses objets instanciés.
    Pour faire un parallèle: on accède aux outlets dans viewDidLoad() parce qu'on est sûr à ce moment que la vue est chargée ainsi que ses vues enfants, et que les outlets pointent bien vers les objets.

    Ok je vois, du coup je laisse ça ici.

    Encore une fois, je ne connais pas la hiérarchie de tes view controllers.

    J'arrive sur LoginController, pour m'identifier, ensuite j'affiche mon HomeController (via un segue)..
    Et à partir de là, j'aimerai pouvoir afficher n'importe quel controller..

    Mais du coup je ne sais pas quoi mettre à la place de :
    navigationController = window.rootViewController as? UINavigationController

    Cette partie là n'est pas clair pour moi :/

    Est-ce-que je peux redéfinir un nouveau rootController par exemple ?
    Peut-être qu'un fois que je suis connecté et que je passe par le segue pour aller sur mon HomeController, je peux changer le rootController (remplacer LoginController par HomeController) et ainsi pouvoir régler le problème de :

    Warning: Attempt to present on <Projet.LoginController: 0x101a0bd50> whose view is not in the window hierarchy!

    nan ?

    (je sais pas si c'est possible, si ça apporter d'autres soucis ou si je pars pas dans la bonne direction mais je vais quand même allé tester..)

  • CéroceCéroce Membre, Modérateur

    @Insou a dit :

    Est-ce-que je peux redéfinir un nouveau rootController par exemple ?

    Oui, mais, tu ne dois pas. Le rootViewController est déjà défini par ton Main.storyboard. C'est le view controller marqué en "Initial View Controller", celui avec une flèche.

    J'arrive sur LoginController, pour m'identifier, ensuite j'affiche mon HomeController (via un segue)..

    Donc, le rootViewController est un LoginController ?

    Peut-être qu'un fois que je suis connecté et que je passe par le segue pour aller sur mon HomeController, je peux changer le rootController (remplacer LoginController par HomeController)

    Disons qu'habituellement on ne fait pas ainsi, parce qu'on a rarement besoin de s'identifier.
    Typiquement, on a un UITabBarController à la racine. Aussi, on va plutôt afficher le LoginViewController en modal plein écran quand c'est nécessaire — par exemple au premier lancement.

  • Oui, mais, tu ne dois pas.

    Pourquoi ?
    Une fois que je suis connecté, je n'ai plus besoin de lui..
    Et si je relance l'appli, il redevient le rootViewController
    Je suppose que j'ai pas la bonne vision du truc ^^

    Donc, le rootViewController est un LoginController ?

    Du coup, j'ai vérifier via le code et oui, c'est bien LoginController (et c'est aussi lui qui a la flèche)

    Typiquement, on a un UITabBarController à la racine. Aussi, on va plutôt afficher le LoginViewController en modal plein écran quand c'est nécessaire — par exemple au premier lancement.

    Donc je devrais plutôt arriver sur mon HomeController (mettre la flèche dessus) et si je ne suis pas connecté, j'affiche la page de connexion ?

  • CéroceCéroce Membre, Modérateur

    @Insou a dit :
    Une fois que je suis connecté, je n'ai plus besoin de lui..

    L'appli aura sans doute une option pour se déconnecter, et il faudra alors demander à se reconnecter.

    Et si je relance l'appli, il redevient le rootViewController
    Je suppose que j'ai pas la bonne vision du truc ^^

    L'utilisateur ne se connecte pas à chaque lancement. Tu vas conserver les identifiants dans le Keychain.

    Donc, le rootViewController est un LoginController ?

    Du coup, j'ai vérifier via le code et oui, c'est bien LoginController (et c'est aussi lui qui a la flèche)

    Typiquement, on a un UITabBarController à la racine. Aussi, on va plutôt afficher le LoginViewController en modal plein écran quand c'est nécessaire — par exemple au premier lancement.

    Donc je devrais plutôt arriver sur mon HomeController (mettre la flèche dessus) et si je ne suis pas connecté, j'affiche la page de connexion ?

    Oui, c'est une fonctionnement plus classique.

    Pour revenir à ton problème:
    dans l'AppDelegate, tu vas donc récupérer une référence vers un HomeController (et pas un UINavigationController).
    Ensuite, tu surchargeras la méthode prepareForSegue() du HomeViewController, afin de récupérer des références vers ses view controllers enfants.

  • L'utilisateur ne se connecte pas à chaque lancement. Tu vas conserver les identifiants dans le Keychain.

    Pour le coup si..
    C'est pas pratique mais c'est comme ça (et quand ça va les souler de se connecter à chaque fois, ils vont me demander si c'est pas possible de rester connecter, j'le vois venir gros comme une maison ^^)

    Bon, je vais commencer par faire cette modification là..
    Je reviendrais pour tester la suite après :)

Connectez-vous ou Inscrivez-vous pour répondre.