web-dev-qa-db-fra.com

Init personnalisé de UIViewController à partir du storyboard

J'ai essayé de trouver des questions pertinentes mais je n'ai rien pu obtenir, j'espère que quelqu'un pourra vous aider.

J'ai installé certains UIViewController sur un storyboard. Je souhaite ensuite charger un des contrôleurs de vue dans le code et le placer dans la pile de navigation. Je trouve que la bonne façon de faire est d'utiliser 

instantiateViewControllerWithIdentifier

Ceci appelle init(coder: NSCoder) et tout va bien, mon programme fonctionne, mais je veux pouvoir utiliser un initialiseur personnalisé qui configure certaines variables pour mon contrôleur de vue. Considérer ce qui suit:

class A : UIViewController {   

  let i : Int

  required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    // property self.i not initialized at super.init call   
  }

}

Je reçois évidemment une erreur, car i doit être spécifié au moment de la création de l'objet. Des solutions à cela? Je ne suis pas intéressé à déclarer i comme var et à le configurer ultérieurement car cela annule le point et je n'ai plus de garantie du compilateur que i est immuable.

Clarification edit

Supposons que j'ai une ViewController chargée qui a une variable i. Cette valeur est variable et peut changer. Supposons maintenant de ce ViewController que je veuille en présenter un autre et l’initialiser avec i.

class ViewController: UIViewController {

   var i : Int

   // ... other things

  // in response to some button tap...
  @IBAction func tappedButton(sender: AnyObject) {
    let st = UIStoryboard(name: "Main", bundle: nil)
    let vc = st.instantiateViewControllerWithIdentifier("AControllerID") as! A
    // How do I initialize A with i ?
    self.presentViewController(vc, animated: true, completion: nil)
  }

}

Je ne semble pas pouvoir faire cela et garder i immuable en utilisant let au lieu de var.

16
rafalio

Une simplification de ma réponse précédente qui est rapide et évite les correctifs alternatifs:

Voici un contrôleur de vue de détail que vous voudrez peut-être instancier à partir du storyboard avec un ensemble objectID:

import UIKit

class DetailViewController: UIViewController {

    var objectID : Int!

    internal static func instantiate(with objectID: Int) -> DetailViewController {

        let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "DetailViewController") as DetailViewController
        vc.objectID = objectID
        return vc
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        if((objectID) != nil){
            print("Here is my objectID: \(objectID)")
        }
    }
}

Voici comment vous l'utiliseriez pour pousser sur un contrôleur de navigation avec objectID défini sur 1:

self.navigationController.pushViewController(DetailViewController.instantiate(1), animated: true)

Ajout d'un article de blog: https://theswiftcook.wordpress.com/2017/02/17/how-to-initialize-a-storyboard-viewcontroller-with-data- sans-segues-Swift-3-0git/ /

Lien vers l'exemple sur GitHub: https://github.com/hammadzz/Instantiate-ViewController-From-Storyboard-With-Data

6
hahmed

Ci-dessous deux assistants, l'un est un enum Storyboard, ajoutez chaque storyboard de votre projet en tant que cas sous cet enum. Le nom doit correspondre au fichier {. Storyboard_name} .storyboard. Chaque contrôleur de vue de votre storyboard doit avoir son identifiant de storyboard défini sur le nom de la classe. C'est une pratique assez courante.

import UIKit

public enum Storyboard: String {
    case Main
    case AnotherStoryboard
    //case {storyboard_name}

    public func instantiate<VC: UIViewController>(_ viewController: VC.Type) -> VC {
        guard
            let vc = UIStoryboard(name: self.rawValue, bundle: nil)
                .instantiateViewController(withIdentifier: VC.storyboardIdentifier) as? VC
            else { fatalError("Couldn't instantiate \(VC.storyboardIdentifier) from \(self.rawValue)") }

        return vc
    }

    public func instantiateInitialVC() -> UIViewController {

        guard let vc = UIStoryboard(name: self.rawValue, bundle: nil).instantiateInitialViewController() else {
            fatalError("Couldn't instantiate initial viewcontroller from \(self.rawValue)")
        }

        return vc
    }
}

extension UIViewController {
    public static var defaultNib: String {
        return self.description().components(separatedBy: ".").dropFirst().joined(separator: ".")
    }

    public static var storyboardIdentifier: String {
        return self.description().components(separatedBy: ".").dropFirst().joined(separator: ".")
    }
}

Voici comment vous pouvez instancier à partir d'un storyboard avec une valeur définie dans votre contrôleur de vue. Voici la magie:

import UIKit

class DetailViewController: UIViewController {

    var objectID : Int!
    var objectDetails: ObjectDetails = ObjectDetails()        

    internal static func instantiate(with objectID: Int) -> DetailViewController {

        let vc = Storyboard.Main.instantiate(DetailViewController.self)
        vc.objectID = objectID
        return vc
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        if((objectID) != nil){
            // In this method I use to make a web request to pull details from an API
            loadObjectDetails()
        }
    }
}

(Architecture influencée/copie du projet iOS open source de Kickstarter)

3
hahmed

Un moyen (laid) de résoudre ce problème:

Vous pouvez définir votre let i à partir d'un tampon externe dans votre code (variable AppDelegate dans cet exemple)

required init?(coder aDecoder: NSCoder) {
    self.i = UIApplication.shared().delegate.bufferForI

    super.init(coder: aDecoder)
}

Et lorsque vous démarrez votre UIViewController via Storyboard:

UIApplication.shared().delegate.bufferForI = myIValue
self.navigationController!.pushViewControllerFading(self.storyboard!.instantiateViewController(withIdentifier: "myViewControllerID") as UIViewController)

EDIT: Il n'est pas nécessaire de transmettre la valeur via AppDelegate. Meilleure réponse ici .

1
Tulleb