Como faço um inicializador personalizado para uma subclasse UIViewController em Swift?

93

Desculpas se isso foi perguntado antes, eu pesquisei muito e muitas respostas são de versões beta do Swift anteriores, quando as coisas eram diferentes. Não consigo encontrar uma resposta definitiva.

Quero criar uma subclasse UIViewControllere ter um inicializador personalizado que me permita configurá-lo no código facilmente. Estou tendo problemas para fazer isso no Swift.

Quero uma init()função que possa usar para passar um específico NSURLque irei usar com o controlador de visualização. Em minha mente, parece algo assim init(withImageURL: NSURL). Se eu adicionar essa função, ele me pedirá para adicionar a init(coder: NSCoder)função.

Acredito que isso seja porque está marcado na superclasse com a requiredpalavra - chave? Então eu tenho que fazer isso na subclasse? Eu adiciono:

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

O que agora? Meu inicializador especial é considerado convenienceum? Um designado? Devo chamar um superinicializador? Um inicializador da mesma classe?

Como adiciono meu inicializador especial a uma UIViewControllersubclasse?

Doug Smith
fonte
Inicializadores são bons para viewControllers criados programaticamente, mas para viewcontrollers criados por meio de storyboard você está sem sorte e tem que trabalhar do seu jeito
Honey

Respostas:

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
fonte
pode imageURL ser uma constante? ("let imageURL: NSURL?)
Van Du Tran
2
não está funcionando o xCode 7, o construtor necessário não consegue inicializar a propriedade de nível de classe, no meu caso um Int, não NSURL?
Chris Hayes
1
@NikolaLukic - Podemos cria uma nova instância da classe chamando ViewController(), ViewController(imageURL: url)ou carregá-lo a partir de um storyboard.
ylin0x81
3
Tive que escrever o meu caso required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }contrário super.init(coder: aDecoder), estava recebendo o erro de Propriedade self.somePropertynão inicializada na super.initchamada
Honey
1
Não tenho certeza de como isso está relacionado. Se você estiver fazendo código puro, não precisa preencher suas propriedades dentro do init(coder: aDecoder). O fatalErrorbastam vontade
Mel
26

Para aqueles que escrevem UI em código

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 Angel
fonte
12

Isso é muito semelhante às outras respostas, mas com alguma explicação. A resposta aceita é enganosa porque sua propriedade é opcional e não expõe o fato de que você init?(coder: NSCoder)DEVE inicializar cada propriedade e a única solução para isso é ter um fatalError(). No final das contas, você poderia escapar tornando suas propriedades opcionais, mas isso não responde verdadeiramente à pergunta do OP.

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

Você basicamente tem que ABANDONAR o carregamento do storyboard. Por quê?

Porque quando você chama um viewController, storyboard.instantiateViewController(withIdentifier: "viewController")então o UIKit fará seu trabalho e chamará

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

Você nunca pode redirecionar essa chamada para outro método init.

Docs em instantiateViewController(withIdentifier:):

Use este método para criar um objeto de controlador de visualização para apresentar programaticamente. Cada vez que você chama este método, ele cria uma nova instância do controlador de visualização usando o init(coder:)método.

No entanto, para viewController criado programaticamente ou viewControllers criados com nib, você pode redirecionar essa chamada conforme mostrado acima.

Mel
fonte
5

Os inicializadores de conveniência são secundários, suportando inicializadores para uma classe. Você pode definir um inicializador de conveniência para chamar um inicializador designado da mesma classe que o inicializador de conveniência com alguns dos parâmetros do inicializador designado definidos para valores padrão. Você também pode definir um inicializador de conveniência para criar uma instância dessa classe para um caso de uso específico ou tipo de valor de entrada.

Eles estão documentados aqui .

Vatsal Manot
fonte
0

Se precisar de um init personalizado para um popover, por exemplo, você pode usar a seguinte abordagem:

Crie um init customizado que usa o super init com nibName e bundle e depois acesse a propriedade de visualização para forçar a carga da hierarquia de visualização.

Então, na função viewDidLoad você pode configurar as visualizações com os parâmetros passados ​​na inicialização.

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
fonte