swiftgenerics

Keep type of generic for later reuse


What I want: A SwiftUI style .padding method in UIKit.

What I tried:

class Padding<T: UIView>: UIView {
    var base: T { viewToPad }
    
    let defaultPadding: UIEdgeInsets = .init(top: 8, left: 12, bottom: 8, right: 12)
    
    private var viewToPad: T
    
    init(view: T, padding: UIEdgeInsets? = nil) {
        self.viewToPad = view
        self.viewToPad.translatesAutoresizingMaskIntoConstraints = false
        
        super.init(frame: .zero)
        
        addSubview(viewToPad)
        
        self.viewToPad.attachToContainer(side: .top, constant: padding?.top ?? defaultPadding.top)
        self.viewToPad.attachToContainer(side: .bottom, constant: padding?.bottom ?? defaultPadding.bottom)
        self.viewToPad.attachToContainer(side: .left, constant: padding?.left ?? defaultPadding.left)
        self.viewToPad.attachToContainer(side: .right, constant: padding?.right ?? defaultPadding.right)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

extension UIView {
    func padded<T: UIView>(with padding: UIEdgeInsets? = nil) -> Padding<T>  {
        return Padding(view: self as! T, padding: padding)
    }
}

My issue: I can't make base be the actual T type:

let label = UILabel(text: "Instructions").padding()
label.base.textColor = UIColor.black // Value of type 'UIView' has no member 'textColor'

label.base is of type UIView here. Is there a way to make base be of type UILabel?

Thanks


Solution

  • Instead of being generic, padded should return Padding<Self>, but it cannot do that directly in a an extension of UIView. You can work around this by introducing a protocol, and extending that protocol.

    protocol Paddable: UIView {}
    extension UIView: Paddable {}
    
    extension Paddable {
        func padded(with padding: UIEdgeInsets? = nil) -> Padding<Self>  {
            return Padding(view: self, padding: padding)
        }
    }
    

    Now this is possible:

    let label = UILabel().padded()
    label.base.textColor = UIColor.black