Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/swift/20.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何在Swift中对自定义UIViewController进行子类化?_Swift_Inheritance_View_Uiviewcontroller_Subclassing - Fatal编程技术网

如何在Swift中对自定义UIViewController进行子类化?

如何在Swift中对自定义UIViewController进行子类化?,swift,inheritance,view,uiviewcontroller,subclassing,Swift,Inheritance,View,Uiviewcontroller,Subclassing,我想创建一个可重用的视图控制器UsersViewControllerBase UsersViewControllerBase扩展了UIViewController,实现了两个委托(UITableViewDelegate,UITableViewDataSource),并有两个视图(UITableView,UISegmentedControl) 目标是继承UsersViewControllerBase的实现,并在UsersViewController类中自定义分段控件的分段项 class Users

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

UsersViewControllerBase
扩展了
UIViewController
,实现了两个委托(
UITableViewDelegate
UITableViewDataSource
),并有两个视图(
UITableView
UISegmentedControl

目标是继承
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
outlets (我创建了一个简单的
UIViewController
,并在情节提要中将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()

但不幸的是,这不起作用

问题在于,您在情节提要中创建了一个场景,但没有为视图控制器的视图提供任何子视图或连接任何插座,因此界面为空

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


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

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

  • 它找到具有该标识符的场景
  • 它确定该场景的基类;及
  • 它返回该类的一个实例
然后,当您首次访问
视图时,它将:

  • 创建该情节提要场景中概述的视图层次;及
  • 把插座接上
(这个过程实际上要复杂得多,但我正试图将其简化为该工作流中的关键元素。)

此工作流的含义是,插座和基类由传递给
实例化eviewController WithiIdentifier
的唯一情节提要标识符确定。因此,对于基类的每个子类,您都需要一个单独的故事板场景,并将插座连接到该特定子类

不过,有一种方法可以实现您的要求。您可以让视图控制器实现
loadView
(不要与
viewDidLoad
)并让它以编程方式创建视图控制器类所需的视图层次结构,而不是为视图控制器使用故事板场景。苹果曾经在他们的iOS视图控制器编程指南中对这一过程做了很好的介绍,但后来就不再讨论这个问题了,但仍然可以在他们的网站上找到

话虽如此,我个人不会被迫回到以编程方式创建视图的旧世界,除非有非常令人信服的理由。我可能更倾向于放弃视图控制器子类的方法,采用类似于单个类的方法(这意味着我回到了故事板的世界),然后向它传递一些标识符,指示我希望从该场景的特定实例中执行的行为。如果您想在这方面保持一些面向对象的优雅,您可以根据在此视图控制器类中设置的某些属性为数据源和委托实例化自定义类


如果您需要真正的动态视图控制器行为,而不是通过编程创建的视图层次结构,我更倾向于走这条路。或者,更简单的是,继续采用您最初的视图控制器子类化方法,只需接受故事板中每个子类都需要单独的场景即可。

因此,您有了基类:

class UsersViewControllerBase: UIViewController, UITableViewDelegate, UITableViewDataSource {
    @IBOutlet weak var segmentedControl: UISegmentedControl!
    @IBOutlet weak var tableView: UITableView!
    //implementation of delegates
}
[A] 以及您的子类:
class UsersViewController:UsersViewControllerBase{var text=“Hello!”}

[B] 子类将要扩展的协议:

protocol SomeProtocol {
    var text: String? { get set }
}
[C] 还有一些类来处理你的数据。例如,单例类:

class MyDataManager {

    static let shared = MyDataManager()
    var text: String?

    private init() {}

    func cleanup() {
        text = nil
    }
}
[D] 以及您的子类:

class UsersViewController: UsersViewControllerBase {

    deinit {
        // Revert
        object_setClass(self, UsersViewControllerBase.self)
        MyDataManager.shared.cleanup()
    }
}

extension UsersViewController: SomeProtocol {
    var text: String? {
        get {
            return MyDataManager.shared.text
        }
        set {
            MyDataManager.shared.text = newValue
        }
    }
}
要正确使用子类,您需要执行以下操作:

编辑: 正如@VyachaslavGerchicov所指出的,原始答案并非始终有效,因此标记为[A]的部分被划掉。正如这里的答案所解释的:

。。。setClass无法向已创建的对象添加实例变量


[B] 添加了、[C]和[D]作为解决方案。[C]的另一个选项是将其设置为
UsersViewController
的私有内部类,这样只有它才能访问该单例。

好吧,它们是继承的,所以其他一些东西出了问题(比如可能是故事板场景的设置)。您记得连接这些插座吗?不,我没有连接UsersViewController中的插座,因为我认为它们已经连接(所以可能是继承的)
class UsersViewController: UsersViewControllerBase {

    deinit {
        // Revert
        object_setClass(self, UsersViewControllerBase.self)
        MyDataManager.shared.cleanup()
    }
}

extension UsersViewController: SomeProtocol {
    var text: String? {
        get {
            return MyDataManager.shared.text
        }
        set {
            MyDataManager.shared.text = newValue
        }
    }
}
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 property 'text', so:
        let subclassObject = usersViewController as! UsersViewController
        subclassObject.text = "Hello! World."
        //Use UsersViewController object as desired. For example:
        navigationController?.pushViewController(subclassObject, animated: true)
    }
}