webentwicklung-frage-antwort-db.com.de

Wie man UITableViewController in Swift

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()'
40
Nick Snyder

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.

Der Grund des Seins

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.

Das Problem

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:)

Was den Absturz verursacht und warum Sie ihn nicht lösen können

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

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!

Ein Einwand - nicht

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.

59
matt

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)
    }
}
20
Nick Snyder

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)
}
3
Manuel Wa

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.

MyTableViewController.h

#import <UIKit/UIKit.h>
// extend but only override 1 designated initializer
@interface MyTableViewController : UITableViewController
- (instancetype)initWithStyle:(UITableViewStyle)style NS_DESIGNATED_INITIALIZER;
@end

MyTableViewController.m:

#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

Fügen Sie dem Bridging-Header.h des Projekts folgend hinzu

#import "MyTableViewController.h"

Dann in Swift anwenden. Beispiel: "PuppyViewController.Swift":

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 ...
}
2
ɲeuroburɳ

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)

1
John Stricker

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
0
Cfr

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.

0
C0D3
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")
0
neoneye

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()
        }

    }
0
user3030674