如何在 Swift 中继承 UITableViewController

Posted

技术标签:

【中文标题】如何在 Swift 中继承 UITableViewController【英文标题】:How to subclass UITableViewController in Swift 【发布时间】:2014-08-05 13:00:22 【问题描述】:

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

【问题讨论】:

此错误已在 ios 9 中修复。 【参考方案1】:

注意 此错误已在 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 中创建它,并且我们也将它挂在那里。更改标记为带星号的 cmets:

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:) 来实例化我们,并且从一开始就不会出现问题。

【讨论】:

谢谢,很好的回答!我现在明白问题所在了。我同意,没有真正需要继承 UITableViewController。 不要忘记在 deinit 中取消委托和数据源,它可能会崩溃。在 iOS9 及更高版本中,不需要 deinit self.tableView?.delegate = nil self.tableView?.dataSource = nil 还要注意 UITableViewController 做了额外的事情,你必须重新实现(或多或少重复)。想想在某些时候清除选择,闪烁滚动指示器,诸如此类。没什么大不了的,只是简单地创建一个表格视图并将其加载为它的视图并不是它的全部。 这个bug是iOS 9 SDK引入的吗?在发布使用 iOS 9 SDK 构建的更新之前,我的代码在 iOS 8 上运行良好。现在我看到由于 [super initWithStyle:] 调用 [self initWithNibName:bundle:] 而导致 iOS 8 崩溃。 @JeffV 阅读我的回答,拜托。查看我回答的原始 date。该错误在 iOS 9 中修复。该错误在 iOS 8 中。因此,在 iOS 9 中工作的代码可能在 iOS 8 中无法工作。【参考方案2】:

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 个这样的错误。有什么办法可以放松编译器施加的限制,使其像 beta 5 之前一样运行? @user2237853 我还没有找到更好的方法。 从 Xcode 6.3.1 开始,这仍然是一个问题。现在不可能有​​一个 UITableViewController 子类的自定义 init 方法。它不会调用您指定的初始化,而是调用 init(nibname:bundle)。【参考方案3】:

向 matt 提供一个很好的解释。我已经使用了matt's 和@Nick Snyder's 解决方案,但是我遇到了一个情况,这两种解决方案都不起作用,因为我需要(1)初始化let 字段,(2)使用init(style: .Grouped)(不会出现运行时错误),以及(3)使用内置的refreshControl(来自 UITableViewController)。我的解决方法是在 ObjC 中引入一个中间类 MyTableViewController,然后将该类用作我的表视图控制器的基础。

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 ...

【讨论】:

【参考方案4】:

我是这样做的

class TestViewController: UITableViewController 

    var dsc_var: UITableViewController?

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

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

使用此代码在UISplitViewController 中创建和显示TestViewController 的实例确实对我有用。 也许这是不好的做法,请告诉我是不是(刚开始用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)

【讨论】:

这真是太疯狂了。呃。【参考方案5】:

matt's answer 是最完整的,但如果您确实想使用 .plain 样式的 tableViewController(比如出于遗留原因)。那么你需要做的就是调用

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

而不是

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

【讨论】:

【参考方案6】:

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

    

【讨论】:

【参考方案7】:

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

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

如果你实施:

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

我刚刚在那儿撞车了……有点像预期的那样。希望这会有所帮助。

【讨论】:

【参考方案8】:

不确定它是否与您的问题有关,但如果您想使用 xib 初始化 UITableView 控制器,Xcode 6.3 beta 4 Release Notes 提供了一种解决方法:

在您的 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

【讨论】:

【参考方案9】:
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")

【讨论】:

以上是关于如何在 Swift 中继承 UITableViewController的主要内容,如果未能解决你的问题,请参考以下文章

在 Swift 中继承具有自己类型和存储属性的数组

在创建新文件时在Swift中继承Viewcontroller

如何在 TypeScript 中继承 PromiseLike 接口?

如何在一个类中继承 QGraphicsRectItem 和 QGraphicsEllipseItem?

如何在 python 中继承类型提示?

如何在 JavaScript 中继承 C++ 类?