Wie erstelle ich einen benutzerdefinierten Initialisierer für eine UIViewController-Unterklasse in Swift?

92

Entschuldigung, wenn dies schon einmal gefragt wurde, habe ich viel gesucht und viele Antworten stammen von früheren Swift-Betas, als die Dinge anders waren. Ich kann anscheinend keine endgültige Antwort finden.

Ich möchte eine Unterklasse haben UIViewControllerund einen benutzerdefinierten Initialisierer haben, damit ich ihn einfach im Code einrichten kann. Ich habe Probleme damit in Swift.

Ich möchte eine init()Funktion, mit der ich eine bestimmte Funktion übergeben kann, die NSURLich dann mit dem View Controller verwenden werde. In meinen Augen sieht es so aus init(withImageURL: NSURL). Wenn ich diese Funktion hinzufüge, werde ich aufgefordert, die init(coder: NSCoder)Funktion hinzuzufügen .

Ich glaube, das liegt daran, dass es in der Oberklasse mit dem requiredSchlüsselwort markiert ist . Also muss ich es in der Unterklasse machen? Ich füge es hinzu:

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

Was jetzt? Wird mein spezieller Initialisierer als convenienceEins betrachtet? Ein ausgewiesener? Rufe ich einen Super-Initialisierer auf? Ein Initialisierer aus derselben Klasse?

Wie füge ich meinen speziellen Initialisierer einer UIViewControllerUnterklasse hinzu?

Doug Smith
quelle
Initialisierer sind gut für programmgesteuert erstellte viewController, aber für über das Storyboard erstellte Ansichtscontroller haben Sie kein Glück und müssen sich darum
Honey

Antworten:

156
class ViewController: UIViewController {

    var imageURL: NSURL?

    // this is a convenient way to create this view controller without a imageURL
    convenience init() {
        self.init(imageURL: nil)
    }

    init(imageURL: NSURL?) {
        self.imageURL = imageURL
        super.init(nibName: nil, bundle: nil)
    }

    // if this view controller is loaded from a storyboard, imageURL will be nil

    /* Xcode 6
    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    */

    // Xcode 7 & 8
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}
ylin0x81
quelle
Kann imageURL stattdessen eine Konstante sein? ("let imageURL: NSURL?)
Van Du Tran
2
funktioniert xCode 7 nicht, der erforderliche Konstruktor kann die Eigenschaft auf Klassenebene nicht initialisieren, in meinem Fall ein Int, nicht NSURL?
Chris Hayes
1
@NikolaLukic - Wir können durch den Aufruf eine neue Instanz der Klasse erstellt ViewController(), ViewController(imageURL: url)oder es aus dem Storyboard zu laden.
ylin0x81
3
Ich musste meine schreiben, da required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }sonst super.init(coder: aDecoder)der Fehler von Property self.somePropertybeim super.initAnruf nicht initialisiert wurde
Honey
1
Ich bin mir nicht sicher, wie das zusammenhängt. Wenn Sie reinen Code ausführen, müssen Sie Ihre Eigenschaften nicht in das Feld einfügen init(coder: aDecoder). Das fatalErrorwird ausreichen
Honey
24

Für diejenigen, die UI in Code schreiben

class Your_ViewController : UIViewController {

    let your_property : String

    init(your_property: String) {
        self.your_property = your_property
        super.init(nibName: nil, bundle: nil)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) is not supported")
    }
}
Sasan Soroush
quelle
12

Dies ist den anderen Antworten sehr ähnlich, jedoch mit einigen Erklärungen. Die akzeptierte Antwort ist irreführend, da ihre Eigenschaft optional ist und nicht die Tatsache init?(coder: NSCoder)aufdeckt , dass Sie jede einzelne Eigenschaft initialisieren MÜSSEN und die einzige Lösung dafür darin besteht, eine zu haben fatalError(). Letztendlich könnten Sie davonkommen, indem Sie Ihre Immobilien optional machen, aber das beantwortet die Frage des OP nicht wirklich.

// Think more of a OnlyNibOrProgrammatic_NOTStoryboardViewController
class ViewController: UIViewController {

    let name: String

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    // I don't have a nib. It's all through my code.
    init(name: String) {
        self.name = name

        super.init(nibName: nil, bundle: nil)
    }

    // I have a nib. I'd like to use my nib and also initialze the `name` property
    init(name: String, nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle? ) {
        self.name = name
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }

    // when you do storyboard.instantiateViewController(withIdentifier: "ViewController")
    // The SYSTEM will never call this!
    // it wants to call the required initializer!

    init?(name: String, coder aDecoder: NSCoder) {
        self.name = "name"
        super.init(coder: aDecoder)
    }

    // when you do storyboard.instantiateViewController(withIdentifier: "ViewController")
    // The SYSTEM WILL call this!
    // because this is its required initializer!
    // but what are you going to do for your `name` property?!
    // are you just going to do `self.name = "default Name" just to make it compile?!
    // Since you can't do anything then it's just best to leave it as `fatalError()`
    required init?(coder aDecoder: NSCoder) {
        fatalError("I WILL NEVER instantiate through storyboard! It's impossible to initialize super.init?(coder aDecoder: NSCoder) with any other parameter")
    }
}

Sie müssen es im Grunde verlassen, um es vom Storyboard zu laden. Warum?

Denn wenn man einen Viewcontroller ruft storyboard.instantiateViewController(withIdentifier: "viewController")dann UIKit wird seine Sache und Telefonieren

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

Sie können diesen Aufruf niemals an eine andere init-Methode umleiten.

Dokumente zu instantiateViewController(withIdentifier:):

Verwenden Sie diese Methode, um ein View Controller-Objekt zu erstellen, das programmgesteuert dargestellt werden soll. Jedes Mal, wenn Sie diese Methode aufrufen, wird mithilfe der init(coder:)Methode eine neue Instanz des Ansichtscontrollers erstellt .

Für programmgesteuert erstellte viewController- oder nib-erstellte viewController können Sie diesen Aufruf jedoch wie oben gezeigt umleiten.

Honig
quelle
5

Convenience-Initialisierer sind sekundär und unterstützen Initialisierer für eine Klasse. Sie können einen Convenience-Initialisierer definieren, um einen bestimmten Initialisierer aus derselben Klasse wie den Convenience-Initialisierer aufzurufen, wobei einige der Parameter des festgelegten Initialisierers auf Standardwerte gesetzt sind. Sie können auch einen praktischen Initialisierer definieren, um eine Instanz dieser Klasse für einen bestimmten Anwendungsfall oder Eingabewerttyp zu erstellen.

Sie sind hier dokumentiert .

Vatsal Manot
quelle
0

Wenn Sie beispielsweise eine benutzerdefinierte Init für ein Popover benötigen, können Sie den folgenden Ansatz verwenden:

Erstellen Sie eine benutzerdefinierte Init, die die Super-Init mit nibName und Bundle verwendet, und greifen Sie anschließend auf die view-Eigenschaft zu, um das Laden der View-Hierarchie zu erzwingen.

Anschließend können Sie in der Funktion viewDidLoad die Ansichten mit den bei der Initialisierung übergebenen Parametern konfigurieren.

import UIKit

struct Player {
    let name: String
    let age: Int
}

class VC: UIViewController {


@IBOutlet weak var playerName: UILabel!

let player: Player

init(player: Player) {
    self.player = player
    super.init(nibName: "VC", bundle: Bundle.main)
    if let view = view, view.isHidden {}
}

override func viewDidLoad() {
    super.viewDidLoad()
    configure()
}

func configure() {
    playerName.text = player.name + "\(player.age)"
}
}

func showPlayerVC() {
    let foo = Player(name: "bar", age: 666)
    let vc = VC(player: foo)
    present(vc, animated: true, completion: nil)
}
rockdaswift
quelle