【问题标题】:Link Storyboard to UIViewController with Generic使用 Generic 将 Storyboard 链接到 UIViewController
【发布时间】:2016-09-13 22:58:49
【问题描述】:

我有这个视图控制器,旨在以通用方式呈现模型对象的详细信息:

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

我希望我的故事板使用这个类。我可以在 Interface Builder 中分配它:

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

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

【问题讨论】:

    标签: ios interface-builder


    【解决方案1】:

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

    解决方法是使用NSObject.load() 方法。

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

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

    您应该创建一个“虚拟”视图控制器,例如:

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

    并在情节提要中设置最后一个 ViewController。之后,为了让这个在 Objective-c 运行时中工作,你应该在你的 AppDelegate 或者这个控制器加载之前的某个地方添加以下内容。

    StartWasModelDetailsVC.load()
    

    希望对你有帮助!

    【讨论】:

    • 一年后。你搞定了。 :) 谢谢!
    • objective-C 运行时支持泛型。有所谓的轻量级泛型。我不知道这些是否真的可以与 Interface Builder 一起使用。
    • 在 iOS 13.3 上,不再需要 .load()。不过对于 iOS 12 仍然有用,谢谢!
    【解决方案2】:

    我刚刚遇到了同样的问题并想出了另一个解决方法:

    我只在代码中创建我的通用 UIViewController。然后在 IB 中,我在 XIB 文件中创建一个 UIView,该文件具有我需要的所有控件以及一个名为“mainView”的类,该类具有所有出口。

    在通用 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 的代码来连接事件。

    如果您不想做“仅代码”,这是一种混合,但在我的情况下,它似乎工作正常。它确实阻止了许多虚拟 ViewController 的创建,在我看来这有点违背泛型的想法。但这只是个人喜好问题,并不意味着以任何方式批评 Federico Ojeda 的解决方案。

    【讨论】:

      【解决方案3】:

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

      我的解决方案灵感来自:List all subclasses of one class

      通用 UIView 控制器可以在情节提要中工作,前提是您在使用它们之前在 Obj-C 运行时中注册它们。通过使用通用基础,我们可以完成这项工作。 所有 UIViewController 都是 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!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-02-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-12-28
        • 1970-01-01
        • 2012-08-12
        • 1970-01-01
        相关资源
        最近更新 更多