J'ai un problème pour écrire un init personnalisé pour la sous-classe de UIViewController, fondamentalement, je veux passer la dépendance via la méthode init pour viewController plutôt que de définir la propriété directement comme viewControllerB.property = value
J'ai donc créé un init personnalisé pour mon viewController et j'ai appelé 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, j'ai également fait de l'interface pour la classe personnalisée 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'essaye d'appeler mon init personnalisé sans MyViewController(meme: meme)
qu'il n'initialise du tout les propriétés dans mon viewController ...
J'essayais de déboguer, j'ai trouvé dans mon viewController, init(coder aDecoder: NSCoder)
être appelé en premier, puis mon init personnalisé est appelé plus tard. Cependant, ces deux méthodes init renvoient des self
adresses mémoire différentes .
Je soupçonne que quelque chose ne va pas avec l'initialisation de mon viewController, et il reviendra 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 personnalisé 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)
}
}
Réponses:
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'initialisation et le storyboard personnalisés. Mais vous pouvez toujours utiliser une méthode statique pour instancier ViewController à partir d'un storyboard et y effectuer une configuration supplémentaire. 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 comme identificateur de contrôleur de vue dans votre storyboard. Vous devrez peut-être également changer le nom du storyboard dans le code ci-dessus.
la source
Vous ne pouvez pas utiliser un initialiseur personnalisé lorsque vous initialisez à partir d'un storyboard, en utilisant
init?(coder aDecoder: NSCoder)
est la façon dont Apple a conçu le storyboard pour initialiser un contrôleur. Cependant, il existe des moyens d'envoyer des données à un fichierUIViewController
.Le nom de votre contrôleur de vue y est
detail
, donc je suppose que vous y arrivez à partir d'un contrôleur différent. Dans ce cas, vous pouvez utiliser laprepareForSegue
méthode pour envoyer des données au détail (c'est 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 deMeme
à des fins de test. Assurez-vous également de transmettre l'identifiant de segment correct (il"identifier"
s'agissait simplement d'un espace réservé).la source
Comme @Caleb Kleveter l'a souligné, nous ne pouvons pas utiliser un initialiseur personnalisé lors de l'initialisation à partir d'un Storyboard.
Mais, nous pouvons résoudre le problème en utilisant la méthode factory / class qui instancie l'objet contrôleur de vue à partir de Storyboard et retourne l'objet contrôleur de vue. Je pense que c'est une manière assez cool.
Remarque: ce n'est pas une réponse exacte à la question mais plutôt une solution de contournement pour résoudre le problème.
Créez une méthode de classe, dans la classe MemeDetailVC, comme suit:
// 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())
la source
class func
init(meme: Meme) -> MemeDetailVC
Xcode 10.2 est confus et donne l'erreurIncorrect argument label in call (have 'meme:', expected 'coder:')
Une façon de procéder est d'utiliser un initialiseur pratique.
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 de didacticiels Ray Wenderlich: Initialisation en profondeur qui devrait vous donner de nombreuses explications / exemples sur vos différentes options d'initialisation et la «bonne» façon de faire les choses.
EDIT : 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 utiliser d'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 complètement par programme, un initialiseur pratique est probablement le moyen le plus simple de faire ce que vous essayez de faire puisque vous n'avez pas à gérer l'initialisation requise le NSCoder (que je ne comprends toujours pas vraiment).
Si vous obtenez votre contrôleur de vue via le storyboard, vous devrez suivre la réponse de @Caleb Kleveter et convertir le contrôleur de vue dans la sous-classe souhaitée, puis définir la propriété manuellement.
la source
convenience
aidera-t-il ici.init
fonction de la classe (les structs peuvent cependant). Donc, pour appelerself.init()
, vous devez marquer leinit(meme: Meme)
commeconvenience
initialiseur. Sinon, vous devrez définir manuellement toutes les propriétés requises de aUIViewController
dans l'initialiseur vous-même, et je ne suis pas sûr de toutes ces propriétés.MemeDetail()
et dans ce cas le code planteraIl y avait à l'origine quelques réponses, qui ont été votées par la vache et supprimées même si elles étaient fondamentalement correctes. La réponse est que vous ne pouvez pas.
Lorsque vous travaillez à partir d'une définition de storyboard, vos instances de contrôleur de vue sont toutes archivées. Donc, pour les initier, il est nécessaire de
init?(coder...
les utiliser. C'est decoder
là que proviennent tous les paramètres / informations d'affichage.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 comme une propriété lors de la préparation de la segue, ou vous pouvez abandonner les segues et 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 par le SDK et passez des paramètres supplémentaires par la suite.
la source
Swift 5
Vous pouvez écrire un initialiseur personnalisé comme celui-ci ->
class MyFooClass: UIViewController { var foo: Foo? init(with foo: Foo) { self.foo = foo super.init(nibName: nil, bundle: nil) } public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) self.foo = nil } }
la source
UIViewController
classe conforme auNSCoding
protocole défini comme:public protocol NSCoding { public func encode(with aCoder: NSCoder) public init?(coder aDecoder: NSCoder) // NS_DESIGNATED_INITIALIZER }
A donc
UIViewController
deux initialiseurs désignésinit?(coder aDecoder: NSCoder)
etinit(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)
.Storyborad appelle
init?(coder aDecoder: NSCoder)
directement initUIViewController
etUIView
, il n'y a pas de place pour que vous passiez des paramètres.Une solution de contournement fastidieuse 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 }
la source
À partir d'iOS 13, vous pouvez initialiser le contrôleur de vue qui réside dans un storyboard en utilisant:
instantiateViewController(identifier:creator:)
method sur l'UIStoryboard
instance.tutoriel: https://sarunw.com/posts/better-dependency-injection-for-storyboards-in-ios13/
la source
Avertissement: je ne préconise pas cela et je n'ai pas testé en profondeur sa résilience, mais c'est une solution potentielle que j'ai découverte en jouant.
Techniquement, une initialisation personnalisée peut être obtenue tout en préservant l'interface configurée pour le storyboard en initialisant le contrôleur de vue deux fois: la première fois via votre personnalisé
init
, et la deuxième fois à l'intérieurloadView()
où vous prenez la vue du storyboard.final class CustomViewController: UIViewController { @IBOutlet private weak var label: UILabel! @IBOutlet private weak var textField: UITextField! private let foo: Foo! init(someParameter: Foo) { self.foo = someParameter super.init(nibName: nil, bundle: nil) } override func loadView() { //Only proceed if we are not the storyboard instance guard self.nibName == nil else { return super.loadView() } //Initialize from storyboard let storyboard = UIStoryboard(name: "Main", bundle: nil) let storyboardInstance = storyboard.instantiateViewController(withIdentifier: "CustomVC") as! CustomViewController //Remove view from storyboard instance before assigning to us let storyboardView = storyboardInstance.view storyboardInstance.view.removeFromSuperview() storyboardInstance.view = nil self.view = storyboardView //Receive outlet references from storyboard instance self.label = storyboardInstance.label self.textField = storyboardInstance.textField } required init?(coder: NSCoder) { //Must set all properties intended for custom init to nil here (or make them `var`s) self.foo = nil //Storyboard initialization requires the super implementation super.init(coder: coder) } }
Maintenant, ailleurs dans votre application, vous pouvez appeler votre initialiseur personnalisé comme
CustomViewController(someParameter: foo)
et toujours recevoir la configuration de la vue du storyboard.Je ne considère pas cela comme une excellente solution pour plusieurs raisons:
init
doivent être stockés en tant que propriétés facultativesVous pouvez peut-être accepter ces compromis, mais utilisez-les à vos propres risques .
la source
Bien que nous puissions maintenant faire une init personnalisée pour les contrôleurs par défaut dans le storyboard en utilisant
instantiateInitialViewController(creator:)
et pour les segues, y compris la relation et le spectacle.Cette fonctionnalité a été ajoutée dans Xcode 11 et ce qui suit est un extrait des notes de mise à jour de Xcode 11 :
Une méthode de contrôleur de vue annotée avec le nouvel
@IBSegueAction
attribut peut être utilisée pour créer le contrôleur de vue de destination d'un segment dans le code, à l'aide d'un initialiseur personnalisé avec toutes les valeurs requises. Cela permet d'utiliser des contrôleurs de vue avec des exigences d'initialisation non facultatives dans les storyboards. Créez une connexion depuis un segue vers une@IBSegueAction
méthode sur son contrôleur de vue source. Sur les nouvelles versions de système d'exploitation qui prennent en charge les actions Segue, cette méthode sera appelée et la valeur qu'elle renvoie sera celledestinationViewController
de l'objet segue passé àprepareForSegue:sender:
. Plusieurs@IBSegueAction
méthodes peuvent être définies sur un seul contrôleur de vue source, ce qui peut réduire la nécessité de vérifier les chaînes d'identificateur de segue dansprepareForSegue:sender:
. (47091566)Une
IBSegueAction
méthode prend jusqu'à trois paramètres: un codeur, l'expéditeur et l'identifiant de la séquence. Le premier paramètre est obligatoire et les autres paramètres peuvent être omis de la signature de votre méthode si vous le souhaitez. LeNSCoder
doit être transmis à l'initialiseur du contrôleur de vue de destination, pour s'assurer qu'il est personnalisé avec des valeurs configurées dans le storyboard. La méthode retourne un contrôleur de vue qui correspond au type de contrôleur de destination défini dans le storyboard, ounil
pour provoquer l'initialisation d'un contrôleur de destination avec lainit(coder:)
méthode standard . Si vous savez que vous n'avez pas besoin de retournernil
, le type de retour peut être non facultatif.Dans Swift, ajoutez l'
@IBSegueAction
attribut:@IBSegueAction func makeDogController(coder: NSCoder, sender: Any?, segueIdentifier: String?) -> ViewController? { PetController( coder: coder, petName: self.selectedPetName, type: .dog ) }
En Objective-C, ajoutez
IBSegueAction
devant le type de retour:- (IBSegueAction ViewController *)makeDogController:(NSCoder *)coder sender:(id)sender segueIdentifier:(NSString *)segueIdentifier { return [PetController initWithCoder:coder petName:self.selectedPetName type:@"dog"]; }
la source
Le bon flux est, appelez l'initialiseur désigné qui dans ce cas est l'init avec nibName,
init(tap: UITapGestureRecognizer) { // Initialise the variables here // Call the designated init of ViewController super.init(nibName: nil, bundle: nil) // Call your Viewcontroller custom methods here }
la source
// Le contrôleur de vue est dans Main.storyboard et son identificateur est défini
Classe B
class func customInit(carType:String) -> BViewController { let storyboard = UIStoryboard(name: "Main", bundle: nil) let objClassB = storyboard.instantiateViewController(withIdentifier: "BViewController") as? BViewController print(carType) return objClassB! }
Classe A
let objB = customInit(carType:"Any String") navigationController?.pushViewController(objB,animated: true)
la source