Tengo problemas para escribir init personalizado para la subclase de UIViewController, básicamente quiero pasar la dependencia a través del método init para viewController en lugar de configurar la propiedad directamente como viewControllerB.property = value
Así que hice un init personalizado para mi viewController y llamé a init super designado
init(meme: Meme?) {
self.meme = meme
super.init(nibName: nil, bundle: nil)
}
La interfaz del controlador de vista reside en el guión gráfico, también hice la interfaz para que la clase personalizada sea mi controlador de vista. Y Swift requiere llamar a este método init incluso si no está haciendo nada dentro de este método. De lo contrario, el compilador se quejará ...
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
El problema es que cuando intento llamar a mi init personalizado MyViewController(meme: meme)
, no inicia propiedades en mi viewController en absoluto ...
Estaba tratando de depurar, encontré en mi viewController, init(coder aDecoder: NSCoder)
me llaman primero, luego se llama a mi init personalizado más tarde. Sin embargo, estos dos métodos init devuelven self
direcciones de memoria diferentes .
Sospecho que algo anda mal con init para mi viewController, y siempre regresará self
con init?(coder aDecoder: NSCoder)
, que no tiene implementación.
¿Alguien sabe cómo hacer un init personalizado para su viewController correctamente? Nota: la interfaz de mi viewController está configurada en el guión gráfico
aquí está mi código viewController:
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)
}
}
fuente
Respuestas:
Como se especificó en una de las respuestas anteriores, no puede usar el método de inicio personalizado y el guión gráfico. Pero aún puede usar un método estático para crear una instancia de ViewController desde un guión gráfico y realizar una configuración adicional en él. Se verá así:
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 } }
No olvide especificar IdentifierOfYouViewController como identificador del controlador de vista en su guión gráfico. También es posible que deba cambiar el nombre del guión gráfico en el código anterior.
fuente
No puede usar un inicializador personalizado cuando inicializa desde un Storyboard, así
init?(coder aDecoder: NSCoder)
es como Apple diseñó el storyboard para inicializar un controlador. Sin embargo, hay formas de enviar datos a un archivoUIViewController
.El nombre de su controlador de vista tiene
detail
en él, así que supongo que llega desde un controlador diferente. En este caso, puede usar elprepareForSegue
método para enviar datos al detalle (esto es Swift 3):override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "identifier" { if let controller = segue.destinationViewController as? MemeDetailVC { controller.meme = "Meme" } } }
Solo usé una propiedad de tipo en
String
lugar deMeme
con fines de prueba. Además, asegúrese de pasar el identificador de segue correcto ("identifier"
era solo un marcador de posición).fuente
Como ha señalado @Caleb Kleveter, no podemos usar un inicializador personalizado al inicializar desde un Storyboard.
Pero, podemos resolver el problema utilizando el método de fábrica / clase que instancia el objeto del controlador de vista desde Storyboard y devuelve el objeto del controlador de vista. Creo que esta es una forma genial.
Nota: Esta no es una respuesta exacta a la pregunta, sino una solución para resolver el problema.
Haga el método de clase, en la clase MemeDetailVC, de la siguiente manera:
// 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 }
Uso:
let memeDetailVC = MemeDetailVC.init(meme: Meme())
fuente
class func
init(meme: Meme) -> MemeDetailVC
Xcode 10.2 se confunde y da el errorIncorrect argument label in call (have 'meme:', expected 'coder:')
Una forma de hacerlo es con un inicializador de conveniencia.
class MemeDetailVC : UIViewController { convenience init(meme: Meme) { self.init() self.meme = meme } }
Luego inicializa su MemeDetailVC con
let memeDetailVC = MemeDetailVC(theMeme)
La documentación de Apple sobre los inicializadores es bastante buena, pero mi favorito personal es la serie de tutoriales Ray Wenderlich: Initialization in Depth , que debería darte muchas explicaciones / ejemplos sobre tus diversas opciones de inicio y la forma "correcta" de hacer las cosas.
EDITAR : Si bien puede usar un inicializador de conveniencia en los controladores de vista personalizados, todos tienen razón al afirmar que no puede usar inicializadores personalizados al inicializar desde el guión gráfico o mediante una secuencia de guión gráfico.
Si su interfaz está configurada en el guión gráfico y está creando el controlador de forma completamente programática, entonces un inicializador de conveniencia es probablemente la forma más fácil de hacer lo que está tratando de hacer, ya que no tiene que lidiar con el init requerido con el NSCoder (que todavía no entiendo realmente).
Sin embargo, si obtiene su controlador de vista a través del guión gráfico, deberá seguir la respuesta de @Caleb Kleveter y convertir el controlador de vista en la subclase deseada y luego configurar la propiedad manualmente.
fuente
convenience
is aquí?init
función de la clase (aunque las estructuras sí). Entonces, para llamarself.init()
, debe marcarinit(meme: Meme)
comoconvenience
inicializador. De lo contrario, tendría que configurar manualmente todas las propiedades requeridas de aUIViewController
en el inicializador usted mismo, y no estoy seguro de cuáles son todas esas propiedades.MemeDetail()
y en ese caso el código fallaráOriginalmente había un par de respuestas, que fueron votadas y eliminadas aunque eran básicamente correctas. La respuesta es que no puedes.
Cuando se trabaja desde una definición de guión gráfico, todas las instancias del controlador de vista se archivan. Entonces, para iniciarlos se requiere que
init?(coder...
se use. Elcoder
es donde toda la información de los ajustes / vista viene.Entonces, en este caso, no es posible llamar también a otra función de inicio con un parámetro personalizado. Debe establecerse como una propiedad al preparar la segue, o puede deshacerse de las segues y cargar las instancias directamente desde el guión gráfico y configurarlas (básicamente un patrón de fábrica usando un guión gráfico).
En todos los casos, utiliza la función de inicio requerida del SDK y luego pasa parámetros adicionales.
fuente
Rápido 5
Puede escribir un inicializador personalizado como este ->
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 } }
fuente
UIViewController
la clase se ajusta alNSCoding
protocolo que se define como:public protocol NSCoding { public func encode(with aCoder: NSCoder) public init?(coder aDecoder: NSCoder) // NS_DESIGNATED_INITIALIZER }
Así que
UIViewController
tiene dos inicializador designadoinit?(coder aDecoder: NSCoder)
yinit(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)
.Storyborad llama
init?(coder aDecoder: NSCoder)
directamente a initUIViewController
yUIView
no hay espacio para que usted pase parámetros.Una solución complicada es utilizar un caché temporal:
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 }
fuente
A partir de iOS 13, puede inicializar el controlador de vista que reside en un guión gráfico usando:
instantiateViewController(identifier:creator:)
método en laUIStoryboard
instancia.tutorial: https://sarunw.com/posts/better-dependency-injection-for-storyboards-in-ios13/
fuente
Descargo de responsabilidad: no abogo por esto y no he probado a fondo su resistencia, pero es una solución potencial que descubrí mientras jugaba.
Técnicamente, la inicialización personalizada se puede lograr mientras se conserva la interfaz configurada del guión gráfico inicializando el controlador de vista dos veces: la primera vez a través de su personalizado
init
y la segunda vez dentro deloadView()
donde toma la vista del guión gráfico.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) } }
Ahora, en otra parte de su aplicación, puede llamar a su inicializador personalizado como
CustomViewController(someParameter: foo)
y aún recibir la configuración de vista del guión gráfico.No considero que esta sea una gran solución por varias razones:
init
deben almacenarse como propiedades opcionalesQuizás pueda aceptar estas compensaciones, pero úselas bajo su propio riesgo .
fuente
Aunque ahora podemos hacer una inicialización personalizada para los controladores predeterminados en el guión gráfico usando
instantiateInitialViewController(creator:)
y para segues, incluida la relación y el espectáculo.Esta capacidad se agregó en Xcode 11 y el siguiente es un extracto de las Notas de la versión de Xcode 11 :
Se
@IBSegueAction
puede usar un método de controlador de vista anotado con el nuevo atributo para crear un controlador de vista de destino de segue en el código, usando un inicializador personalizado con los valores requeridos. Esto hace posible utilizar controladores de vista con requisitos de inicialización no opcionales en los guiones gráficos. Cree una conexión desde un segue a un@IBSegueAction
método en su controlador de vista de origen. En las nuevas versiones del sistema operativo que admiten Segue Actions, se llamará a ese método y el valor que devuelve será eldestinationViewController
del objeto segue al que se pasaprepareForSegue:sender:
. Se@IBSegueAction
pueden definir múltiples métodos en un solo controlador de vista de fuente, lo que puede aliviar la necesidad de verificar las cadenas de identificadores de segue enprepareForSegue:sender:
. (47091566)Un
IBSegueAction
método toma hasta tres parámetros: un codificador, el remitente y el identificador del segue. El primer parámetro es obligatorio y los demás parámetros se pueden omitir de la firma de su método si lo desea. SeNSCoder
debe pasar al inicializador del controlador de la vista de destino para garantizar que esté personalizado con los valores configurados en el guión gráfico. El método devuelve un controlador de vista que coincide con el tipo de controlador de destino definido en el guión gráfico, onil
para hacer que un controlador de destino se inicialice con elinit(coder:)
método estándar . Si sabe que no necesita regresarnil
, el tipo de devolución puede ser no opcional.En Swift, agregue el
@IBSegueAction
atributo:@IBSegueAction func makeDogController(coder: NSCoder, sender: Any?, segueIdentifier: String?) -> ViewController? { PetController( coder: coder, petName: self.selectedPetName, type: .dog ) }
En Objective-C, agregue
IBSegueAction
delante del tipo de retorno:- (IBSegueAction ViewController *)makeDogController:(NSCoder *)coder sender:(id)sender segueIdentifier:(NSString *)segueIdentifier { return [PetController initWithCoder:coder petName:self.selectedPetName type:@"dog"]; }
fuente
El flujo correcto es llamar al inicializador designado que en este caso es el init con 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 }
fuente
// El controlador de vista está en Main.storyboard y tiene un identificador establecido
Clase 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! }
Clase A
let objB = customInit(carType:"Any String") navigationController?.pushViewController(objB,animated: true)
fuente