2

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:

  • @IBDesignable - I want to see a render of my view in storyboard/xib;
  • Creatable from code

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.

mfaani
  • 33,269
  • 19
  • 164
  • 293
Kamil Harasimowicz
  • 4,684
  • 5
  • 32
  • 58

2 Answers2

2

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.

Jippe Joosten
  • 403
  • 1
  • 3
  • 16
2

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.

DonMag
  • 69,424
  • 5
  • 50
  • 86
  • storyboard don't render contentEdgeInsets in your solution :( – Kamil Harasimowicz Mar 17 '17 at 15:27
  • Is your goal to *restrict* the button to `contentEdgeInsets` of `(0.0, 8.0, 0.0, 8.0)`? Or do you want `contentEdgeInsets` as set in IB *plus* 8.0 on left and right? – DonMag Mar 17 '17 at 17:25
  • In this case I want to see this edges in storyboard preview but my goal is to find a place in custom view where I can put all of changes of my custom view. Please check my question edit. – Kamil Harasimowicz Mar 23 '17 at 15:26