Comment créer un initialiseur personnalisé pour une sous-classe UIViewController dans Swift?

93

Toutes mes excuses si cela a été demandé auparavant, j'ai beaucoup cherché et de nombreuses réponses proviennent de versions bêta précédentes de Swift lorsque les choses étaient différentes. Je n'arrive pas à trouver une réponse définitive.

Je veux sous UIViewController- classer et avoir un initialiseur personnalisé pour me permettre de le configurer facilement dans le code. J'ai du mal à faire ça dans Swift.

Je veux une init()fonction que je peux utiliser pour passer une fonction spécifique que NSURLje vais ensuite utiliser avec le contrôleur de vue. Dans mon esprit, cela ressemble à quelque chose init(withImageURL: NSURL). Si j'ajoute cette fonction, il me demande alors d'ajouter la init(coder: NSCoder)fonction.

Je crois que c'est parce qu'il est marqué dans la superclasse avec le requiredmot-clé? Alors je dois le faire dans la sous-classe? Je l'ajoute:

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

Maintenant quoi? Mon initialiseur spécial est-il considéré comme un convenienceun? Un désigné? Dois-je appeler un super initialiseur? Un initialiseur de la même classe?

Comment ajouter mon initialiseur spécial à une UIViewControllersous-classe?

Doug Smith
la source
Les initialiseurs sont bons pour les viewControllers créés par programmation, mais pour les viewcontrollers créés via le storyboard, vous n'avez pas de chance et devez
Chérie

Réponses:

157
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
la source
imageURL peut-il être une constante à la place? ("let imageURL: NSURL?)
Van Du Tran
2
ne fonctionne pas xCode 7, le constructeur requis ne parvient pas à initialiser la propriété au niveau de la classe, dans mon cas un Int, pas NSURL?
Chris Hayes
1
@NikolaLukic - Nous pouvons crée une nouvelle instance de la classe en appelant ViewController(), ViewController(imageURL: url)ou de le charger d'un story - board.
ylin0x81
3
J'ai dû écrire le mien car required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }sinon, super.init(coder: aDecoder)j'obtenais l'erreur de propriété self.somePropertynon initialisée à l' super.initappel
Honey
1
Je ne sais pas comment cela est lié. Si vous utilisez du code pur, vous n'avez pas besoin de renseigner vos propriétés dans le fichier init(coder: aDecoder). La fatalErrorvolonté suffira
Honey
26

Pour ceux qui écrivent l'interface utilisateur dans le code

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")
    }
}
Ange Sasan
la source
12

Ceci est très similaire aux autres réponses, mais avec quelques explications. La réponse acceptée est trompeuse car sa propriété est facultative et n'expose pas le fait que votre init?(coder: NSCoder)DOIT initialiser chaque propriété et la seule solution à cela est d'avoir un fatalError(). En fin de compte, vous pourriez vous en sortir en rendant vos propriétés optionnelles, mais cela ne répond pas vraiment à la question du PO.

// 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")
    }
}

Vous devez essentiellement ABANDON le charger à partir du storyboard. Pourquoi?

Parce que quand vous appelez un viewController storyboard.instantiateViewController(withIdentifier: "viewController")alors UIKit fera sa chose et appel

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

Vous ne pouvez jamais rediriger cet appel vers une autre méthode init.

Documents sur instantiateViewController(withIdentifier:):

Utilisez cette méthode pour créer un objet contrôleur de vue à présenter par programme. Chaque fois que vous appelez cette méthode, elle crée une nouvelle instance du contrôleur de vue à l'aide de la init(coder:)méthode.

Pourtant, pour les viewController créés par programme ou les viewControllers créés par nib, vous pouvez rediriger cet appel comme indiqué ci-dessus.

Mon chéri
la source
5

Les initialiseurs de commodité sont secondaires et prennent en charge les initialiseurs d'une classe. Vous pouvez définir un initialiseur de commodité pour appeler un initialiseur désigné de la même classe que l'initialiseur de commodité avec certains des paramètres de l'initialiseur désigné définis sur les valeurs par défaut. Vous pouvez également définir un initialiseur pratique pour créer une instance de cette classe pour un cas d'utilisation ou un type de valeur d'entrée spécifique.

Ils sont documentés ici .

Vatsal Manot
la source
0

Si vous avez besoin d'un init personnalisé pour un popover par exemple, vous pouvez utiliser l'approche suivante:

Créez un init personnalisé qui utilise le super init avec nibName et bundle, puis accédez à la propriété view pour forcer le chargement de la hiérarchie de vues.

Ensuite, dans la fonction viewDidLoad, vous pouvez configurer les vues avec les paramètres passés lors de l'initialisation.

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
la source