2

I have created a custom button in PaintCode. PC has lots of documentation on creating graphics, but not using them.

My method works but it has problems which I'll get to... I went the route of subclassing a UIButton which I placed in my storyboard. I then assigned it the class of my custom button, we'll call it customButton. Using this method you can connect an action in IB, and the highlighted state is handled by the touchesBegan and touchesEnded methods in tandem with a variable that toggles the highlighted view, but the problem is, the highlighted state is never displayed on quick touches.

customButton.m:

@interface customButton ()

@property BOOL isPressed;

@end

@implementation customButton

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
    }
    return self;
}

-(void) awakeFromNib {
    [super awakeFromNib];
    _buttonText = @"Post";
}

- (void)drawRect:(CGRect)rect
{
    [StyleKit drawCustomButtonWithFrame:rect pressed:_isPressed buttonText:_buttonText];
}

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    _isPressed = YES;
    [self setNeedsDisplay];
}

- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    [self sendActionsForControlEvents:UIControlEventTouchUpInside];
    _isPressed = NO;
    [self setNeedsDisplay];
}

My question: is there a better way to implement a button drawn with PaintCode? The issue with this one is that it doesn't always display the highlighted state, and feels kinda hacky. Surely there's a better way?

skaffman
  • 398,947
  • 96
  • 818
  • 769
inorganik
  • 24,255
  • 17
  • 90
  • 114
  • 1
    You are sending Touch Up Inside events but you're not checking that the touch is inside the button when it ends. – Fogmeister Nov 13 '14 at 17:44
  • Also, You'd probably be better off just using UIButton and an image. – Fogmeister Nov 13 '14 at 17:52
  • 1
    @Fogmeister the point is to use a PaintCode button (as clearly stated in the question) - whose graphics are drawn with code, and are resolution independent. – inorganik Nov 13 '14 at 20:30
  • Yeah, I get that. I just think that you would be better with a standard button and an image. It's resolution independent already. Resizable. Performs better. No drawing calculation. Has more complete support for different actions. Etc... I did like the look of paint code and started writing a tutorial blog for it. I just think there are better ways to do what it's trying to do. – Fogmeister Nov 13 '14 at 22:28
  • @Fogmeister if you use an image, it is not resolution independent. You've got to create @ 2x and 3x versions of it. The point of PaintCode is that it's vector, and limitlessly scalable. There are also other benefits, like reusable symbols, parametric designs, and design behavior based on expressions. Maybe you should give it another look. – inorganik Nov 14 '14 at 04:52
  • @inorganik actually you can achieve resolution independence by not using 2x, 3x, etc. PNGs anymore and using PDFs instead. Fog is right, drawing code is slow (whether it comes from Paintcode or not), images are faster. – bpapa Jan 05 '15 at 05:03

2 Answers2

6

The best way to go about this is to override the highlighted property in UIControl. It is the most accurate indicator of the button's state. I am using swift, but it's trivial to translate to ObjC:

class VectorizedButton: UIButton {
    override var highlighted: Bool {
        didSet {
            setNeedsDisplay()
        }
    }
}

Now, instead of passing in _isPressed, just pass in highlighted (or [self highlighted]).

For the sake of completeness:

- (void)setHighlighted:(BOOL)isHigh
{
    [super setHighlighted:isHigh];
    [self setNeedsDisplay];
}

It seems there is a good article about this here.


Complete code sample:

NOTE I took it a step further, and highlight if the button is not enabled or highlighted.

class VectorizedButton: UIButton {

    override var highlighted: Bool {
        didSet {
            setNeedsDisplay()
        }
    }

    // MARK: - Init & Dealloc

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

    override func awakeFromNib() {
        super.awakeFromNib()

        backgroundColor = UIColor.clearColor()
    }

    // MARK: - Private methods

    private var shouldHighlight: Bool {
        return highlighted || !enabled
    }

    // MARK: - Public methods

    override func drawRect(rect: CGRect) {
        StyleKit.drawMyButton(frame: bounds, highlighted: shouldHighlight)
    }

}
Mazyod
  • 22,319
  • 10
  • 92
  • 157
  • Are you suggesting to call the overrided `setHighlighted` method from `touchesBegan` and `touchesEnded`? Just want to be clear. – inorganik Jan 04 '15 at 23:07
  • @inorganik No. The whole point is to have UIKit handle the touch events, and set the `highlighted` property for us. Indeed, UIKit sets highlighted to `true` on touch began, and back to `false` on touch ended. After that is set, we simply trigger a redraw (`setNeedsDisplay`) and pass `highlighted`. – Mazyod Jan 05 '15 at 04:52
2

Thanks, @Mazyod. That really helps.

Here is a Swift 3 project based on @Mazyod's answer:

https://github.com/backslash-f/paint-code-ui-button

(The PaintCode project is attached there).

import UIKit

@IBDesignable class CustomUIButton: UIButton {

    // MARK: Properties

    override var isHighlighted: Bool {
        didSet {
            setNeedsDisplay()
        }
    }

    override var isSelected: Bool {
        didSet {
            setNeedsDisplay()
        }
    }

    override var isEnabled: Bool {
        didSet {
            setNeedsDisplay()
        }
    }

    // MARK: Lifecycle

    override func draw(_ rect: CGRect) {
        StyleKit.drawWatchButton(frame: rect, highlighted: isHighlighted)
    }
}

demo

backslash-f
  • 7,923
  • 7
  • 52
  • 80