Generic Wrapper Types in Swift

The other day I needed to add a gradient to a view hierarchy in a UIKit app. I wanted the ease of use of CAGradientLayer but the Auto Layout interactions of UIView. So, I dutifully wrote a quick little wrapper around CAGradientLayer:

class GradientView: UIView {

    override class var layerClass: AnyClass { CAGradientLayer.self }

    var gradientLayer: CAGradientLayer? { layer as? CAGradientLayer }

}

This works fine, but to set the options particular to CAGradientLayer, you need to call through to the layer:

myGradientView.gradientLayer?.colors = [UIColor.white.cgColor, UIColor.black.cgcolor]

It’s easy enough to write UIKit wrappers around the properties. Or I can make this generic:

class CustomLayerHost<LayerType: CALayer>: UIView {

    override class var layerClass: AnyClass { LayerType.self }

    var layerCast: LayerType? { layer as? LayerType }

}

What I would really like to do is pass the call to colors along to the layer automatically, like so:

let myGradientView = CustomLayerHost<CAGradientLayer>()
myGradientView.colors = [UIColor.white.cgColor, UIColor.black.cgcolor]

This doesn’t appear to be possible with @dynamicMemberLookup. Is there some mechanism—kind of like Objective-C’s respondsToSelector: family of methods—to do runtime replacement of this method with CAGradientLayer’s?

Did you try subscripts with keypaths from the wrapped object as key?

This works for me:

@dynamicMemberLookup
class CustomLayerHost<LayerType: CALayer>: UIView {
    override class var layerClass: AnyClass { LayerType.self }
    var layerCast: LayerType? { layer as? LayerType }
    
    subscript<T>(dynamicMember keyPath: ReferenceWritableKeyPath<LayerType, T?>) -> T? {
        get {
            return layerCast?[keyPath: keyPath]
        }
        set {
            layerCast?[keyPath: keyPath] = newValue
        }
    }
}

class Test {
    func setColors() {
        let myGradientView = CustomLayerHost<CAGradientLayer>()
        myGradientView.colors = [UIColor.white.cgColor, UIColor.black.cgColor]
    }
}

2 Likes

Thanks! The part I was missing was using ReferenceWritableKeyPath. @dynamicMemberLookup is still magic to me. :grinning:

1 Like