Je veux sous-classer UITableViewController et pouvoir l'instancier en appelant un initialiseur par défaut sans argument.
class TestViewController: UITableViewController {
convenience init() {
self.init(style: UITableViewStyle.Plain)
}
}
Depuis le Xcode 6 Beta 5, l'exemple ci-dessus ne fonctionne plus.
Overriding declaration requires an 'override' keyword
Invalid redeclaration of 'init()'
NOTE Ce bogue est corrigé dans iOS 9, donc tout le problème sera discuté à ce stade. La discussion ci-dessous ne s'applique qu'au système et à la version particuliers de Swift auxquels il est explicitement adapté.
Il s'agit clairement d'un bug, mais il existe également une solution très simple. Je vais expliquer le problème et ensuite donner la solution. Veuillez noter que j'écris ceci pour Xcode 6.3.2 et Swift 1.2; Apple a été partout sur la carte depuis le jour Swift est sorti en premier, donc les autres versions se comporteront différemment.
Vous allez instancier UITableViewController à la main (c'est-à-dire en appelant son initialiseur dans le code). Et vous voulez sous-classer UITableViewController car vous avez des propriétés d'instance que vous souhaitez lui donner.
Donc, vous commencez avec une propriété d'instance:
class MyTableViewController: UITableViewController {
let greeting : String
}
Cela n'a pas de valeur par défaut, vous devez donc écrire un initialiseur:
class MyTableViewController: UITableViewController {
let greeting : String
init(greeting:String) {
self.greeting = greeting
}
}
Mais ce n'est pas un initialiseur légal - vous devez appeler super
. Supposons que votre appel à super
consiste à appeler init(style:)
.
class MyTableViewController: UITableViewController {
let greeting : String
init(greeting:String) {
self.greeting = greeting
super.init(style: .Plain)
}
}
Mais vous ne pouvez toujours pas compiler, car vous devez implémenter init(coder:)
. Vous faites donc:
class MyTableViewController: UITableViewController {
let greeting : String
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init(greeting:String) {
self.greeting = greeting
super.init(style: .Plain)
}
}
Votre code se compile maintenant! Vous êtes maintenant heureux (vous pensez) d'instancier cette sous-classe de contrôleur de vue de table en appelant l'initialiseur que vous avez écrit:
let tvc = MyTableViewController(greeting:"Hello there")
Tout semble joyeux et rose jusqu'à ce que vous exécutiez l'application, à quel point vous plantez avec ce message:
erreur fatale: utilisation d'un initialiseur non implémenté
init(nibName:bundle:)
Le crash est provoqué par un bug dans Cocoa. À votre insu, init(style:)
lui-même appelle init(nibName:bundle:)
. Et il l'appelle sur self
. C'est vous - MyTableViewController. Mais MyTableViewController n'a pas d'implémentation de init(nibName:bundle:)
. Et n'hérite pas init(nibName:bundle:)
, non plus, car vous avez déjà fourni un initialiseur désigné, coupant ainsi l'héritage.
Votre seule solution serait d'implémenter init(nibName:bundle:)
. Mais vous ne pouvez pas, car cette implémentation vous obligerait à définir la propriété d'instance greeting
- et vous ne savez pas à quoi la définir.
La solution simple - presque trop simple, c'est pourquoi il est si difficile d'y penser - est: ne pas sous-classer UITableViewController. Pourquoi est-ce une solution raisonnable? Parce que vous n'avez jamais réellement besoin de le sous-classer en premier lieu. UITableViewController est une classe largement inutile; cela ne fait rien pour vous que vous ne pouvez pas faire pour vous-même.
Donc, maintenant, nous allons réécrire notre classe en tant que sous-classe UIViewController à la place. Nous avons toujours besoin d'une vue de table comme vue, nous allons donc la créer dans loadView
, et nous la raccorderons également là-bas. Les modifications sont marquées comme des commentaires suivis:
class MyViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { // *
let greeting : String
weak var tableView : UITableView! // *
init(greeting:String) {
self.greeting = greeting
super.init(nibName:nil, bundle:nil) // *
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() { // *
self.view = UITableView(frame: CGRectZero, style: .Plain)
self.tableView = self.view as! UITableView
self.tableView.delegate = self
self.tableView.dataSource = self
}
}
Vous voudrez aussi, bien sûr, ajouter les méthodes minimales requises de source de données. Nous instancions maintenant notre classe comme ceci:
let tvc = MyViewController(greeting:"Hello there")
Notre projet se compile et s'exécute sans accroc. Problème résolu!
Vous pourriez objecter qu'en n'utilisant pas UITableViewController, nous avons perdu la possibilité d'obtenir une cellule prototype du storyboard. Mais ce n'est pas une objection, car nous n'avons jamais eu cette capacité en premier lieu. Rappelez-vous, notre hypothèse est que nous sous-classons et appelons l'initialiseur de notre propre sous-classe. Si nous obtenions la cellule prototype du storyboard, le storyboard nous instancierait en appelant init(coder:)
et le problème ne se serait jamais posé en premier lieu.
Xcode 6 Beta 5
Il semble que vous ne puissiez plus déclarer un initialiseur de commodité sans argument pour une sous-classe UITableViewController. Au lieu de cela, vous devez remplacer l'initialiseur par défaut.
class TestViewController: UITableViewController {
override init() {
// Overriding this method prevents other initializers from being inherited.
// The super implementation calls init:nibName:bundle:
// so we need to redeclare that initializer to prevent a runtime crash.
super.init(style: UITableViewStyle.Plain)
}
// This needs to be implemented (enforced by compiler).
required init(coder aDecoder: NSCoder!) {
// Or call super implementation
fatalError("NSCoding not supported")
}
// Need this to prevent runtime error:
// fatal error: use of unimplemented initializer 'init(nibName:bundle:)'
// for class 'TestViewController'
// I made this private since users should use the no-argument constructor.
private override init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
}
Je l'ai fait comme ça
class TestViewController: UITableViewController {
var dsc_var: UITableViewController?
override convenience init() {
self.init(style: .Plain)
self.title = "Test"
self.clearsSelectionOnViewWillAppear = true
}
}
La création et l'affichage d'une instance de TestViewController
dans un UISplitViewController
a fonctionné pour moi avec ce code. C'est peut-être une mauvaise pratique, dites-moi si c'est le cas (tout juste commencé avec Swift).
Pour moi, il y a toujours un problème quand il y a des variables non optionnelles et la solution de Nick Snyder est la seule qui fonctionne dans cette situation
Il n'y a qu'un seul problème: les variables sont initialisées 2 fois.
Exemple:
var dsc_statistcs_ctl: StatisticsController?
var dsrc_champions: NSMutableArray
let dsc_search_controller: UISearchController
let dsrc_search_results: NSMutableArray
override init() {
dsrc_champions = dsrg_champions!
dsc_search_controller = UISearchController(searchResultsController: nil)
dsrc_search_results = NSMutableArray.array()
super.init(style: .Plain) // -> calls init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) of this class
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
// following variables were already initialized when init() was called and now initialized again
dsrc_champions = dsrg_champions!
dsc_search_controller = UISearchController(searchResultsController: nil)
dsrc_search_results = NSMutableArray.array()
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
Les accessoires au mat pour une excellente explication. J'ai utilisé à la fois les solutions de matt et de @Nick Snyder, mais j'ai rencontré un cas dans lequel aucun ne fonctionnerait tout à fait, car je devais (1) initialiser les champs let
, (2) utiliser la fonction init(style: .Grouped)
(sans obtenir d'erreur d'exécution), et (3) utilisez le refreshControl
intégré (de UITableViewController). Ma solution de contournement consistait à introduire une classe intermédiaire MyTableViewController
dans ObjC, puis à utiliser cette classe comme base de mes contrôleurs de vue de table.
#import <UIKit/UIKit.h>
// extend but only override 1 designated initializer
@interface MyTableViewController : UITableViewController
- (instancetype)initWithStyle:(UITableViewStyle)style NS_DESIGNATED_INITIALIZER;
@end
#import "MyTableViewController.h"
// clang will warn about missing designated initializers from
// UITableViewController without the next line. In this case
// we are intentionally doing this so we disregard the warning.
#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
@implementation MyTableViewController
- (instancetype)initWithStyle:(UITableViewStyle)style {
return [super initWithStyle:style];
}
@end
#import "MyTableViewController.h"
class PuppyViewController : MyTableViewController {
let _puppyTypes : [String]
init(puppyTypes : [String]) {
_puppyTypes = puppyTypes // (1) init let field (once!)
super.init(style: .Grouped) // (2) call super with style and w/o error
self.refreshControl = MyRefreshControl() // (3) setup refresh control
}
// ... rest of implementation ...
}
la réponse de Matt est la plus complète, mais si vous souhaitez utiliser un tableViewController dans le style .plain (par exemple pour des raisons héritées). Il ne vous reste plus qu'à appeler
super.init(nibName: nil, bundle: nil)
au lieu de
super.init(style: UITableViewStyle.Plain)
ou self.init(style: UITableViewStyle.Plain)
Je ne suis pas sûr que cela soit lié à votre question, mais si vous souhaitez lancer le contrôleur UITableView avec xib, les notes de version Xcode 6.3 beta 4 fournissent une solution de contournement:
- Dans votre projet Swift, créez un nouveau fichier iOS Objective-C vide. Cela déclenchera une feuille vous demandant "Voulez-vous configurer un en-tête de pontage Objective-C".
- Appuyez sur "Oui" pour créer un en-tête de pontage
- À l'intérieur de [YOURPROJECTNAME] -Bridging-Header.h, ajoutez le code suivant:
@import UIKit;
@interface UITableViewController() // Extend UITableViewController to work around 19775924
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil NS_DESIGNATED_INITIALIZER ;
@end
J'ai remarqué une erreur similaire lors de l'utilisation de cellules statiques tableview et vous devez implémenter ceci:
init(coder decoder: NSCoder) {
super.init(coder: decoder)
}
si vous implémentez:
required init(coder aDecoder: NSCoder!) {
// Or call super implementation
fatalError("NSCoding not supported")
}
J'étais en train de faire un crash là-bas ... Un peu comme prévu. J'espère que cela t'aides.
class ExampleViewController: UITableViewController {
private var userName: String = ""
static func create(userName: String) -> ExampleViewController {
let instance = ExampleViewController(style: UITableViewStyle.Grouped)
instance.userName = userName
return instance
}
}
let vc = ExampleViewController.create("John Doe")
Je voulais sous-classer UITableViewController et ajouter une propriété non facultative qui nécessite de remplacer l'initialiseur et de traiter tous les problèmes décrits ci-dessus.
L'utilisation d'un Storyboard et d'un enchaînement vous offre plus d'options si vous pouvez travailler avec un var facultatif plutôt qu'avec une entrée non facultative dans votre sous-classe de UITableViewController
En appelant performSegueWithIdentifier et en remplaçant prepareForSegue dans votre contrôleur de vue de présentation, vous pouvez obtenir l'instance de la sous-classe UITableViewController et définir les variables facultatives avant la fin de l'initialisation:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "segueA"{
var viewController : ATableViewController = segue.destinationViewController as ATableViewController
viewController.someVariable = SomeInitializer()
}
if segue.identifier == "segueB"{
var viewController : BTableViewController = segue.destinationViewController as BTableViewController
viewController.someVariable = SomeInitializer()
}
}