Kamil Harasimowicz
Kamil Harasimowicz

Reputation: 5004

How to create IBDesignable custom view?

I am still improving my programing skills in iOS(Swift). Today I am thinking about creating a UIView subclasses in a good way. I want to create custom view which is:

I know such methods as init(withFrame) init() init(withDecoder) prepareForInterfacebuilder(), etc but I don't know how to use them to minimize redundancy of code.

For example here is my custom UIButton:

import UIKit

@IBDesignable
class SecurityButton: UIButton {

    @IBInspectable
    var color: UIColor = UIColor.black {
        didSet {
            setTitleColor(color, for: .normal)
            contentEdgeInsets = UIEdgeInsetsMake(0.0, 8.0, 0.0, 8.0)

            layer.borderWidth = 1.0
            layer.borderColor = color.cgColor
            layer.cornerRadius = 6.0
        }
    }
}

It works good but I know that contentEdgeInsets = UIEdgeInsetsMake(0.0, 8.0, 0.0, 8.0) layer.cornerRadius = 6.0 layer.borderWidth = 1.0 are called every I set a new color. Where should I place this custom setup to keep my requirements about custom view?

Edit

I do not looking for fix my example. I looking for a good place to initialize my custom view. Suppose that you need to do some time-consuming operations but only ones on create view.

Upvotes: 2

Views: 3971

Answers (2)

DonMag
DonMag

Reputation: 77690

You can create a "custom init" function to handle the setup tasks.

So, for example:

import UIKit

@IBDesignable
class SecurityButton: UIButton {

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }

    override func awakeFromNib() {
        super.awakeFromNib()
        commonInit()
    }

    func commonInit() {

        layer.borderWidth = 1.0
        layer.borderColor = color.cgColor
        layer.cornerRadius = 6.0

        _contentEdgeInsets = UIEdgeInsets(top: 0.0, left: 8.0, bottom: 0.0, right: 8.0)
        updateEdgeInsets()
    }

    private var _contentEdgeInsets: UIEdgeInsets = UIEdgeInsets.zero {
        didSet { updateEdgeInsets() }
    }
    override public var contentEdgeInsets: UIEdgeInsets {
        get { return super.contentEdgeInsets }
        set { _contentEdgeInsets = newValue }
    }

    private func updateEdgeInsets() {
        let initialEdgeInsets = contentEdgeInsets

        let t = _contentEdgeInsets.top + initialEdgeInsets.top
        let l = _contentEdgeInsets.left + initialEdgeInsets.left
        let b = _contentEdgeInsets.bottom + initialEdgeInsets.bottom
        let r = _contentEdgeInsets.right + initialEdgeInsets.right

        super.contentEdgeInsets = UIEdgeInsets(top: t, left: l, bottom: b, right: r)
    }

    @IBInspectable
    var color: UIColor = UIColor.black {
        didSet {
            setTitleColor(color, for: .normal)
        }
    }

}

Edit: Custom contentEdgeInsets have to be handled a little differently than most other properties.

@KamilHarasimowicz - Your "question edit" states "I do not looking for fix my example. I looking for a good place to initialize my custom view." ...

My initial answer did exactly that - it showed you where to initialize your custom view.

However, since your comment "storyboard don't render contentEdgeInsets in your solution" indicates that you actually DO want your example fixed (otherwise you would have just fixed it yourself), I have edited my answer to do so.

Upvotes: 3

Jippe Joosten
Jippe Joosten

Reputation: 413

You are missing the function

self.setNeedsDisplay()

Your code should look as follows

import UIKit

@IBDesignable
class SecurityButton: UIButton {

    @IBInspectable
    var color: UIColor = UIColor.black {
        didSet {
            setTitleColor(color, for: .normal)
            contentEdgeInsets = UIEdgeInsetsMake(0.0, 8.0, 0.0, 8.0)

            layer.borderWidth = 1.0
            layer.borderColor = color.cgColor
            layer.cornerRadius = 6.0

            self.setNeedsDisplay()
        }
    }
}

This will call the draw function that will update the storyboard view. The function should be called as last in the didSet Now it will show up after you set the color.

I tested it and It works for me in xcode, after I set the value color in the storyboard.

If you want it to draw always not only when u set a color by the inspector you can use the draw(rect) function it will look following

import UIKit

@IBDesignable
class SecurityButton: UIButton {

    @IBInspectable
    var color: UIColor = UIColor.black

    override func draw(_ rect: CGRect) {
        setTitleColor(color, for: .normal)
        contentEdgeInsets = UIEdgeInsetsMake(0.0, 8.0, 0.0, 8.0)

        layer.borderWidth = 1.0
        layer.borderColor = color.cgColor
        layer.cornerRadius = 6.0

        setNeedsDisplay()
    }
}

This will draw the border always, not only when you set in in the interface inspector.

Upvotes: 2

Related Questions