web-dev-qa-db-fra.com

Init personnalisé pour UIViewController dans Swift avec configuration de l'interface dans le storyboard

J'ai un problème pour écrire un init personnalisé pour la sous-classe de UIViewController. En gros, je veux passer la dépendance par la méthode init pour viewController plutôt que de définir une propriété directement, comme viewControllerB.property = value.

J'ai donc créé un init personnalisé pour mon viewController et appeler un init super désigné

init(meme: Meme?) {
        self.meme = meme
        super.init(nibName: nil, bundle: nil)
    }

L’interface du contrôleur de vue réside dans le storyboard, c’est aussi l’interface de la classe personnalisée qui est mon contrôleur de vue. Et Swift nécessite d'appeler cette méthode init même si vous ne faites rien dans cette méthode. Sinon, le compilateur se plaindra ...

required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

Le problème est que lorsque j'essaie d'appeler mon init personnalisé avec MyViewController(meme: meme), il ne lance pas du tout les propriétés de mon viewController ...

J'essayais de déboguer, j'ai trouvé dans mon viewController, init(coder aDecoder: NSCoder) est appelé en premier, puis mon init personnalisé est appelé plus tard. Cependant, ces deux méthodes init renvoient des adresses de mémoire self différentes.

Je soupçonne que quelque chose ne va pas avec init pour mon viewController et il retournera toujours self avec le init?(coder aDecoder: NSCoder), qui n'a pas d'implémentation.

Est-ce que quelqu'un sait comment créer correctement un init pour votre viewController? Remarque: l'interface de mon viewController est configurée dans le storyboard.

voici mon code viewController:

class MemeDetailVC : UIViewController {

    var meme : Meme!

    @IBOutlet weak var editedImage: UIImageView!

    // TODO: incorrect init
    init(meme: Meme?) {
        self.meme = meme
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override func viewDidLoad() {
        /// setup nav title
        title = "Detail Meme"

        super.viewDidLoad()
    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        editedImage = UIImageView(image: meme.editedImage)
    }

}
64
Pigfly

Comme cela a été spécifié dans l'une des réponses ci-dessus, vous ne pouvez pas utiliser à la fois la méthode d'init personnalisée et le scénarimage. Mais vous pouvez toujours utiliser une méthode statique pour instancier ViewController à partir d'un storyboard et effectuer une configuration supplémentaire sur celui-ci. Il ressemblera à ceci:

class MemeDetailVC : UIViewController {

    var meme : Meme!

    static func makeMemeDetailVC(meme: Meme) -> MemeDetailVC {
        let newViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("IdentifierOfYouViewController") as! MemeDetailVC

        newViewController.meme = meme

        return newViewController
    }
}

N'oubliez pas de spécifier IdentifierOfYouViewController en tant qu'identificateur de contrôleur de vue dans votre storyboard. Vous devrez peut-être également changer le nom du storyboard dans le code ci-dessus.

33

Vous ne pouvez pas utiliser un initialiseur personnalisé lors de l'initialisation à partir d'un storyboard, avec init?(coder aDecoder: NSCoder) voici comment Apple a conçu le storyboard pour initialiser un contrôleur. Toutefois, il existe des moyens d'envoyer données à un UIViewController.

Le nom de votre contrôleur de vue contient detail, je suppose donc que vous y arrivez à partir d'un autre contrôleur. Dans ce cas, vous pouvez utiliser la méthode prepareForSegue pour envoyer des données au détail (Swift 3):

override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "identifier" {
        if let controller = segue.destinationViewController as? MemeDetailVC {
            controller.meme = "Meme"
        }
    }
}

Je viens d'utiliser une propriété de type String au lieu de Meme à des fins de test. Assurez-vous également que vous identifiez le bon identifiant de séquence ("identifier" était juste un espace réservé).

23
Caleb Kleveter

L'une des méthodes que j'ai employées consiste à utiliser un initialiseur de commodité.

class MemeDetailVC : UIViewController {

    convenience init(meme: Meme) {
        self.init()
        self.meme = meme
    }
}

Ensuite, vous initialisez votre MemeDetailVC avec let memeDetailVC = MemeDetailVC(theMeme)

La documentation d'Apple sur les initialiseurs est plutôt bonne, mais mon préféré est la série Ray Wenderlich: Initialisation en profondeur qui devrait vous fournir une foule d'explications et d'exemples sur vos diverses options d'initialisation. et la "bonne" façon de faire les choses.


[~ # ~] modifier [~ # ~] : Bien que vous puissiez utiliser un initialiseur pratique sur les contrôleurs de vue personnalisés, tout le monde a raison de dire que vous ne pouvez pas utilisez des initialiseurs personnalisés lors de l'initialisation à partir du storyboard ou via une séquence de storyboard.

Si votre interface est configurée dans le storyboard et que vous créez le contrôleur de manière entièrement programmée, un initialiseur pratique est probablement le moyen le plus simple de faire ce que vous essayez de faire, car vous n'avez pas à gérer l'init requis. le NSCoder (que je ne comprends toujours pas vraiment).

Si vous obtenez votre contrôleur de vue via le storyboard, vous devrez alors suivre réponse de @ Caleb Kleveter et convertir le contrôleur de vue dans la sous-classe de votre choix, puis définir la propriété manuellement.

18
Ponyboy47

Comme @Caleb Kleveter l'a souligné, nous ne pouvons pas utiliser d'initialiseur personnalisé lors de l'initialisation à partir d'un Storyboard.

Cependant, nous pouvons résoudre le problème en utilisant la méthode factory/class qui instancie un objet contrôleur de vue à partir de Storyboard et renvoie un objet contrôleur de vue. Je pense que c'est une façon plutôt cool.

Note: Ce n'est pas une réponse exacte à la question, mais plutôt une solution de contournement pour résoudre le problème.

La méthode make class, dans la classe MemeDetailVC, est la suivante:

// Considering your view controller resides in Main.storyboard and it's identifier is set to "MemeDetailVC"
class func `init`(meme: Meme) -> MemeDetailVC {
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    let vc = storyboard.instantiateViewController(withIdentifier: "MemeDetailVC") as? MemeDetailVC
    vc.meme = meme
    return vc
}

Usage:

let memeDetailVC = MemeDetailVC.init(meme: Meme())
15
Nitesh Borad

Il y avait à l'origine un couple de réponses, qui ont été voté vache et supprimé bien qu'ils étaient fondamentalement correct. La réponse est que vous ne pouvez pas.

Lorsque vous utilisez une définition de storyboard, vos instances de contrôleur de vue sont toutes archivées. Donc, pour les initier, il faut que init?(coder... être utilisé. Le coder est l’origine de tous les paramètres/informations de vue.

Donc, dans ce cas, il n'est pas possible d'appeler également une autre fonction init avec un paramètre personnalisé. Il doit être défini en tant que propriété lors de la préparation de la division, ou vous pouvez laisser les divisions en différé, charger les instances directement à partir du storyboard et les configurer (essentiellement un modèle d'usine utilisant un storyboard).

Dans tous les cas, vous utilisez la fonction init requise du SDK et transmettez ensuite des paramètres supplémentaires.

7
Wain

La classe UIViewController est conforme au protocole NSCoding qui est défini comme suit:

public protocol NSCoding {

   public func encode(with aCoder: NSCoder)

   public init?(coder aDecoder: NSCoder) // NS_DESIGNATED_INITIALIZER
}    

Donc, UIViewController a deux initialisateurs désignés init?(coder aDecoder: NSCoder) et init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?).

Storyborad appelle init?(coder aDecoder: NSCoder) directement à init UIViewController et UIView, il n'y a pas de place pour vous pour passer des paramètres.

Une solution de contournement lourde consiste à utiliser un cache temporaire:

class TempCache{
   static let sharedInstance = TempCache()

   var meme: Meme?
}

TempCache.sharedInstance.meme = meme // call this before init your ViewController    

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder);
    self.meme = TempCache.sharedInstance.meme
}
1
wj2061