Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use a generic class as a custom view in Interface Builder

I have a custom subclass of UIButton:

import UIKit

@IBDesignable
class MyButton: UIButton {

    var array : [String]?

}

It is IBDesignable and I have set it as the custom class for one of the buttons in my storyboard. I would like to make it generic so that the array does not have to be one of String objects. So, I tried this:

import UIKit

@IBDesignable
class MyButton<T>: UIButton {

    var array : [T]?

}

However, I am unsure how to set this as the class now in IB. I tried putting MyButton<String> or MyButton<Int>, but Interface Builder just removes the angle brackets portion and gets the following compile error:

Command /Applications/Xcode6-Beta4.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift failed with exit code 254

Is there a way to use a generic custom class, or is it not supported?

like image 819
golddove Avatar asked Aug 12 '14 12:08

golddove


3 Answers

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, so there's not a way for IB to tell the runtime which specialization of your generic class to use. (And a Swift generic class can't be instantiated without a specialization, so you get an error trying to instantiate a MyButton instead of a MyButton<WhateverConcreteType>.)

(You can recognize the ObjC runtime at work in IB when you break other things: Attempting to use a pure Swift class with outlets/actions in a nib/storyboard gives runtime errors about ObjC introspection. Leaving an outlet connected whose corresponding code declaration has changed or gone away gives runtime errors about KVC. Et cetera.)

To ignore the runtime issues and put it in a slightly different way... let's go back to what IB needs to know. Remember that the nib loading system is what instantiates anything in a storyboard at runtime. So even if the parts of your class that take a generic type parameter aren't @IBInspectable, the nib still needs to know what specialization of your generic class to load. So, for IB to let you use generic classes for views, IB would have to have a place for you to identify which specialization of your class the nib should use. It doesn't — but that'd make a great feature request.

In the meantime, if it's still helpful for your MyButton class to involve some generic storage, perhaps you could look into having MyButton reference another class that includes generic storage. (It'd have to do so in such a way that the type of said storage isn't known at compile time, though — otherwise the type parameters of that other class would have to propagate back to MyButton.)

like image 126
rickster Avatar answered Nov 13 '22 01:11

rickster


I had the same problem as first I wanted to use the following structure :

import UIKit

protocol MyDataObjectProtocol{
   var name:String { get}
}

class MyObjectTypeOne:MyDataObjectProtocol{
    var name:String { get { return "object1"}}
}

class MyObjectTypeTwo:MyDataObjectProtocol{
    var name:String { get { return "object2"}}
}

class SpecializedTableViewControllerOne: GenericTableViewController<MyObjectTypeOne> {
}

class SpecializedTableViewControllerTwo: GenericTableViewController<MyObjectTypeTwo> {
}

class GenericTableViewController<T where T:MyDataObjectProtocol>: UITableViewController {
    // some common code I don't want to be duplicated in 
    // SpecializedTableViewControllerOne and SpecializedTableViewControllerTwo
    // and that uses object of type MyDataObjectProtocol.
}

But as explained in the previous answer, this is not possible. So I managed to work it around with the following code :

import UIKit

protocol MyDataObjectProtocol{
    var name:String { get}
}

class MyObjectTypeOne:MyDataObjectProtocol{
    var name:String { get { return "object1"}}
}

class MyObjectTypeTwo:MyDataObjectProtocol{
    var name:String { get { return "object2"}}
}

class SpecializedTableViewControllerOne: GenericTableViewController {
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        super.object = MyObjectTypeOne()
    }
}

class SpecializedTableViewControllerTwo: GenericTableViewController {
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        super.object = MyObjectTypeTwo()
    }
}

class GenericTableViewController : UITableViewController {
    var object : MyDataObjectProtocol?

    @IBOutlet weak var label: UILabel!

    override func viewDidLoad() {
        label.text = object?.name
    }


    // some common code I don't want to be duplicated in 
    // SpecializedTableViewControllerOne and SpecializedTableViewControllerTwo
    // and that uses object of type MyDataObjectProtocol.
}

Now my label outlet displays "object1" when I link the SpecializedTableViewControllerOne as a custom class of my view controller in the storyboard and "object2" if I link to SpecializedTableViewControllerTwo

like image 2
Nicolas Massart Avatar answered Nov 13 '22 01:11

Nicolas Massart


It is possible to use generic UIView in Interface Builder. Let's say you have:

class A<T>: UIView {}

First you have to inherit your generic view to make it non-generic:

class B: A<String> {}

Go ahead and use B class in Interface Builder.

After it you can make @IBOutlet connection in your code:

@IBOutlet var b: B

It will produce error, so to get rid of it make it use UIView class instead, and downcast it like so:

@IBOutlet var b: UIView

var _b: B {
    b as! B
}

Done, now your _b variable is type of B, and it works.

like image 1
Simon Moshenko Avatar answered Nov 13 '22 00:11

Simon Moshenko