Search code examples
iosswiftuibuttonframecgrect

Incorrect drawRect frame relative to UIButton frame


I am attempting to add status indicators to some UIButtons, nuzzling it right into the spot the blue indicator is in the following screenshot. I first calibrated by creating the first indicator line of code from scratch using minY and minX to use the UIButton origin as a reference point, then copied it for the other two indicators thinking it should translate but it seems it has not.

//In header/property declaration
    @IBOutlet weak var thirdButton: UIButton!
    @IBOutlet weak var secondButton: UIButton!
    @IBOutlet weak var firstButton: UIButton!

    var capicheIndicator: UIView!
    var secondIndicator: UIView!
    var thirdIndicator: UIView!

// In viewDidLoad
        let firstButtonFrame = firstButton.frame
        self.capicheIndicator = DrawBlueCircle(frame: CGRect(x: firstButtonFrame.minX + 15, y: firstButtonFrame.minY + 4, width: 11, height: 11))
        self.view.addSubview(capicheIndicator)

        let secondButtonFrame = secondButton.frame
        self.secondIndicator = DrawYellowCircle(frame: CGRect(x: secondButtonFrame.minX + 15, y: secondButtonFrame.minY + 4, width: 11, height: 11))
        self.view.addSubview(secondIndicator)

        let thirdButtonFrame = thirdButton.frame
        self.thirdIndicator = DrawRedCircle(frame: CGRect(x: thirdButtonFrame.minX + 15, y: thirdButtonFrame.minY + 4, width: 11, height: 11))
        self.view.addSubview(thirdIndicator).

This is how I draw each indicator, with the circle color fill line of code being the only change between DrawBlueCircle, DrawYellowCircle, and DrawRedCircle.

class DrawBlueCircle: UIView {

    override func drawRect(rect: CGRect)
    {
        let contextRef = UIGraphicsGetCurrentContext()

        CGContextClearRect(contextRef, rect)

        //Set the border width
        CGContextSetLineWidth(contextRef, 1.5)

        //Set the circle fil
        CGContextSetRGBFillColor(contextRef, 0/255, 122/255, 1, 1)

        CGContextFillEllipseInRect(contextRef, rect)
    }
}

However unfortunately, only the blue indicator is getting drawn correctly. enter image description here

In my attempts to debug I printed to the console every step of the way as shown by the following code, but the code and the screen somehow do not match.

 let firstButtonFrame = firstButton.frame
    print(firstButtonFrame)
    print(firstButtonFrame.minY)
    self.capicheIndicator = DrawBlueCircle(frame: CGRect(x: firstButtonFrame.minX + 15, y: firstButtonFrame.minY + 4, width: 11, height: 11))
    print(capicheIndicator.frame)
    self.view.addSubview(capicheIndicator)

    let secondButtonFrame = secondButton.frame
    print(secondButtonFrame)
    print(secondButtonFrame.minY)
    self.secondIndicator = DrawYellowCircle(frame: CGRect(x: secondButtonFrame.minX + 15, y: secondButtonFrame.minY + 4, width: 11, height: 11))
    print(secondIndicator.frame)
    self.view.addSubview(secondIndicator)

    let thirdButtonFrame = thirdButton.frame
    print(thirdButtonFrame)
    print(thirdButtonFrame.minY)
    self.thirdIndicator = DrawRedCircle(frame: CGRect(x: thirdButtonFrame.minX + 15, y: thirdButtonFrame.minY + 4, width: 11, height: 11))
    print(thirdIndicator.frame)
    self.view.addSubview(thirdIndicator)

And the console output:

(36.0, 381.0, 528.0, 40.0)
381.0
(51.0, 385.0, 11.0, 11.0)
(36.0, 431.0, 528.0, 40.0)
431.0
(51.0, 435.0, 11.0, 11.0)
(36.0, 481.0, 528.0, 40.0)
481.0
(51.0, 485.0, 11.0, 11.0)

Solution

  • This is done so much more easily by subclassing UIView and adding a button. Just add the indicator as a subview. Try this in Playground:

    class IndicatorButton: UIView {
    
        let indicatorView: UIView = {
            let indicator = UIView(frame: CGRectMake(0,0,10,10))
            indicator.layer.cornerRadius = 5.0
            indicator.clipsToBounds = true
            indicator.backgroundColor = UIColor.clearColor()
            return indicator
        }()
    
        var indicatorColor: UIColor = UIColor.clearColor() {
            didSet {
                indicatorView.backgroundColor = indicatorColor
                self.layer.borderColor = indicatorColor.CGColor
                self.button.setTitleColor(indicatorColor, forState: .Normal)
            }
        }
    
        let button: UIButton = {
            let button = UIButton(type: .Custom)
            return button
        }()
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            self.layer.borderWidth = 2.0
            self.layer.borderColor = indicatorColor.CGColor
            self.layer.cornerRadius = 8.0
            self.clipsToBounds = true
            button.frame = CGRectMake(0, 0, frame.size.width, frame.size.height)
            button.autoresizingMask = [.FlexibleHeight, .FlexibleWidth]
            button.titleEdgeInsets = UIEdgeInsetsMake(0,15,0,0)
            self.addSubview(button)
            self.addSubview(indicatorView)
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
    
        override func layoutSubviews() {
            super.layoutSubviews()
            indicatorView.frame = CGRectMake(10, (self.bounds.size.height-10) / 2.0, 10,10)
        }
    
    }
    

    Use it like this:

    let b = IndicatorButton(frame: CGRectMake(0,0,100,30))
    b.indicatorColor = UIColor.redColor()
    b.button.setTitle("Start", forState: .Normal)
    b
    

    Result:

    Button