¿Cómo hago un inicializador personalizado para una subclase UIViewController en Swift?

95

Disculpas si esto se ha preguntado antes, he buscado mucho y muchas respuestas son de versiones beta de Swift anteriores cuando las cosas eran diferentes. Parece que no puedo encontrar una respuesta definitiva.

Quiero UIViewControllercrear una subclase y tener un inicializador personalizado que me permita configurarlo en código fácilmente. Tengo problemas para hacer esto en Swift.

Quiero una init()función que pueda usar para pasar un específico NSURLque luego usaré con el controlador de vista. En mi mente se parece a algo así init(withImageURL: NSURL). Si agrego esa función, me pide que agregue la init(coder: NSCoder)función.

Creo que esto se debe a que está marcado en la superclase con la requiredpalabra clave? ¿Entonces tengo que hacerlo en la subclase? Lo agrego:

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

¿Ahora que? ¿Mi inicializador especial se considera convenienceuno? ¿Uno designado? ¿Llamo a un superinicializador? ¿Un inicializador de la misma clase?

¿Cómo agrego mi inicializador especial a una UIViewControllersubclase?

Doug Smith
fuente
Los inicializadores son buenos para los viewControllers creados mediante programación, pero para los viewcontrollers creados a través del guión gráfico, no tiene suerte y tiene que trabajar
Cariño

Respuestas:

161
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
fuente
¿imageURL puede ser una constante en su lugar? ("let imageURL: NSURL?)
Van Du Tran
2
no funciona xCode 7, el constructor requerido no puede inicializar la propiedad de nivel de clase, en mi caso, ¿un Int, no NSURL?
Chris Hayes
1
@NikolaLukic - Podemos crea una nueva instancia de la clase llamando ViewController(), ViewController(imageURL: url)o cargarlo de un guión.
ylin0x81
3
Tuve que escribir el mío, de lo required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }contrario super.init(coder: aDecoder), recibía el error de Propiedad self.somePropertyno inicializada en la super.initllamada
cariño,
1
No estoy seguro de cómo se relaciona eso. Si está haciendo código puro, entonces no necesita completar sus propiedades dentro de init(coder: aDecoder). El fatalErrortestamento será suficiente
Cariño
26

Para aquellos que escriben UI en 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")
    }
}
Ángel sasan
fuente
12

Esto es muy similar a las otras respuestas, pero con alguna explicación. La respuesta aceptada es engañosa porque su propiedad es opcional y no expone el hecho de que init?(coder: NSCoder)DEBE inicializar todas y cada una de las propiedades y la única solución es tener un fatalError(). En última instancia, podría salirse con la suya haciendo que sus propiedades sean opcionales, pero eso no responde realmente a la pregunta del 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")
    }
}

Básicamente tienes que ABANDONAR cargarlo desde el guión gráfico. ¿Por qué?

Porque cuando llamas a un viewController storyboard.instantiateViewController(withIdentifier: "viewController"), UIKit hará lo suyo y llamará

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

Nunca puede redirigir esa llamada a otro método de inicio.

Documentos sobre instantiateViewController(withIdentifier:):

Utilice este método para crear un objeto de controlador de vista para presentar mediante programación. Cada vez que llama a este método, crea una nueva instancia del controlador de vista utilizando el init(coder:)método.

Sin embargo, para viewController creado mediante programación o viewControllers creado con nib, puede redirigir esa llamada como se muestra arriba.

Miel
fuente
5

Los inicializadores de conveniencia son secundarios y admiten inicializadores para una clase. Puede definir un inicializador de conveniencia para llamar a un inicializador designado de la misma clase que el inicializador de conveniencia con algunos de los parámetros del inicializador designado establecidos en valores predeterminados. También puede definir un inicializador de conveniencia para crear una instancia de esa clase para un caso de uso específico o un tipo de valor de entrada.

Están documentados aquí .

Vatsal Manot
fuente
0

Si necesita un inicio personalizado para un popover, por ejemplo, puede usar el siguiente enfoque:

Cree un init personalizado que use el super init con nibName y bundle y luego acceda a la propiedad de la vista para forzar la carga de la jerarquía de vistas.

Luego, en la función viewDidLoad puede configurar las vistas con los parámetros pasados ​​en la inicialización.

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
fuente