Im essayant de créer un GenericListController pour mon application.
J'ai un ProductListController qui étend ce controller générique qui étend UIViewController. J'ai connecté ProductListController à un storyboard et fait 2 sockets, mais je reçois toujours cette erreur:
Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<UIViewController 0x7c158ca0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key searchBar.'
Je reçois cette erreur pour tous mes points de vente, si je supprime le T générique de GenericListController cela fonctionne. Je suppose qu'un storyboard ne peut pas charger un super avec des generics. Comment puis-je le faire fonctionner?
Mon code:
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) } }
–MODIFIER–
J'ai trouvé que si je prolonge une class générique et essaye d'append la class à un storyboard xcode ne complètera pas automatiquement le nom de class (probablement parce qu'il ne peut pas détecter la class)
Cela explique pourquoi cela n'est pas possible: utilisez une class générique en tant que vue personnalisée dans le constructor d'interface
Interface Builder "parle" à votre code via l'environnement d'exécution ObjC. En tant que tel, IB ne peut accéder qu'aux fonctionnalités de votre code qui sont représentables dans le runtime ObjC. ObjC ne fait pas de generics
Cet indice à un possible travail autour de: generics dans obj-c Peut – être que vous pouvez créer un ViewController générique dans obj-c, puis IB va l'accepter?
Avez-vous envisagé d'utiliser un protocole? Cela ne perturbe pas le storyboard. Changé le code un peu pour le rendre facilement testable. L'inconvénient de ceci est que vous ne pouvez pas avoir de propriétés stockées dans un protocole. Donc, vous auriez toujours besoin de copyr les coller. Upside est que cela fonctionne.
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) } }
Mise à jour: Autoriser l'access aux attributes à la class générique. Changé à une class de base pour tester facilement dans un terrain de jeu. UIViewController est dans le code ci-dessus.
class ProductModel { var productID : Int = 0 init(id:Int) { productID = id } } class ProductA : ProductModel { var aSpecificStuff : Float = 0 } class ProductB : ProductModel { var bSpecificStuff : Ssortingng = "" } 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()
Comme @ r-menke a déclaré ci-dessus:
Interface Builder "parle" à votre code via l'environnement d'exécution ObjC. En tant que tel, IB ne peut accéder qu'aux fonctionnalités de votre code qui sont représentables dans le runtime ObjC. ObjC ne fait pas de generics
C'est vrai,
Dans mon expérience, cependant, nous pouvons contourner le problème comme suit (YMMV).
Nous pouvons faire un exemple artificiel ici et voir comment cela échoue:
class C<T> {} class D: C<Ssortingng> {} print(NSClassFromSsortingng("main.D"))
Exemple courant ici:
http://swiftstub.com/878703680
Vous pouvez voir qu'il imprime nil
Maintenant, modifions légèrement ceci et réessayez:
http://swiftstub.com/346544378
class C<T> {} class D: C<Ssortingng> {} print(NSClassFromSsortingng("main.D")) let _ = D() print(NSClassFromSsortingng("main.D"))
Nous obtenons ceci:
nil Optional(main.D)
Hey-o! Il l'a trouvé APRÈS qu'il a été initialisé la première fois.
Permet d'appliquer cela aux storyboards. Je le fais dans une application en ce moment (à tort ou à raison)
// 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)
Fonctionne comme prévu Dans mon cas, mon CanvasController
est déclaré comme suit:
class CanvasController: MyNSViewController<SomeGeneric1, SomeGeneric2>
Maintenant, j'ai rencontré quelques problèmes sur iOS en utilisant cette technique avec une sous-class générique UITableView. Je ne l'ai pas essayé sous iOS 9 donc YMMV. Mais je suis en train de faire cela sous 10.11 pour une application sur laquelle je travaille, et je n'ai pas rencontré de problèmes majeurs. Cela ne veut pas dire que je ne rencontrerai aucun problème à l'avenir, ou que c'est même approprié de le faire, je ne peux pas prétendre en connaître toutes les ramifications. Tout ce que je peux dire, c'est que, pour l'instant, il semble que cela contourne le problème.
J'ai déposé un radr sur ce return le 4 août: # 22133133 Je ne le vois pas dans RADR ouvert, mais sous bugreport.apple.com il est au less listé sous mon count, quoi que ça vaille.
Je post un peu de code que j'ai obtenu avec l'aide de l'user R menke et d'autres. Mon but est d'avoir un GenericListProtocol qui peut gérer UISearchBarDelegate, UITableViewDelegate et ma méthode getData (qui a besoin d'une class de type pour pouvoir parsingr correctement json.
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: Ssortingng) } 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(Ssortingng(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: Ssortingng) { print("searching") self.filterContentForSearchText(searchText) if(filteredlist.count == 0){ searchActive = false; } else { searchActive = true; } self.setData() } }
Bien que j'ai pu implémenter la plupart des methods UISearchBarDelegate, UITableViewDelegate, je dois encore en implémenter 2 sur ma class par défaut:
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: Ssortingng) { 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(Ssortingng(myList.count)) self.list = myList print(Ssortingng(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: Ssortingng) { // Filter the array using the filter method self.filteredlist = self.list.filter({( object: ProductModel) -> Bool in // let categoryMatch = (scope == "All") || (object.category == scope) let ssortingngMatch = object.name!.lowercaseSsortingng.rangeOfSsortingng(searchText.lowercaseSsortingng) return (ssortingngMatch != 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 est juste un UIViewController avec quelques methods auxiliaires
Pour contourner le ProductListController
vous pouvez simplement charger votre ProductListController
dans l'environnement d'exécution ObjC (par exemple AppDelegate?) Avant de l'instancier avec le Storyboard.
ProductListController.load()
À votre santé