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)
}
}
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.
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é).
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.
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())
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.
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
}