Swift 2.0中使用故事板的通用控制器

我试图为我的应用程序创build一个GenericListController。

我有一个ProductListController扩展这个扩展UIViewController的通用控制器。 我已经将ProductListController连接到一个故事板并创build了2个sockets,但是我总是收到这个错误:

Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<UIViewController 0x7c158ca0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key searchBar.' 

我收到所有我的网点这个错误,如果我从GenericListController中删除genericsT它的作品。 我想故事板不能加载generics的超级。 我怎样才能使它工作?

我的代码:

 class GenericListController<T> : UIViewController { var list : [T] = [T]() var filteredlist : [T] = [T]() func getData(tableView : UITableView) { ..... } func setData(list : [T], tableView : UITableView) { ..... } override func viewDidLoad() { super.viewDidLoad() } } class ProductListController : GenericListController<ProductModel> { @IBOutlet weak var searchBar: UISearchBar! @IBOutlet weak var tableView: UITableView! override func viewDidLoad() { super.viewDidLoad() getData(tableView) } } 

– 编辑 –

我发现,如果我扩展一个generics类,并尝试将类添加到故事板xcode不会自动完成类名(可能是因为它不能检测类)

这回答了为什么它是不可能的: 在接口构build器中使用generics类作为自定义视图

接口生成器通过ObjC运行时“与您的代码进行对话”。 因此,IB可以只访问在ObjC运行时中可表示的代码的特征。 ObjC不会做generics

这提示了一个可能的解决方法: obj-c中的generics也许你可以在obj-c中创build一个通用的ViewController,然后IB会接受它?

你有没有考虑过使用协议? 这并不扰乱故事板。 改变了一下代码,使其易于testing。 这个缺点是你不能在协议中存储属性。 所以你仍然需要复制粘贴这些。 上行是它的工作原理。

 protocol GenericListProtocol { typealias T var list : [T] { get set } var filteredlist : [T] { get set } func setData(list : [T]) } extension GenericListProtocol { func setData(list: [T]) { list.forEach { item in print(item) } } } class ProductModel { var productID : Int = 0 init(id:Int) { productID = id } } class ProductListController: UIViewController, GenericListProtocol { var list : [ProductModel] = [ProductModel(id: 1),ProductModel(id: 2),ProductModel(id: 3),ProductModel(id: 4)] var filteredlist : [ProductModel] = [] override func viewDidLoad() { super.viewDidLoad() setData(list) } } 

更新:允许对generics类的属性进行一些访问。 把它改成了一个基本的类,以方便在游乐场进行testing。 UIViewController的东西在上面的代码中。

 class ProductModel { var productID : Int = 0 init(id:Int) { productID = id } } class ProductA : ProductModel { var aSpecificStuff : Float = 0 } class ProductB : ProductModel { var bSpecificStuff : String = "" } protocol GenericListProtocol { typealias T = ProductModel var list : [T] { get set } var filteredlist : [T] { get set } func setData(list : [T]) } extension GenericListProtocol { func setData(list: [T]) { list.forEach { item in guard let productItem = item as? ProductModel else { return } print(productItem.productID) } } } class ProductListController: GenericListProtocol { var list : [ProductA] = [ProductA(id: 1),ProductA(id: 2),ProductA(id: 3),ProductA(id: 4)] var filteredlist : [ProductA] = [] init() { setData(list) } } var test = ProductListController() 

正如@ r-menke所述:

接口生成器通过ObjC运行时“与您的代码进行对话”。 因此,IB可以只访问在ObjC运行时中可表示的代码的特征。 ObjC不会做generics

这是真的,

但是,根据我的经验,我们可以解决这个问题(YMMV)。

我们可以在这里做一个人为的例子,看看它是如何失败的:

 class C<T> {} class D: C<String> {} print(NSClassFromString("main.D")) 

在这里运行示例:

http://swiftstub.com/878703680

你可以看到它打印nil

现在让我们稍微调整一下,然后再试一次:

http://swiftstub.com/346544378

 class C<T> {} class D: C<String> {} print(NSClassFromString("main.D")) let _ = D() print(NSClassFromString("main.D")) 

我们得到这个:

nil Optional(main.D)

嘿-O! 它在第一次初始化之后发现它。

让我们把这个应用到故事板。 我正在应用程序中(正确或错误地)

 // do the initial throw away load let _ = CanvasController(nibName: "", bundle: nil) // Now lets load the storyboard let sb = NSStoryboard(name: "Canvas", bundle: nil) let canvas = sb.instantiateInitialController() as! CanvasController myView.addSubView(canvas.view) 

按照您的预期工作。 在我的情况下,我的CanvasController声明如下:

 class CanvasController: MyNSViewController<SomeGeneric1, SomeGeneric2> 

现在,我已经遇到了一些问题在iOS上使用这种技术与通用的UITableView子类。 我没有尝试过,所以在YMMV的iOS 9下。 但是我现在正在为10.11下的应用程序做这个工作,并没有遇到任何重大问题。 这并不是说我今后不会遇到任何问题,或者说这样做甚至是适当的,我不能说自己知道这个问题的全部影响。 我只能说现在看起来似乎解决了这个问题。

8月4日我在这里提交了一个radr:#22133133我没有看到它在开放RADR,但在bugreport.apple.com至less列在我的帐户下,无论什么是值得的。

生病发布了一些代码,我已经在用户R menke和其他人的帮助下实现了。 我的目标是有一个GenericListProtocol,它可以处理UISearchBarDelegate,UITableViewDelegate和我的getData方法(它需要一个类类才能正确parsingjson。

 import Foundation import UIKit protocol GenericListProtocol : UISearchBarDelegate, UITableViewDelegate{ typealias T : MyModel // MyModel is a model i use for getId, getDate... var list : [T] { get set } var filteredlist : [T] { get set } var searchActive : Bool { get set } func setData(tableView : UITableView, myList : [T]) func setData() func getData(tableView : UITableView, objectType : T, var myList : [T]) func filterContentForSearchText(searchText: String) } extension GenericListProtocol { func setData(atableView : UITableView, myList : [T]) { print("reloading tableView data") atableView.reloadData() } func getData(tableView : UITableView, objectType : T, var myList : [T]) { let dao: GenericDao<T> = GenericDao<T>() let view : UIView = UIView() let c: CallListListener<T> = CallListListener<T>(view: view, loadingLabel: "loading", save: true, name: "ProductModel") c.onSuccess = { (onSuccess: JsonMessageList<T>) in print("status " + onSuccess._meta!.status!) // this is from my ws myList = onSuccess.records self.setData(tableView, myList: myList) } c.onFinally = { (any: AnyObject) in // tableView.stopPullToRefresh() } // my dao saves json list on NSUSER, so we check if its already downloaded let savedList = c.getDefaultList() if (savedList == nil) { dao.getAll(c); } else { myList = savedList! print(String(myList.count)) self.setData(tableView, myList: myList) } } func searchBarTextDidBeginEditing(searchBar: UISearchBar) { searchActive = true; } func searchBarTextDidEndEditing(searchBar: UISearchBar) { searchActive = false; } func searchBarCancelButtonClicked(searchBar: UISearchBar) { searchActive = false; } func searchBarSearchButtonClicked(searchBar: UISearchBar) { searchActive = false; } func searchBar(searchBar: UISearchBar, textDidChange searchText: String) { print("searching") self.filterContentForSearchText(searchText) if(filteredlist.count == 0){ searchActive = false; } else { searchActive = true; } self.setData() } } 

虽然我能够实现大多数UISearchBarDelegate,UITableViewDelegate方法,我仍然需要在我的默认类中实现其中的2个:

 import Foundation import UIKit import EVReflection import AlamofireJsonToObjects class ProductListController : GenericListController, GenericListProtocol { @IBOutlet weak var searchBar: UISearchBar! @IBOutlet weak var tableView: UITableView! var list : [ProductModel] = [ProductModel]() var filteredlist : [ProductModel] = [ProductModel]() var searchActive : Bool = false override func setInit() { self.searchBar.delegate = self self.listName = "ProductModel" self.setTableViewStyle(self.tableView, searchBar4 : self.searchBar) getData(self.tableView, objectType: ProductModel(), myList: self.list) } // this method hasnt worked from extension, so i just pasted it here func searchBar(searchBar: UISearchBar, textDidChange searchText: String) { self.filterContentForSearchText(searchText) if(filteredlist.count == 0){ searchActive = false; } else { searchActive = true; } self.setData(self.tableView, myList: list) } // this method hasnt worked from extension, so i just pasted it here func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if searchActive { return self.filteredlist.count } else { return self.list.count } } // self.list = myList hasnt worked from extension, so i just pasted it here func setData(atableView: UITableView, myList : [ProductModel]) { print(String(myList.count)) self.list = myList print(String(self.list.count)) self.tableView.reloadData() } // i decided to implement this method because of the tableView func setData() { self.tableView.reloadData() } // this method hasnt worked from extension, so i just pasted it here func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell:GenericListCell = tableView.dequeueReusableCellWithIdentifier("cell") as! GenericListCell var object : ProductModel if searchActive { object = filteredlist[indexPath.row] } else { object = list[indexPath.row] } cell.formatData(object.name!, subtitle: object.price ?? "valor", char: object.name!) print("returning cell") return cell } override func viewDidLoad() { // searchFuckinBar.delegate = self super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } func filterContentForSearchText(searchText: String) { // Filter the array using the filter method self.filteredlist = self.list.filter({( object: ProductModel) -> Bool in // let categoryMatch = (scope == "All") || (object.category == scope) let stringMatch = object.name!.lowercaseString.rangeOfString(searchText.lowercaseString) return (stringMatch != nil) }) } func formatCell(cell : GenericListCell, object : ProductModel) { cell.formatData(object.name!, subtitle: object.price ?? "valor", char: object.name!) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } 

* GenericListController只是一个UIViewController与一些辅助方法

作为一种解决方法,您可以将您的ProductListController加载到ObjC运行时(即AppDelegate?),然后使用Storyboard进行实例化。

 ProductListController.load() 

干杯