How to have a generic property on a class?

In my app, I aimplemented a class that is initialized with an instance of UIView. After initialization, this instance is then exposed as a property on the class.

After initializing this class, I would like the call site to be able to call functions on that exposed property based on the actual type. E.g. if it is a label, the callsite should be able to set a title, if it is a button, it should be able to call -setTitleColor:forState:, etc)


Here is the current implementation of my class:

class EmbeddingView: UIView
{
    let embeddedView: UIView
    let insets: UIEdgeInsets
    required init(embeddedView: UIView, 
                        insets: UIEdgeInsets)
    {
        self.insets = insets
        self.embeddedView = embeddedView
        super.init(frame: CGRect.zero)
        self.addSubview(embeddedView)
        
        // setup constraints on the embeddedView using the insets
        // ... 
    }
    
    required init?(coder aDecoder: NSCoder)
    {
        fatalError("init(coder:) has not been implemented")
    }
}



At the call site, I would like to be able to do things like this:

let embeddedLabel = EmbeddingView(embeddedView: UILabel(), insets: UIEdgeInsets.zero)
embeddedLabel.embeddedView.title = "42"
        
let embeddedButton = EmbeddingView(embeddedView: UIButton(type: .system), insets: UIEdgeInsets.zero)
embeddedLabel.embeddedView.setTitleColor(UIColor.blue, for: .normal)



So I changed the implementation to use generics, (indicated the places where I made changes, lines 1, 3 and 5)

class EmbeddingView <T: UIView>: UIView //// note the addition here
{
    let embeddedView: T // note the change here
    let insets: UIEdgeInsets
    required init(embeddedView: T, // note the change here
                  insets: UIEdgeInsets)
    {
        self.insets = insets
        self.embeddedView = embeddedView
        super.init(frame: CGRect.zero)
        self.addSubview(embeddedView)
        
        // setup constraints on the embeddedView
        // ...
    }
    
    required init?(coder aDecoder: NSCoder)
    {
        fatalError("init(coder:) has not been implemented")
    }
}

Yet, at the call sites, I get compilation errors saying:

"Value of type 'UILabel' has no member 'title'" and

"Value of type 'UILabel' has no member 'setTitleColor'"


How can I get the compiler to understand what is the real type of the instance that the EmbeddingView was initialized with?

Answered by OOPer in 336458022

As far as I tested your code in the Xcode 10 Playground, Swift infers the generic type `T` as expected.

let embeddedLabel = EmbeddingView(embeddedView: UILabel(), insets: UIEdgeInsets.zero)
print(type(of: embeddedLabel)) //->EmbeddingView<UILabel>


But your testing code has some simple mistakes:

embeddedLabel.embeddedView.title = "42"

The type of `embeddedLabel.embeddedView` is `UILabel` as expected, but `UILabel` has no property named `title`.

I guess you may want to write something like this:

embeddedLabel.embeddedView.text = "42"


And this line:

embeddedLabel.embeddedView.setTitleColor(UIColor.blue, for: .normal)

Again UILabel has no method named `setTitleColor(_:for:)`.

Isn't `embededButton` that you want to use in your seconde case?

embeddedButton.embeddedView.setTitleColor(UIColor.blue, for: .normal)
Accepted Answer

As far as I tested your code in the Xcode 10 Playground, Swift infers the generic type `T` as expected.

let embeddedLabel = EmbeddingView(embeddedView: UILabel(), insets: UIEdgeInsets.zero)
print(type(of: embeddedLabel)) //->EmbeddingView<UILabel>


But your testing code has some simple mistakes:

embeddedLabel.embeddedView.title = "42"

The type of `embeddedLabel.embeddedView` is `UILabel` as expected, but `UILabel` has no property named `title`.

I guess you may want to write something like this:

embeddedLabel.embeddedView.text = "42"


And this line:

embeddedLabel.embeddedView.setTitleColor(UIColor.blue, for: .normal)

Again UILabel has no method named `setTitleColor(_:for:)`.

Isn't `embededButton` that you want to use in your seconde case?

embeddedButton.embeddedView.setTitleColor(UIColor.blue, for: .normal)

Oh man... Thank you for pointing out those mistakes. Been staring at this code for almost an hour. Time to get some sleep I guess 🙂.

How to have a generic property on a class?
 
 
Q