我有这个视图控制器,用于以通用方式显示模型对象的详细信息:
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.
这是故事板的记录限制吗?有没有办法可以/应该指定通用,以便故事板可以链接到它?
答案 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!