Best practices for object organisation in a Swift file

There are several ways you could reorganize your code. For example, you could make add a convenience init to UIButton:

extension UIButton {
    fileprivate convenience init(drawButtonWithTarget target: Any?, action: Selector) {
        self.init()
        setImage(UIImage(named: "pen")?.withRenderingMode(.alwaysTemplate), for: .normal)
        imageView?.tintColor = .white
        imageView?.layer.shadowColor = UIColor.black.cgColor
        imageView?.layer.shadowOffset = CGSize(width: 0, height: 2)
        imageView?.layer.shadowOpacity = 1
        addTarget(target, action: action, for: .touchUpInside)
        translatesAutoresizingMaskIntoConstraints = false
    }
}

Then creating drawButton looks like this:

let drawButton = UIButton(drawButtonWithTarget: self, action: #selector(pen))

Or you could give UIButton a convenience init that takes a closure to configure the button. I do it like this:

protocol Withable {
    init()
}

extension Withable {
    init(with body: (inout Self) -> ()) {
        self.init()
        body(&self)
    }
}

extension UIButton: Withable { }

This lets you shorten the definition of drawButton slightly:

let drawButton = UIButton {
    $0.setImage(UIImage(named: "pen")?.withRenderingMode(.alwaysTemplate), for: .normal)
    $0.imageView?.tintColor = .white
    $0.imageView?.layer.shadowColor = UIColor.black.cgColor
    $0.imageView?.layer.shadowOffset = CGSize(width: 0, height: 2)
    $0.imageView?.layer.shadowOpacity = 1
    $0.addTarget(self, action: #selector(pen), for: .touchUpInside)
    $0.translatesAutoresizingMaskIntoConstraints = false
}

If you're setting a lot of layer shadows, you might want to factor out that code. Here's one way:

struct LayerShadow {
    var opacity: Float
    var radius: CGFloat
    var offset: CGSize
    var color: UIColor?
    var path: CGPath?

    init(opacity: Float = 0, radius: CGFloat = 3, offset: CGSize = CGSize(width: 0, height: -3), color: UIColor? = .black, path: CGPath? = nil) {
        self.opacity = opacity
        self.radius = radius
        self.offset = offset
        self.color = color
        self.path = path
    }
}

extension CALayer {
    var shadow: LayerShadow {
        get {
            return .init(opacity: shadowOpacity, radius: shadowRadius, offset: shadowOffset, color: shadowColor.map { UIColor(cgColor: $0) }, path: shadowPath)
        }
        set {
            shadowOpacity = shadow.opacity
            shadowRadius = shadow.radius
            shadowOffset = shadow.offset
            shadowColor = shadow.color?.cgColor
            shadowPath = shadow.path
        }
    }
}

Then you can reduce setting the shadow to this line:

imageView?.layer.shadow = .init(opacity: 1, offset: .init(width: 0, height: 2))
2 Likes