Ich möchte UITableViewController in eine Unterklasse unterteilen und es instanziieren, indem ich einen Standardinitialisierer ohne Argumente aufrufe.
class TestViewController: UITableViewController {
convenience init() {
self.init(style: UITableViewStyle.Plain)
}
}
Ab dem Xcode 6 Beta 5 funktioniert das obige Beispiel nicht mehr.
Overriding declaration requires an 'override' keyword
Invalid redeclaration of 'init()'
NOTE Dieser Fehler wurde in iOS 9 behoben, so dass die gesamte Angelegenheit an diesem Punkt nicht weiter bearbeitet werden kann. Die nachstehende Diskussion gilt nur für das bestimmte System und die Version von Swift, auf die es ausdrücklich abzielt.
Dies ist eindeutig ein Fehler, aber es gibt auch eine sehr einfache Lösung. Ich erkläre das Problem und gebe dann die Lösung. Bitte beachte, dass ich dies für Xcode 6.3.2 und Swift 1.2 schreibe; Apple ist seit dem Tag, an dem Swift zum ersten Mal veröffentlicht wurde, überall auf der Karte zu finden, sodass sich andere Versionen anders verhalten.
Sie werden UITableViewController von Hand instanziieren (dh indem Sie den Initialisierer im Code aufrufen). Und Sie möchten UITableViewController in Unterklassen unterteilen, weil Sie Instanzeigenschaften haben, die Sie ihm geben möchten.
Sie beginnen also mit einer Instanzeigenschaft:
class MyTableViewController: UITableViewController {
let greeting : String
}
Dies hat keinen Standardwert, so dass Sie einen Initialisierer schreiben müssen:
class MyTableViewController: UITableViewController {
let greeting : String
init(greeting:String) {
self.greeting = greeting
}
}
Aber das ist kein legaler Initialisierer - Sie müssen super
aufrufen. Angenommen, Sie rufen super
auf, um init(style:)
aufzurufen.
class MyTableViewController: UITableViewController {
let greeting : String
init(greeting:String) {
self.greeting = greeting
super.init(style: .Plain)
}
}
Sie können jedoch immer noch nicht kompilieren, da Sie init(coder:)
implementieren müssen. Also tust du es:
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)
}
}
Ihr Code wird jetzt kompiliert! Sie instanziieren diese Table-View-Controller-Unterklasse nun glücklich (Sie denken), indem Sie den Initialisierer aufrufen, den Sie geschrieben haben:
let tvc = MyTableViewController(greeting:"Hello there")
Alles sieht fröhlich und rosig aus, bis Sie die App ausführen . An diesem Punkt stürzen Sie mit der folgenden Meldung ab:
schwerwiegender Fehler: Verwendung des nicht implementierten Initialisierers
init(nibName:bundle:)
Der Absturz wird durch einen Fehler in Cocoa verursacht. Unbekannt ruft init(style:)
selbst init(nibName:bundle:)
auf. Und es ruft es auf self
auf. Das bist du - MyTableViewController. MyTableViewController hat jedoch keine Implementierung von init(nibName:bundle:)
. Und erbt auch nicht init(nibName:bundle:)
, weil Sie bereits einen bestimmten Initialisierer angegeben haben, wodurch die Vererbung abgeschnitten wird.
Ihre einzige Lösung wäre, init(nibName:bundle:)
zu implementieren. Dies ist jedoch nicht möglich, da für diese Implementierung die Instanzeigenschaft greeting
festgelegt werden muss und Sie nicht wissen, auf was Sie sie festlegen sollen.
Die einfache Lösung - fast zu einfach, weshalb sie so schwer vorstellbar ist - lautet: ITableViewController nicht unterordnen. Warum ist das eine vernünftige Lösung? Weil Sie es eigentlich nie benötigt haben , um es überhaupt zu unterordnen. UITableViewController ist eine weitgehend sinnlose Klasse. es tut nichts für dich, was du nicht für dich tun kannst.
Jetzt werden wir unsere Klasse stattdessen als UIViewController-Unterklasse umschreiben. Wir brauchen noch eine Tabellenansicht als unsere Ansicht, also werden wir sie in loadView
erstellen und sie dort auch anschließen. Änderungen sind als markierte Kommentare gekennzeichnet:
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
}
}
Außerdem möchten Sie natürlich die minimal erforderlichen Datenquellenmethoden hinzufügen. Wir instanziieren unsere Klasse jetzt wie folgt:
let tvc = MyViewController(greeting:"Hello there")
Unser Projekt kompiliert und läuft reibungslos. Problem gelöst!
Sie könnten einwenden, dass wir durch die Nichtverwendung von UITableViewController nicht mehr in der Lage sind, eine Prototypzelle aus dem Storyboard abzurufen. Aber das ist kein Einwand, denn wir hatten diese Fähigkeit überhaupt nicht. Denken Sie daran, unsere Hypothese lautet, dass wir die Initialisierung unserer eigenen Unterklasse unterordnen und aufrufen. Wenn wir die Prototypzelle aus dem Storyboard erhalten würden, würde das Storyboard uns durch Aufrufen von init(coder:)
instanziieren, und das Problem wäre überhaupt nicht aufgetreten.
Xcode 6 Beta 5
Es scheint, dass Sie für eine UITableViewController-Unterklasse keinen Komfortinitialisierer ohne Argumente mehr deklarieren können. Stattdessen müssen Sie den Standardinitialisierer überschreiben.
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)
}
}
Ich habe es so gemacht
class TestViewController: UITableViewController {
var dsc_var: UITableViewController?
override convenience init() {
self.init(style: .Plain)
self.title = "Test"
self.clearsSelectionOnViewWillAppear = true
}
}
Das Erstellen und Anzeigen einer Instanz von TestViewController
in einem UISplitViewController
hat bei mir mit diesem Code funktioniert. Vielleicht ist dies eine schlechte Übung, bitte sagen Sie mir, ob dies der Fall ist (habe gerade mit Swift begonnen).
Für mich gibt es immer noch ein Problem, wenn es nicht optionale Variablen gibt und die Lösung von Nick Snyder die einzige ist, die in dieser Situation funktioniert
Es gibt nur ein Problem: Die Variablen werden zweimal initialisiert.
Beispiel:
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)
}
Requisiten zu matt für eine gute Erklärung. Ich habe sowohl die Lösungen von matt als auch von @Nick Snyder verwendet, bin jedoch auf einen Fall gestoßen, in dem keiner von beiden wirklich funktioniert, da ich (1) let
-Felder initialisieren und (2) init(style: .Grouped)
(ohne einen Laufzeitfehler zu erhalten) und (3) den eingebauten refreshControl
(von UITableViewController) verwenden. Meine Problemumgehung bestand darin, eine Zwischenklasse MyTableViewController
in ObjC einzuführen und diese Klasse dann als Basis für meine Tabellensicht-Controller zu verwenden.
#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 ...
}
die Antwort von matt ist die vollständigste, aber wenn Sie einen tableViewController im .plain-Stil verwenden möchten (etwa aus älteren Gründen). Dann brauchen Sie nur noch anzurufen
super.init(nibName: nil, bundle: nil)
anstatt von
super.init(style: UITableViewStyle.Plain)
oder self.init(style: UITableViewStyle.Plain)
Sie sind sich nicht sicher, ob es sich um eine Frage handelt, aber für den Fall, dass Sie den UITableView-Controller mit xib starten möchten, bieten die Versionshinweise zu Xcode 6.3 Beta 4 eine Problemumgehung:
- Erstellen Sie in Ihrem Swift - Projekt eine neue leere iOS Objective-C-Datei. Dadurch wird ein Blatt mit der Frage ausgelöst, ob Sie einen Objective-C-Bridging-Header konfigurieren möchten.
- Tippen Sie auf „Ja“, um einen Überbrückungsheader zu erstellen
- Fügen Sie in [YOURPROJECTNAME] -Bridging-Header.h den folgenden Code ein:
@import UIKit;
@interface UITableViewController() // Extend UITableViewController to work around 19775924
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil NS_DESIGNATED_INITIALIZER ;
@end
Bei der Verwendung statischer Tabellenzellen ist ein ähnlicher Fehler aufgetreten, und Sie müssen Folgendes implementieren:
init(coder decoder: NSCoder) {
super.init(coder: decoder)
}
wenn Sie implementieren:
required init(coder aDecoder: NSCoder!) {
// Or call super implementation
fatalError("NSCoding not supported")
}
Ich hatte gerade einen Unfall dort ... Irgendwie wie erwartet. Hoffe das hilft.
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")
Ich wollte UITableViewController in eine Unterklasse unterteilen und eine nicht optionale Eigenschaft hinzufügen, die das Überschreiben des Initialisierers und das Behandeln aller oben beschriebenen Probleme erfordert.
Die Verwendung eines Storyboards und eines Segues bietet Ihnen mehr Optionen, wenn Sie mit einer optionalen Variable anstatt mit einer nicht optionalen Eingabe in Ihrer Unterklasse von UITableViewController arbeiten können
Durch Aufrufen von performSegueWithIdentifier und Überschreiben von prepareForSegue in Ihrem Presenting View Controller können Sie die Instanz der Unterklasse UITableViewController abrufen und die optionalen Variablen festlegen, bevor die Initialisierung abgeschlossen ist:
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()
}
}