Ich habe Probleme beim Schreiben eines benutzerdefinierten Init für die Unterklasse von UIViewController. Grundsätzlich möchte ich die Abhängigkeit über die Init-Methode für ViewController übergeben, anstatt die Eigenschaft direkt wie festzulegen viewControllerB.property = value
Also habe ich eine benutzerdefinierte Init für meinen viewController erstellt und einen super designierten Init aufgerufen
init(meme: Meme?) {
self.meme = meme
super.init(nibName: nil, bundle: nil)
}
Die View Controller-Oberfläche befindet sich im Storyboard. Ich habe auch die Schnittstelle für die benutzerdefinierte Klasse zu meinem View Controller gemacht. Und Swift muss diese init-Methode aufrufen, auch wenn Sie innerhalb dieser Methode nichts tun. Andernfalls beschwert sich der Compiler ...
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
Das Problem ist, wenn ich versuche, meinen benutzerdefinierten Init aufzurufen, MyViewController(meme: meme)
ohne dass die Eigenschaften in meinem viewController überhaupt nicht initialisiert werden ...
Ich habe versucht zu debuggen, ich habe in meinem viewController gefunden, werde zuerst init(coder aDecoder: NSCoder)
aufgerufen, dann wird mein benutzerdefinierter Init später aufgerufen. Diese beiden Init-Methoden geben jedoch unterschiedliche self
Speicheradressen zurück.
Ich vermute, dass mit dem init für meinen viewController etwas nicht stimmt, und er wird immer self
mit dem zurückgegeben init?(coder aDecoder: NSCoder)
, der keine Implementierung hat.
Weiß jemand, wie man benutzerdefinierte Init für Ihren viewController richtig macht? Hinweis: Die Benutzeroberfläche meines viewControllers ist im Storyboard eingerichtet
Hier ist mein viewController-Code:
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)
}
}
quelle
Antworten:
Wie in einer der obigen Antworten angegeben, können Sie nicht sowohl die benutzerdefinierte Init-Methode als auch das Storyboard verwenden. Sie können jedoch weiterhin eine statische Methode verwenden, um ViewController über ein Storyboard zu instanziieren und zusätzliche Einstellungen vorzunehmen. Es wird so aussehen:
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 } }
Vergessen Sie nicht, IdentifierOfYouViewController als View Controller-ID in Ihrem Storyboard anzugeben. Möglicherweise müssen Sie auch den Namen des Storyboards im obigen Code ändern.
quelle
Sie können keinen benutzerdefinierten Initialisierer verwenden, wenn Sie von einem Storyboard aus initialisieren. Auf diese
init?(coder aDecoder: NSCoder)
Weise hat Apple das Storyboard zum Initialisieren eines Controllers entworfen. Es gibt jedoch Möglichkeiten, Daten an a zu sendenUIViewController
.Der Name Ihres View Controllers ist
detail
darin enthalten, also nehme ich an, dass Sie von einem anderen Controller dorthin gelangen. In diesem Fall können Sie dieprepareForSegue
Methode verwenden, um Daten an das Detail zu senden (Dies ist Swift 3):override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "identifier" { if let controller = segue.destinationViewController as? MemeDetailVC { controller.meme = "Meme" } } }
Ich habe nur eine Eigenschaft vom Typ
String
anstelle vonMeme
zu Testzwecken verwendet. Stellen Sie außerdem sicher, dass Sie die richtige Segue-ID eingeben ("identifier"
war nur ein Platzhalter).quelle
Wie @Caleb Kleveter hervorgehoben hat, können wir beim Initialisieren von einem Storyboard keinen benutzerdefinierten Initialisierer verwenden.
Wir können das Problem jedoch lösen, indem wir die Factory / Class-Methode verwenden, mit der das View Controller-Objekt aus dem Storyboard instanziiert und das View Controller-Objekt zurückgegeben wird. Ich denke, das ist ein ziemlich cooler Weg.
Hinweis: Dies ist keine genaue Antwort auf die Frage, sondern eine Problemumgehung zur Lösung des Problems.
Erstellen Sie die Klassenmethode in der MemeDetailVC-Klasse wie folgt:
// 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 }
Verwendung:
let memeDetailVC = MemeDetailVC.init(meme: Meme())
quelle
class func
init(meme: Meme) -> MemeDetailVC
Xcode 10.2 ist verwirrt und gibt den FehlerIncorrect argument label in call (have 'meme:', expected 'coder:')
Eine Möglichkeit, dies zu tun, ist mit einem praktischen Initialisierer.
class MemeDetailVC : UIViewController { convenience init(meme: Meme) { self.init() self.meme = meme } }
Dann initialisieren Sie Ihre MemeDetailVC mit
let memeDetailVC = MemeDetailVC(theMeme)
Apples Dokumentation zu Initialisierern ist ziemlich gut, aber mein persönlicher Favorit ist die Tutorialserie Ray Wenderlich: Initialization in Depth , die Ihnen viele Erklärungen / Beispiele zu Ihren verschiedenen Init-Optionen und der "richtigen" Vorgehensweise geben sollte.
BEARBEITEN : Während Sie auf benutzerdefinierten Ansichts-Controllern einen praktischen Initialisierer verwenden können, gibt jeder zu Recht an, dass Sie beim Initialisieren vom Storyboard oder über einen Storyboard-Übergang keine benutzerdefinierten Initialisierer verwenden können.
Wenn Ihre Benutzeroberfläche im Storyboard eingerichtet ist und Sie den Controller vollständig programmgesteuert erstellen, ist ein praktischer Initialisierer wahrscheinlich der einfachste Weg, um das zu tun, was Sie versuchen, da Sie sich nicht mit dem erforderlichen Init befassen müssen der NSCoder (den ich immer noch nicht wirklich verstehe).
Wenn Sie Ihren View Controller jedoch über das Storyboard erhalten, müssen Sie der Antwort von @Caleb Kleveter folgen, den View Controller in die gewünschte Unterklasse umwandeln und die Eigenschaft manuell festlegen.
quelle
convenience
hier helfen?init
Funktion der Klasse initialisiert werden (Strukturen können dies jedoch). Um aufzurufenself.init()
, müssen Sie dasinit(meme: Meme)
alsconvenience
Initialisierer markieren . Andernfalls müssten Sie alle erforderlichen Eigenschaften von aUIViewController
im Initialisierer selbst manuell festlegen , und ich bin mir nicht sicher, was all diese Eigenschaften sind.MemeDetail()
In diesem Fall stürzt der Code abEs gab ursprünglich ein paar Antworten, die von der Kuh gewählt und gelöscht wurden, obwohl sie im Grunde richtig waren. Die Antwort ist, Sie können nicht.
Wenn Sie mit einer Storyboard-Definition arbeiten, werden alle Ihre View Controller-Instanzen archiviert. Um sie zu initiieren,
init?(coder...
muss sie verwendet werden. Dascoder
ist , wo alle Einstellungen / view Informationen stammen aus.In diesem Fall ist es also nicht möglich, eine andere Init-Funktion mit einem benutzerdefinierten Parameter aufzurufen. Es sollte entweder als Eigenschaft bei der Vorbereitung des Segues festgelegt werden, oder Sie können Segues loswerden und die Instanzen direkt aus dem Storyboard laden und konfigurieren (im Grunde ein Factory-Muster mit einem Storyboard).
In allen Fällen verwenden Sie die für das SDK erforderliche Init-Funktion und übergeben anschließend zusätzliche Parameter.
quelle
Swift 5
Sie können einen benutzerdefinierten Initialisierer wie folgt schreiben ->
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 } }
quelle
UIViewController
Klasse entspricht demNSCoding
Protokoll, das definiert ist als:public protocol NSCoding { public func encode(with aCoder: NSCoder) public init?(coder aDecoder: NSCoder) // NS_DESIGNATED_INITIALIZER }
So
UIViewController
hat zwei bezeichnete Initialisiererinit?(coder aDecoder: NSCoder)
undinit(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)
.Storyborad ruft
init?(coder aDecoder: NSCoder)
direkt bei init aufUIViewController
undUIView
, Es gibt keinen Platz für Sie, um Parameter zu übergeben.Eine umständliche Problemumgehung besteht darin, einen temporären Cache zu verwenden:
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 }
quelle
Ab iOS 13 können Sie den View Controller, der sich in einem Storyboard befindet, mit der
instantiateViewController(identifier:creator:)
Methode : für dieUIStoryboard
Instanz initialisieren .Tutorial: https://sarunw.com/posts/better-dependency-injection-for-storyboards-in-ios13/
quelle
Haftungsausschluss: Ich befürworte dies nicht und habe seine Widerstandsfähigkeit nicht gründlich getestet, aber es ist eine mögliche Lösung, die ich beim Herumspielen entdeckt habe.
Technisch gesehen kann eine benutzerdefinierte Initialisierung unter Beibehaltung der vom Storyboard konfigurierten Oberfläche erreicht werden, indem der Ansichts-Controller zweimal initialisiert wird: das erste Mal über Ihre benutzerdefinierte
init
und das zweite Mal,loadView()
wenn Sie die Ansicht vom Storyboard aus übernehmen.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) } }
Jetzt können Sie an anderer Stelle in Ihrer App Ihren benutzerdefinierten Initialisierer wie aufrufen
CustomViewController(someParameter: foo)
und trotzdem die Ansichtskonfiguration vom Storyboard erhalten.Ich halte dies aus mehreren Gründen nicht für eine großartige Lösung:
init
müssen als optionale Eigenschaften gespeichert werdenVielleicht können Sie diese Kompromisse akzeptieren, aber auf eigenes Risiko verwenden .
quelle
Obwohl wir jetzt benutzerdefinierte Init für die Standard-Controller im Storyboard verwenden können
instantiateInitialViewController(creator:)
und für Segmente einschließlich Beziehung und Show durchführen können.Diese Funktion wurde in Xcode 11 hinzugefügt. Das Folgende ist ein Auszug aus den Versionshinweisen zu Xcode 11 :
Eine mit dem neuen
@IBSegueAction
Attribut versehene View-Controller-Methode kann verwendet werden, um den Ziel-View-Controller eines Segues im Code mithilfe eines benutzerdefinierten Initialisierers mit den erforderlichen Werten zu erstellen. Dies ermöglicht die Verwendung von View-Controllern mit nicht optionalen Initialisierungsanforderungen in Storyboards. Erstellen Sie eine Verbindung von einem Übergang zu einer@IBSegueAction
Methode auf dem Quellansichts-Controller. In neuen Betriebssystemversionen, die Segue-Aktionen unterstützen, wird diese Methode aufgerufen und der zurückgegebene Wert ist derdestinationViewController
des übergebenen Segue-ObjektsprepareForSegue:sender:
.@IBSegueAction
Auf einem einzelnen Quellansichts-Controller können mehrere Methoden definiert werden, wodurch die Notwendigkeit verringert werden kann, Zeichenfolgen für die Segue-ID zu überprüfenprepareForSegue:sender:
. (47091566)Eine
IBSegueAction
Methode akzeptiert bis zu drei Parameter: einen Codierer, den Absender und die Kennung des Segues. Der erste Parameter ist erforderlich, und die anderen Parameter können bei Bedarf in der Signatur Ihrer Methode weggelassen werden. DasNSCoder
muss an den Initialisierer des Zielansichts-Controllers weitergeleitet werden, um sicherzustellen, dass es mit den im Storyboard konfigurierten Werten angepasst wird. Die Methode gibt einen Ansichtscontroller zurück, der dem im Storyboard definierten Zielcontrollertyp entspricht, oder veranlasstnil
, dass ein Zielcontroller mit der Standardmethode initialisiertinit(coder:)
wird. Wenn Sie wissen, dass Sie nicht zurückkehren müssen,nil
kann der Rückgabetyp nicht optional sein.Fügen Sie in Swift das
@IBSegueAction
Attribut hinzu:@IBSegueAction func makeDogController(coder: NSCoder, sender: Any?, segueIdentifier: String?) -> ViewController? { PetController( coder: coder, petName: self.selectedPetName, type: .dog ) }
Fügen Sie in Objective-C
IBSegueAction
vor dem Rückgabetyp Folgendes hinzu:- (IBSegueAction ViewController *)makeDogController:(NSCoder *)coder sender:(id)sender segueIdentifier:(NSString *)segueIdentifier { return [PetController initWithCoder:coder petName:self.selectedPetName type:@"dog"]; }
quelle
Der richtige Ablauf ist, rufen Sie den angegebenen Initialisierer auf, in diesem Fall den Init mit 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 }
quelle
// View Controller befindet sich in Main.storyboard und hat einen festgelegten Bezeichner
Klasse 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! }
Klasse a
let objB = customInit(carType:"Any String") navigationController?.pushViewController(objB,animated: true)
quelle