Estou tendo problemas para escrever init personalizado para a subclasse de UIViewController, basicamente, quero passar a dependência por meio do método init para viewController em vez de definir a propriedade diretamente como viewControllerB.property = value
Então fiz um init personalizado para meu viewController e chamei o init super designado
init(meme: Meme?) {
self.meme = meme
super.init(nibName: nil, bundle: nil)
}
A interface do controlador de visualização reside no storyboard, eu também fiz a interface da classe customizada para ser meu controlador de visualização. E o Swift requer a chamada deste método init mesmo se você não estiver fazendo nada dentro desse método. Caso contrário, o compilador irá reclamar ...
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
O problema é que quando tento chamar o meu init personalizado, MyViewController(meme: meme)
ele não faz as propriedades do init no meu viewController ...
Eu estava tentando depurar, encontrei em meu viewController, fui init(coder aDecoder: NSCoder)
chamado primeiro e, em seguida, meu init personalizado foi chamado mais tarde. No entanto, esses dois métodos init retornam self
endereços de memória diferentes .
Estou suspeitando de algo errado com o init do meu viewController e ele sempre retornará self
com o init?(coder aDecoder: NSCoder)
, que não tem implementação.
Alguém sabe como fazer init personalizado para seu viewController corretamente? Nota: a interface do meu viewController é configurada no storyboard
aqui está meu 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)
}
}
Respostas:
Como foi especificado em uma das respostas acima, você não pode usar o método init personalizado e o storyboard.
Mas você ainda pode usar um método estático para instanciar a
ViewController
partir de um storyboard e realizar configurações adicionais nele.Isso parecerá assim:
class MemeDetailVC : UIViewController { var meme : Meme! static func makeMemeDetailVC(meme: Meme) -> MemeDetailVC { let newViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "IdentifierOfYouViewController") as! MemeDetailVC newViewController.meme = meme return newViewController } }
Não se esqueça de especificar IdentifierOfYouViewController como identificador do controlador de visualização em seu storyboard. Você também pode precisar alterar o nome do storyboard no código acima.
fonte
Você não pode usar um inicializador personalizado ao inicializar a partir de um Storyboard;
init?(coder aDecoder: NSCoder)
foi assim que a Apple projetou o storyboard para inicializar um controlador. No entanto, existem maneiras de enviar dados para aUIViewController
.O nome do seu controlador de visualização está
detail
nele, então suponho que você o tenha obtido de um controlador diferente. Neste caso, você pode usar oprepareForSegue
método para enviar dados para o detalhe (este é o Swift 3):override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "identifier" { if let controller = segue.destinationViewController as? MemeDetailVC { controller.meme = "Meme" } } }
Acabei de usar uma propriedade do tipo em
String
vez deMeme
para fins de teste. Além disso, certifique-se de passar o identificador de segue correto ("identifier"
era apenas um marcador de posição).fonte
Como @Caleb Kleveter apontou, não podemos usar um inicializador personalizado ao inicializar a partir de um Storyboard.
Mas, podemos resolver o problema usando o método de fábrica / classe que instancia o objeto do controlador de visão do Storyboard e retorna o objeto do controlador de visão. Eu acho que essa é uma maneira bem legal.
Nota: Esta não é uma resposta exata à pergunta, mas uma solução alternativa para resolver o problema.
Faça o método da classe, na classe MemeDetailVC, da seguinte maneira:
// 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())
fonte
class func
init(meme: Meme) -> MemeDetailVC
Xcode 10.2 é confuso e apresenta o erroIncorrect argument label in call (have 'meme:', expected 'coder:')
Uma maneira de fazer isso é com um inicializador de conveniência.
class MemeDetailVC : UIViewController { convenience init(meme: Meme) { self.init() self.meme = meme } }
Então você inicializa seu MemeDetailVC com
let memeDetailVC = MemeDetailVC(theMeme)
A documentação da Apple sobre inicializadores é muito boa, mas meu favorito pessoal é a série de tutoriais Ray Wenderlich: Initialization in Depth , que deve fornecer muitas explicações / exemplos sobre suas várias opções de init e a maneira "correta" de fazer as coisas.
EDITAR : Embora você possa usar um inicializador de conveniência em controladores de exibição personalizados, todos estão corretos ao afirmar que você não pode usar inicializadores personalizados ao inicializar a partir do storyboard ou por meio de um segue de storyboard.
Se sua interface estiver configurada no storyboard e você estiver criando o controlador completamente programaticamente, um inicializador de conveniência é provavelmente a maneira mais fácil de fazer o que você está tentando fazer, já que não precisa lidar com o init requerido o NSCoder (que ainda não entendi direito).
Porém, se você estiver obtendo seu controlador de visualização por meio do storyboard, precisará seguir a resposta de @Caleb Kleveter e lançar o controlador de visualização em sua subclasse desejada e definir a propriedade manualmente.
fonte
convenience
is ajuda aqui.init
função da classe (embora as estruturas possam). Portanto, para chamarself.init()
, você deve marcar oinit(meme: Meme)
como umconvenience
inicializador. Caso contrário, você teria que definir manualmente todas as propriedades necessárias de aUIViewController
no inicializador, e não tenho certeza de quais são todas essas propriedades.MemeDetail()
e, nesse caso, o código irá travarHavia originalmente algumas respostas, que foram votadas e excluídas, embora estivessem basicamente corretas. A resposta é: você não pode.
Ao trabalhar a partir de uma definição de storyboard, suas instâncias do controlador de visualização são todas arquivadas. Então, para iniciá-los é necessário que
init?(coder...
seja usado. Ocoder
é o lugar onde todas as informações de definições / fotografia vem.Portanto, neste caso, não é possível chamar também alguma outra função init com um parâmetro personalizado. Ele deve ser definido como uma propriedade ao preparar a segue, ou você pode abandonar segues e carregar as instâncias diretamente do storyboard e configurá-las (basicamente um padrão de fábrica usando um storyboard).
Em todos os casos, você usa a função init necessária do SDK e passa parâmetros adicionais posteriormente.
fonte
Swift 5
Você pode escrever um 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 } }
fonte
UIViewController
classe em conformidade com oNSCoding
protocolo que é definido como:public protocol NSCoding { public func encode(with aCoder: NSCoder) public init?(coder aDecoder: NSCoder) // NS_DESIGNATED_INITIALIZER }
Então
UIViewController
tem dois inicializadores designadosinit?(coder aDecoder: NSCoder)
einit(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)
.O Storyborad chama
init?(coder aDecoder: NSCoder)
diretamente para o initUIViewController
eUIView
, não há espaço para você passar parâmetros.Uma solução alternativa complicada é usar um cache temporário:
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 }
fonte
A partir do iOS 13, você pode inicializar o controlador de visualização que reside em um storyboard usando o
instantiateViewController(identifier:creator:)
método : naUIStoryboard
instância.tutorial: https://sarunw.com/posts/better-dependency-injection-for-storyboards-in-ios13/
fonte
O fluxo correto é chamar o inicializador designado que, neste caso, é o init com 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 }
fonte
Isenção de responsabilidade: eu não defendo isso e não testei completamente sua resiliência, mas é uma solução potencial que descobri brincando.
Tecnicamente, a inicialização personalizada pode ser alcançada preservando a interface configurada do storyboard inicializando o controlador de visualização duas vezes: a primeira vez por meio de seu customizado
init
e a segunda vez dentro deloadView()
onde você obtém a visualização do storyboard.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) } }
Agora, em outro lugar em seu aplicativo, você pode chamar seu inicializador personalizado como
CustomViewController(someParameter: foo)
e ainda receber a configuração de exibição do storyboard.Não considero esta uma ótima solução por vários motivos:
init
devem ser armazenados como propriedades opcionaisTalvez você possa aceitar essas compensações, mas use por sua própria conta e risco .
fonte
Embora agora possamos fazer init customizado para os controladores padrão no storyboard usando
instantiateInitialViewController(creator:)
e para segues incluindo relacionamento e show.Esse recurso foi adicionado ao Xcode 11 e o seguinte é um trecho das Notas de versão do Xcode 11 :
Um método de controlador de visualização anotado com o novo
@IBSegueAction
atributo pode ser usado para criar um controlador de visualização de destino segue no código, usando um inicializador personalizado com quaisquer valores necessários. Isso torna possível usar controladores de visualização com requisitos de inicialização não opcionais em storyboards. Crie uma conexão de segue para um@IBSegueAction
método em seu controlador de exibição de origem. Em novas versões do sistema operacional que suportam Segue Actions, esse método será chamado e o valor que ele retornará será odestinationViewController
do objeto segue passadoprepareForSegue:sender:
. Vários@IBSegueAction
métodos podem ser definidos em um único controlador de exibição de origem, o que pode aliviar a necessidade de verificar as strings de identificador de segueprepareForSegue:sender:
. (47091566)Um
IBSegueAction
método leva até três parâmetros: um codificador, o remetente e o identificador da segue. O primeiro parâmetro é obrigatório e os outros parâmetros podem ser omitidos da assinatura do seu método, se desejado. ONSCoder
deve ser passado para o inicializador do controlador de visualização de destino, para garantir que seja personalizado com os valores configurados no storyboard. O método retorna um controlador de visualização que corresponde ao tipo de controlador de destino definido no storyboard ounil
para fazer com que um controlador de destino seja inicializado com oinit(coder:)
método padrão . Se você sabe que não precisa retornarnil
, o tipo de retorno pode ser não opcional.Em Swift, adicione o
@IBSegueAction
atributo:@IBSegueAction func makeDogController(coder: NSCoder, sender: Any?, segueIdentifier: String?) -> ViewController? { PetController( coder: coder, petName: self.selectedPetName, type: .dog ) }
Em Objective-C, adicione
IBSegueAction
na frente do tipo de retorno:- (IBSegueAction ViewController *)makeDogController:(NSCoder *)coder sender:(id)sender segueIdentifier:(NSString *)segueIdentifier { return [PetController initWithCoder:coder petName:self.selectedPetName type:@"dog"]; }
fonte
// O controlador de visualização está em Main.storyboard e possui um identificador definido
Classe 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! }
Classe A
let objB = customInit(carType:"Any String") navigationController?.pushViewController(objB,animated: true)
fonte