如何在Swift中继承UITableViewController

时间:2014-08-05 13:00:23

标签: ios uitableview swift xcode6

我想继承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()'

9 个答案:

答案 0 :(得分:59)

注意此错误已在iOS 9中修复,因此整个问题在此时都没有实际意义。以下讨论仅适用于明确适用的特定系统和Swift版本。

这显然是一个错误,但这也是一个非常简单的解决方案。我将解释问题,然后给出解决方案。请注意,我是为Xcode 6.3.2和Swift 1.2写的;自从Swift首次问世以来,Apple已经在这方面全力以赴,所以其他版本的行为会有所不同。

存在的基础

您将手动实例化UITableViewController (即通过在代码中调用其初始化程序)。并且你想要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)
    }
}

您的代码现在已编译!您现在很高兴(您认为)通过调用您编写的初始化程序来实例化此表视图控制器子类:

let tvc = MyTableViewController(greeting:"Hello there")

在你运行应用程序之前,一切看起来都很愉快和美好,此时你崩溃了这条消息:

  

致命错误:使用未实现的初始化程序init(nibName:bundle:)

导致崩溃的原因以及您无法解决的问题

崩溃是由Cocoa中的错误引起的。您不知道,init(style:)本身会调用init(nibName:bundle:)。它在self上调用它。那是你 - MyTableViewController。但MyTableViewController没有init(nibName:bundle:)的实现。并且也不继承 init(nibName:bundle:),因为您已经提供了指定的初始化程序,从而切断了继承。

您唯一的解决方案是实施 init(nibName:bundle:)。但是你不能,因为这个实现需要你设置实例属性greeting - 你不知道该把它设置为什么。

简单解决方案

简单的解决方案 - 几乎太简单了,这就是为什么这么难以想到 - 是:不是UITableViewController的子类。为什么这是合理的解决方案?因为你从来没有真正需要来创建子类。 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:)来实例化我们,并且问题永远不会出现在第一位。

答案 1 :(得分:20)

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

答案 2 :(得分:3)

我这样做了

class TestViewController: UITableViewController {

    var dsc_var: UITableViewController?

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

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

使用此代码在TestViewController中创建和显示UISplitViewController的实例对我有用。 也许这是不好的做法,请告诉我它是否(刚开始使用swift)。

对于我来说,当存在非可选变量并且Nick Snyder的解决方案是唯一在此情况下工作的解决方案时仍然存在问题 只有一个问题: 变量初始化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 :(得分:2)

道具要哑光以获得很好的解释。我已经使用了matt和@Nick Snyder的解决方案,但我遇到了一个既不能正常工作的情况,因为我需要(1)初始化let字段,(2)使用{{1} (没有得到运行时错误),以及(3)使用内置的init(style: .Grouped)(来自UITableViewController)。我的解决方法是在ObjC中引入一个中间类refreshControl,然后使用该类作为我的表视图控制器的基础。

MyTableViewController.h

MyTableViewController

MyTableViewController.m:

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

将以下内容添加到Project的Bridging-Header.h

#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

然后在swift中使用。示例:“PuppyViewController.swift”:

#import "MyTableViewController.h"

答案 4 :(得分:1)

matt的答案是最完整的,但是如果你确实想要使用.plain风格的tableViewController(比如遗留原因)。然后你需要做的就是打电话

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

而不是

super.init(style: UITableViewStyle.Plain)self.init(style: UITableViewStyle.Plain)

答案 5 :(得分:0)

我想继承UITableViewController并添加一个非可选属性,它需要覆盖初始化程序并处理上述所有问题。

如果您可以在UITableViewController的子类中使用可选的var而不是非可选的let,则使用Storyboard和segue可以提供更多选项

通过在呈现视图控制器中调用performSegueWithIdentifier并覆盖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()
        }

    }

答案 6 :(得分:0)

我在使用静态tableview单元时发现了类似的错误,你必须实现这个:

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

如果您实施:

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

我刚刚在那里遇到了崩溃......有点像预期的那样。希望这会有所帮助。

答案 7 :(得分:0)

不确定它与您的问题有关,但如果您想使用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

答案 8 :(得分:0)

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