使用Generic将Storyboard链接到UIViewController

时间:2016-05-17 17:39:04

标签: ios interface-builder

我有这个视图控制器,用于以通用方式显示模型对象的详细信息:

class APIModelDetailsVC<T where T: APIModel>: UIViewController {...}

我喜欢我的故事板来使用这个课程。我能够在Interface Builder中分配它:

enter image description here

我在tableview的didSelect方法中准备了这个ViewController(包括指定通用占位符的类型):

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
  print("didSelectRowAtIndexPath \(indexPath.row)")
  if let vc = UIStoryboard(name: "APIModelDetailsVC", bundle: nil).instantiateInitialViewController() as? APIModelDetailsVC<StarWarsPerson> {
    vc.model = data[indexPath.row]
    self.navigationController?.pushViewController(vc, animated: true)
  }
}

当我尝试导航到此视图控制器时,出现以下控制台错误:

Unknown class _TtC14api_collection17APIModelDetailsVC in Interface Builder file.

这是故事板的记录限制吗?有没有办法可以/应该指定通用,以便故事板可以链接到它?

3 个答案:

答案 0 :(得分:7)

故事板存在泛型类的问题。问题是Interface Builder通过Objective-C运行时与ViewController进行通信。因此,InterfaceBuilder仅限于Objective-C提供的功能。在这种情况下,不支持泛型。

解决方法是使用.load()的{​​{1}}方法。

例如,如果您有上面提到的ViewController类:

NSObject

你应该创建一个&#34;虚拟&#34; ViewController如:

class APIModelDetailsVC<T where T: APIModel>: UIViewController {...}

并在故事板中设置最后一个ViewController。之后,为了在Objective-c运行时中进行此操作,您应该在AppDelegate中或在加载此控制器之前的某处添加以下内容。

class StartWasModelDetailsVC: APIModelDetailsVC<StarWarsPerson> {...}

希望它有所帮助!

答案 1 :(得分:3)

我遇到了同样的问题并提出了另一种解决方法:

我只在代码中创建我的通用UIViewController。在IB中我然后在XIB文件中创建一个UIView,它包含我需要的所有控件以及一个名为&#34; mainView&#34;的类。有所有的网点。

在通用UIViewController的viewDidLoad()中,然后从XIB加载视图,将其连接起来并将其插入到视图控制器的主视图中,如下所示:

class RSListViewController<T: RSObjectProtocol>: UIViewController, UITableViewDelegate {

override func viewDidLoad() {

    super.viewDidLoad()

    guard let mainView = Bundle.main.loadNibNamed("mainView", owner: self, options: nil)?.first as? MainView else { return }

    self.mainView = mainView
    self.mainView.tableView.delegate = self
    self.mainView.segControl.addTarget(self, action: #selector(RSListViewController.listTypeChanged(_:)), for: .valueChanged)

    self.view.addSubview(mainView)

}

到目前为止,我能看到的唯一警告是你无法像

那样直接解决这些问题
self.buttonAdd

但需要通过像

这样的视图
self.mainView.buttonAdd

我在上面的代码示例中使用addTarget通过代码连接事件而不是动作。

如果你不想做什么,那就是混合#34;但在我的情况下它似乎工作正常。它确实阻止了许多虚拟ViewControllers的创建,在我看来,它们违背了泛型的概念。但这只是个人品味的问题,并不意味着以任何方式批评Federico Ojeda的解决方案。

答案 2 :(得分:0)

对于这个问题,还有另一个非常好的选择。

我的解决方案受到List all subclasses of one class

的启发

通用UIViewcontrollers在情节提要中工作,前提是您在使用它们之前在Obj-C运行时中对其进行了注册。通过使用通用库,我们可以完成这项工作。 所有UIViewControllers都是NSObject,因此,我们可以检索所有子对象。因此,我们可以自动注册UIViewController的所有子类,甚至是通用子类,并在AppDelegate中自动对其调用load()。

public func address(of object: Any?) -> UnsafeMutableRawPointer {
    return Unmanaged.passUnretained(object as AnyObject).toOpaque()
}

public func subclasses<T>(of theClass: T) -> [T] {
    var count: UInt32 = 0, result: [T] = []
    let allClasses = objc_copyClassList(&count)!
    let classPtr = address(of: theClass)

    for n in 0 ..< count {
        let someClass: AnyClass = allClasses[Int(n)]
        guard let someSuperClass = class_getSuperclass(someClass), address(of: someSuperClass) == classPtr else { continue }
        result.append(someClass as! T)
    }

    return result
}

在applicationDidFinishLaunchingWithOptions中完成此操作之后,我们可以简单地执行以下操作:

    for subclass in subclasses(of: UIViewController.self) {
        subclass.load()
    }

假设我们有以下视图控制器:

class BaseViewController<CompletionResult> : UIViewController {
    var onComplete: ((CompletionResult) -> ())?


}

class TestViewController : BaseViewController<String> {


    override func viewDidLoad() {
        super.viewDidLoad()

        self.onComplete = {
            value in
            print(value)
        }

        onComplete?("TEST")
    }


}

我们现在可以在情节提要中安全地使用TestViewController!