Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic controller in swift 2.0 using storyboards

Tags:

Im trying to create a GenericListController for my app.

I have a ProductListController that extend this generic controller which extends UIViewController. I have connected ProductListController to a storyboard and made 2 outlets, but i always receive this error:

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

I receive this error for all my outlets, if i remove the generic T from GenericListController it works. I guess a storyboard cant load a super with generics. How can i make it work?

My 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)      } } 

--EDIT--

I have found that if i extend an generic class and try to add the class to an storyboard xcode wont autocomplete the class name (probably because it cant detect the class)

like image 923
Renato Probst Avatar asked Sep 29 '15 05:09

Renato Probst


People also ask

What are view controllers in Swift and how do they work?

Remember that view controllers in Swift manage two roles: they controls views on a page or part of a page, as well as navigation between pages. Creating a multi-page app requires at least one view controller per screen.

How do I create a single view project in Swift?

Start a new single view project in Swift called SwiftProgNavControllerDemo . Go into the storyboard and select the blank view controller. Be sure to select the controller and not the view.

What is a programmatic navigation controller in Swift?

Swift Swift: Programmatic Navigation View Controllers in Swift. Navigation controllers are the workhorse of organizing view controllers. I’ve covered much of their use in other posts about MVC, segues and delegates. In this chapter, We’ll go through some of the Swift code for the Navigation controller.

Where can I find the view controller in the storyboard?

Now click SecondExample or whatever you called it, and follow the right arrows until you get to Main.Storyboard (Base). Click it: You should now see the View Controller in the storyboard:


2 Answers

This answers why it is not possible : use a generic class as a custom view in interface builder

Interface Builder "talks" to your code through the ObjC runtime. As such, IB can can access only features of your code that are representable in the ObjC runtime. ObjC doesn't do generics

This hint at a possible work around : generics in obj-c Maybe you can create a generic ViewController in obj-c and then IB will accept it?

Have you considered using a protocol? This doesn't upset the storyboard. Changed the code a bit to make it easily testable. The downside of this is that you can't have stored properties in a protocol. So you would still need to copy paste those. Upside is that it works.

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)                 } } 

Update: Allow some access to attributes to the generic class. Changed it to a basic class to easily test in a Playground. UIViewController stuff is in the code above.

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() 
like image 71
R Menke Avatar answered Jan 04 '23 14:01

R Menke


As @r-menke stated above:

Interface Builder "talks" to your code through the ObjC runtime. As such, IB can can access only features of your code that are representable in the ObjC runtime. ObjC doesn't do generics

This is true,

In my experience, however, we can get around the issue as follows (YMMV).

We can make a contrived example here and see how this fails:

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

Running example here:

http://swiftstub.com/878703680

You can see that it prints nil

Now lets tweak this slightly and try again:

http://swiftstub.com/346544378

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

We get this:

nil Optional(main.D)

Hey-o! It found it AFTER it was initialized the first time.

Lets apply this to storyboards. I am doing this in an application right now (rightly or wrongly)

// 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) 

Works as you would expect. In my case my CanvasController is declared as follows:

class CanvasController: MyNSViewController<SomeGeneric1, SomeGeneric2> 

Now, I have run into some issues on iOS using this technique with a generic UITableView subclass. I have not tried it under iOS 9 so YMMV. But I am currently doing this under 10.11 for an app I am working on, and have not run into any major issues. That is not to say that I won't run into any issue in the future, or that this is even appropriate to do I cannot claim to know the full ramifications of this. All I can say is that, for now, it appears to get around the issue.

I filed a radr on this back on Aug 4: #22133133 I don't see it in open RADR, but under bugreport.apple.com it's at least listed under my account, whatever that's worth.

like image 40
AJ Venturella Avatar answered Jan 04 '23 14:01

AJ Venturella