如何在Swift中继承自定义UIViewController?

时间:2015-11-19 16:23:24

标签: swift inheritance view uiviewcontroller subclassing

我想创建一个可重用的视图控制器UsersViewControllerBase

UsersViewControllerBase扩展UIViewController,并实现两个代理(UITableViewDelegateUITableViewDataSource),并有两个视图(UITableViewUISegmentedControl

目标是继承UsersViewControllerBase的实现,并在UsersViewController类中自定义分段控件的分段项。

class UsersViewControllerBase: UIViewController, UITableViewDelegate, UITableViewDataSource{
  @IBOutlet weak var segmentedControl: UISegmentedControl!
  @IBOutlet weak var tableView: UITableView!
  //implementation of delegates
}

class UsersViewController: UsersViewControllerBase {
}

故事板中存在UsersViewControllerBase并且所有插座都已连接,标识符已指定。

问题是如何初始化UsersViewController以继承UsersViewControllerBase

的所有视图和功能

当我创建UsersViewControllerBase的实例时,一切正常

let usersViewControllerBase = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()).instantiateViewControllerWithIdentifier("UsersViewControllerBase") as? UsersViewControllerBase

但是当我创建UsersViewController的实例时,我会获得nil个出口 (我创建了一个简单的UIViewController并在故事板中为其分配了UsersViewController类

let usersViewController = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()).instantiateViewControllerWithIdentifier("UsersViewController") as? UsersViewController

看起来不会继承视图。

我希望UsersViewControllerBase中的init方法可以从故事板中获取带有视图和出口的控制器:

  class UsersViewControllerBase: UIViewController, UITableViewDelegate, UITableViewDataSource{
      @IBOutlet weak var segmentedControl: UISegmentedControl!
      @IBOutlet weak var tableView: UITableView!
      init(){
        let usersViewControllerBase = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()).instantiateViewControllerWithIdentifier("UsersViewControllerBase") as? UsersViewControllerBase
        self = usersViewControllerBase //but that doesn't compile
      }
    }

我会初步UsersViewController

let usersViewController = UsersViewController()

但不幸的是,这不起作用

3 个答案:

答案 0 :(得分:5)

当您通过instantiateViewControllerWithIdentifier实例化视图控制器时,该过程基本上如下:

  • 找到具有该标识符的场景;
  • 它确定该场景的基类;和
  • 它返回该类的实例。

然后,当您第一次访问view时,它会:

  • 创建该故事板场景中概述的视图层次结构;和
  • 连接插座。

(这个过程实际上比这更复杂,但我试图将其减少到此工作流程中的关键元素。)

此工作流程的含义是,出口和基类由您传递给instantiateViewControllerWithIdentifier的唯一故事板标识符确定。因此,对于基类的每个子类,您需要一个单独的故事板场景,并将出口连接到该特定子类。

但是,有一种方法可以满足您的要求。您可以使视图控制器实现loadView(不要与viewDidLoad混淆),而是让它以编程方式创建视图控制器类所需的视图层次结构,而不是将视图控制器用于故事板场景。 Apple曾经在他们的适用于iOS的视图控制器编程指南中对这个过程进行了很好的介绍,但此后已经退出了讨论,但它仍然可以在他们的legacy documentation中找到。

话虽如此,我个人不会被迫回到以编程方式创建的视图的旧世界,除非有一个非常有说服力的案例。我可能更倾向于放弃视图控制器子类方法,并采用类似单个类的东西(这意味着我回到故事板的世界中),然后传递一些标识符来指示我想要的特定行为那个场景的实例。 如果你想保持一些OO的优雅,你可以根据你在这个视图控制器类中设置的某些属性来实例化数据源和委托的自定义类。

如果您需要真正的动态视图控制器行为,而不是以编程方式创建的视图层次结构,我更倾向于走这条路。 或者,甚至更简单,继续采用您的原始视图控制器子类化方法,并接受您在每个子类的故事板中需要单独的场景。

答案 1 :(得分:5)

所以,你有基类:

class UsersViewControllerBase: UIViewController, UITableViewDelegate, UITableViewDataSource {
    @IBOutlet weak var segmentedControl: UISegmentedControl!
    @IBOutlet weak var tableView: UITableView!
    //implementation of delegates
}

你的子类:

class UsersViewController: UsersViewControllerBase {
    var text = "Hello!"
}

要正确使用子类,您需要执行此操作:

class TestViewController: UIViewController {
    ...
    func doSomething() {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        //Instantiate as base
        let usersViewController = storyboard.instantiateViewControllerWithIdentifier("UsersViewControllerBase") as! UsersViewControllerBase
        //Replace the class with the desired subclass
        object_setClass(usersViewController, UsersViewController.self)
        //But you also need to access the instance variable 'text', so:
        let subclassObject = usersViewController as! UsersViewController
        subclassObject.text = "Hello! World."
        //Use UsersViewController object as desired. For example:
        navigationController?.pushViewController(subclassObject, animated: true)
    }
}

答案 2 :(得分:0)

问题是你在故事板中创建了一个场景,但你没有给视图控制器查看任何子视图或连接任何插座,所以界面是空白的。

如果您的目标是重复使用与几个不同视图控制器类的实例相关的视图和子视图的集合,最简单的方法是,如果您不想在代码中创建它们,则将它们放在 .xib 文件并在视图控制器自己的视图加载过程之后将其加载到代码中(例如在viewDidLoad中)。

但是如果目标只是在这个视图控制器的不同实例中“自定义分段控件的分段项”,最简单的方法是拥有一个视图控制器类和一个相应的接口设计,并在代码中执行自定义。但是,在每种情况下,可以从其自己的 .xib 中加载该分段控件,如果您在视觉上设计它是很重要的。