web-dev-qa-db-ja.com

SwiftでUITableViewControllerをサブクラス化する方法

UITableViewControllerをサブクラス化し、引数なしでデフォルトの初期化子を呼び出すことでインスタンス化できるようにします。

class TestViewController: UITableViewController {
    convenience init() {
        self.init(style: UITableViewStyle.Plain)
    }
}

Xcode 6 Beta 5以降、上記の例は機能しなくなりました。

Overriding declaration requires an 'override' keyword
Invalid redeclaration of 'init()'
40
Nick Snyder

[〜#〜] note [〜#〜]このバグはiOS 9で修正されたため、その時点ですべての問題は議論の余地があります。以下の説明は、特定のシステムおよびSwiftの明示的な対象となるバージョン)にのみ適用されます。


これは明らかにバグですが、非常に簡単な解決策もあります。問題を説明してから解決策を示します。 Xcode 6.3.2とSwift 1.2; Appleは、この日からマップ全体にSwiftが最初に登場したため、他のバージョンは異なる動作をします。

存在の根拠

UITableViewControllerのインスタンスを作成しますby hand(つまり、コードで初期化子を呼び出します)。そして、与えたいインスタンスプロパティがあるため、UITableViewControllerをサブクラス化します。

問題

したがって、インスタンスプロパティから始めます。

_class MyTableViewController: UITableViewController {
    let greeting : String
}
_

これにはデフォルト値がないため、初期化子を作成する必要があります。

_class MyTableViewController: UITableViewController {
    let greeting : String
    init(greeting:String) {
        self.greeting = greeting
    }
}
_

しかし、それは正当な初期化子ではありません-superを呼び出さなければなりません。 superへの呼び出しがinit(style:)を呼び出すことだとしましょう。

_class MyTableViewController: UITableViewController {
    let greeting : String
    init(greeting:String) {
        self.greeting = greeting
        super.init(style: .Plain)
    }
}
_

ただし、init(coder:)を実装する必要があるため、コンパイルできません。そうしたらいい:

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

コードがコンパイルされました!これで、あなたが書いたイニシャライザを呼び出すことで、このTable View Controllerのサブクラスをインスタンス化することができます(あなたが思うに):

_let tvc = MyTableViewController(greeting:"Hello there")
_

あなたがrunアプリになるまで、すべてが陽気でバラ色に見えます。この時点で、このメッセージでクラッシュします:

致命的なエラー:未実装の初期化子init(nibName:bundle:)の使用

クラッシュの原因と解決できない理由

クラッシュは、Cocoaのバグが原因です。 init(style:)自体はinit(nibName:bundle:)を呼び出します。そして、selfで呼び出します。 MyTableViewControllerです。ただし、MyTableViewControllerにはinit(nibName:bundle:)の実装はありません。また、inheritinit(nibName:bundle:)も同様ではありません。指定された初期化子を既に提供しているため、継承が切断されているためです。

唯一の解決策は、implementinit(nibName:bundle:)です。ただし、その実装ではインスタンスプロパティgreetingを設定する必要があり、設定するものがわからないため、できません。

シンプルなソリューション

単純なソリューション-ほぼ単純すぎるため、考えるのが非常に難しい理由は、ITableViewControllerをサブクラス化しないでくださいです。なぜこれが合理的な解決策なのですか?実際にneededを最初にサブクラス化することはないからです。 UITableViewControllerは、ほとんど意味のないクラスです。自分ではできないことは何もしません。

そのため、今度はクラスをUIViewControllerサブクラスとして書き換えます。ビューとしてまだテーブルビューが必要なので、loadViewに作成し、そこにも接続します。変更はスター付きコメントとしてマークされます:

_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
    }
}
_

もちろん、必要最小限のデータソースメソッドを追加することもできます。次のようにクラスをインスタンス化します。

_let tvc = MyViewController(greeting:"Hello there")
_

私たちのプロジェクトは問題なくコンパイルおよび実行されます。問題が解決しました!

異論-ない

UITableViewControllerを使用しないと、ストーリーボードからプロトタイプセルを取得する機能が失われることに反対するかもしれません。しかし、そもそもその能力は決してなかったなので、これは異論ではありません。覚えておいてください、私たちの仮説は、私たちが自分のサブクラスの初期化子をサブクラス化して呼び出すということです。ストーリーボードからプロトタイプセルを取得している場合、ストーリーボードはinit(coder:)を呼び出すことでインスタンス化され、最初から問題が発生することはありませんでした。

59
matt

Xcode 6 Beta 5

UITableViewControllerサブクラスの引数なしの便利な初期化子を宣言できなくなったようです。代わりに、デフォルトのイニシャライザーをオーバーライドする必要があります。

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

私はこうしました

class TestViewController: UITableViewController {

    var dsc_var: UITableViewController?

    override convenience init() {
        self.init(style: .Plain)

        self.title = "Test"
        self.clearsSelectionOnViewWillAppear = true
    }
}

TestViewControllerUISplitViewControllerのインスタンスを作成して表示すると、このコードでうまくいきました。たぶんこれは悪い習慣です。もしそうなら教えてください(Swiftで始めたばかりです)。

私にとっては、非オプション変数があり、 Nick Snyder の解決策がこの状況で働く唯一のものである場合、まだ問題があります
問題は1つだけです。変数が2回初期化されます。

例:

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

素晴らしい説明のためにマットに小道具。私はマットと@Nick Snyderの両方のソリューションを利用しましたが、(1)letフィールドを初期化し、(2)init(style: .Grouped)(実行時エラーなし)、および(3)組み込みのrefreshControl(UITableViewControllerから)を使用します。私の回避策は、ObjCにMyTableViewControllerという中間クラスを導入し、そのクラスをTable View Controllerのベースとして使用することでした。

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

以下をプロジェクトのBridging-Header.hに追加します

#import "MyTableViewController.h"

その後、Swiftで使用します。例:「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ɳ

mattの答えは最も完全ですが、.plainスタイルでtableViewControllerを使用したい場合(たとえば、従来の理由のため)。あとは、電話するだけです

super.init(nibName: nil, bundle: nil)

の代わりに

super.init(style: UITableViewStyle.Plain)またはself.init(style: UITableViewStyle.Plain)

1
John Stricker

質問に関連するかどうかはわかりませんが、xibでUITableViewコントローラーを初期化する場合は、Xcode 6.3 beta 4リリースノートに回避策があります。

  • Swiftプロジェクトで、新しい空のiOS Objective-Cファイルを作成します。これにより、「Objective-Cブリッジングヘッダーを設定しますか?」というシートが表示されます。
  • 「はい」をタップしてブリッジングヘッダーを作成します
  • [YOURPROJECTNAME] -Bridging-Header.h内に次のコードを追加します。
@import UIKit;
@interface UITableViewController() // Extend UITableViewController to work around 19775924
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil NS_DESIGNATED_INITIALIZER ;
@end
0
Cfr

静的なTableViewセルを使用しているときに同様のエラーに気づいたので、これを実装する必要があります:

init(coder decoder: NSCoder) {
    super.init(coder: decoder)
}

実装する場合:

required init(coder aDecoder: NSCoder!) {
        // Or call super implementation
        fatalError("NSCoding not supported")
}

私はそこでただクラッシュを起こしていました...予想通り。お役に立てれば。

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

UITableViewControllerをサブクラス化し、初期化子をオーバーライドし、上記のすべての問題に対処する必要があるオプションでないプロパティを追加したかったのです。

ストーリーボードとセグエを使用すると、UITableViewControllerのサブクラスでオプションではないletではなくオプションのvarで作業できる場合、より多くのオプションが提供されます

PerformSegueWithIdentifierを呼び出し、表示するView ControllerでprepareForSegueをオーバーライドすることにより、UITableViewControllerサブクラスのインスタンスを取得し、初期化が完了する前にオプションの変数を設定できます。

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