Best practices for object organisation in a Swift file

Hi guys,
I am a long time Swift coder and currently I have a project where one ViewController has a lot of objects. As I do everything in code, every object takes up to 10 lines of code like you can see here:

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

I have all of them at the beginning of my Swift file but I'd like to put them either in a separate file or maybe at the bottom of the file? I saw someone a while ago on Reddit I think who declared everything as lazy var at the end of the file (not sure why lazy var)...

Thanks in advance for your recommendations and best practices!
Dave

1 Like

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
Terms of Service

Privacy Policy

Cookie Policy