Matej Ukmar
Matej Ukmar

Reputation: 2245

How to properly refactor first phase of object initialisation in Swift?

So I learned in Swift we should use two-phase initialisation: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html

One of the rules is: "An initializer cannot call any instance methods, read the values of any instance properties, or refer to self as a value until after the first phase of initialization is complete."

This prevents placing chunks of code in instance methods, so I guess class methods should be used? What do you think? Any alternative solutions?

To illustrate what I mean here are 3 code samples:

Unrefactored code:

@objc class MYChatBarButton: UIBarButtonItem {

    let badgeLabel: UILabel


    init(target: AnyObject, selector: Selector) {

        let button = UIButton.buttonWithType(.Custom) as UIButton
        button.frame = CGRectMake(0, 0, 35, 44)
        button.addTarget(target, action: selector, forControlEvents: .TouchUpInside)
        button.tintColor = UIColor.whiteColor()
        button.setImage(UIImage(named: "chat_icon")?.imageWithRenderingMode(.AlwaysTemplate), forState: .Normal)
        button.imageEdgeInsets = UIEdgeInsets(top: 0, left: 7, bottom: 5, right: 0)

        badgeLabel = UILabel(frame: CGRectMake(0, 0, 15, 15))
        badgeLabel.backgroundColor = UIColor.clearColor()
        badgeLabel.textColor = UIColor.whiteColor()
        badgeLabel.textAlignment = .Center
        badgeLabel.text = "0"
        badgeLabel.userInteractionEnabled = false

        button.addSubview(badgeLabel)


        super.init(customView: button)


    }


}

The way I expected to do refactoring but it doesn't work:

@objc class MYChatBarButton: UIBarButtonItem {

    let badgeLabel: UILabel


    init(target: AnyObject, selector: Selector) {

        let button = createButton(target, selector: selector)
        createBadgeLabel()
        button.addSubview(badgeLabel)

        super.init(customView: button)


    }

    func createButton(target: AnyObject, selector: Selector) -> UIButton {
        let button = UIButton.buttonWithType(.Custom) as UIButton
        button.frame = CGRectMake(0, 0, 35, 44)
        button.addTarget(target, action: selector, forControlEvents: .TouchUpInside)
        button.tintColor = UIColor.whiteColor()
        button.setImage(UIImage(named: "chat_icon")?.imageWithRenderingMode(.AlwaysTemplate), forState: .Normal)
        button.imageEdgeInsets = UIEdgeInsets(top: 0, left: 7, bottom: 5, right: 0)
        return button
    }

    func createBadgeLabel() {
        badgeLabel = UILabel(frame: CGRectMake(0, 0, 15, 15))
        badgeLabel.backgroundColor = UIColor.clearColor()
        badgeLabel.textColor = UIColor.whiteColor()
        badgeLabel.textAlignment = .Center
        badgeLabel.text = "0"
        badgeLabel.userInteractionEnabled = false
    }

}

The way it should probably be done (but I don't like it so very much):

@objc class MYChatBarButton: UIBarButtonItem {

    let badgeLabel: UILabel


    init(target: AnyObject, selector: Selector) {

        let button = MYChatBarButton.createButton(target, selector: selector)
        badgeLabel = MYChatBarButton.createBadgeLabel()
        button.addSubview(badgeLabel)

        super.init(customView: button)


    }

    class func createButton(target: AnyObject, selector: Selector) -> UIButton {
        let button = UIButton.buttonWithType(.Custom) as UIButton
        button.frame = CGRectMake(0, 0, 35, 44)
        button.addTarget(target, action: selector, forControlEvents: .TouchUpInside)
        button.tintColor = UIColor.whiteColor()
        button.setImage(UIImage(named: "chat_icon")?.imageWithRenderingMode(.AlwaysTemplate), forState: .Normal)
        button.imageEdgeInsets = UIEdgeInsets(top: 0, left: 7, bottom: 5, right: 0)
        return button
    }

    class func createBadgeLabel() -> UILabel {
        let bl = UILabel(frame: CGRectMake(0, 0, 15, 15))
        bl.backgroundColor = UIColor.clearColor()
        bl.textColor = UIColor.whiteColor()
        bl.textAlignment = .Center
        bl.text = "0"
        bl.userInteractionEnabled = false
    }

}

Upvotes: 4

Views: 831

Answers (1)

rintaro
rintaro

Reputation: 51911

At least, you can isolate the badgeLabel creation:

@objc class MYChatBarButton: UIBarButtonItem {

    let badgeLabel: UILabel = {
        let bl = UILabel(frame: CGRectMake(0, 0, 15, 15))
        bl.backgroundColor = UIColor.clearColor()
        bl.textColor = UIColor.whiteColor()
        bl.textAlignment = .Center
        bl.text = "0"
        bl.userInteractionEnabled = false
        return bl
    }()

    let button: UIButton = {
        let button = UIButton.buttonWithType(.Custom) as UIButton
        button.frame = CGRectMake(0, 0, 35, 44)
        button.tintColor = UIColor.whiteColor()
        button.setImage(UIImage(named: "chat_icon")?.imageWithRenderingMode(.AlwaysTemplate), forState: .Normal)
        button.imageEdgeInsets = UIEdgeInsets(top: 0, left: 7, bottom: 5, right: 0)
        return button
    }()

    init(target: AnyObject, selector: Selector) {
        button.addTarget(target, action: selector, forControlEvents: .TouchUpInside)
        button.addSubview(badgeLabel)
        super.init(customView: button)
    }

I don't know how to do this without property.

Upvotes: 2

Related Questions