A recent project of ours required some custom drawing components. We spent a fair amount of time creating them, so we thought we’d save you the effort and share them with everyone.
What follows should serve as a beginners tutorial for iOS custom drawing.
We’ll start with a simple one - a button with a border
This one should be reasonably straightforward - you use the border properties on the UIButton’s layer property.
To make it a bit more interesting, we’ll use IBInspectable so that you can change the border colour inside a nib/storyboard. The header file for our button is defined as:
IB_DESIGNABLE @interface BTBorderedButton : UIButton @property (strong, nonatomic) IBInspectable UIColor *borderColor; @end
The implementation file starts with custom init methods to set the default values:
- (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self commonInit]; } return self; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { [self commonInit]; } return self; } - (void)commonInit { _borderColor = [UIColor colorWithRed:69/255.0f green:78/255.0f blue:87/255.0f alpha:1.0f]; self.layer.borderWidth = 1.0f; self.layer.borderColor = _borderColor.CGColor; self.layer.cornerRadius = 4.0f; }
We then override the setter for the borderColor
property so that changes get applied to the layer:
- (void)setBorderColor:(UIColor *)borderColor { _borderColor = borderColor; self.layer.borderColor = borderColor.CGColor; }
The next button gives us a gradient fill colour. You could add this to the bordered button above, but in this instance we’ll keep it separate for clarity.
Similar to the bordered button, we start with our header file and custom init code in the implementation file: BTGradientFillButton.h
IB_DESIGNABLE @interface BTGradientFillButton : UIButton @property (strong, nonatomic) IBInspectable UIColor *startColor; @property (strong, nonatomic) IBInspectable UIColor *endColor; @end BTGradientFillButton.m - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self commonInit]; } return self; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { [self commonInit]; } return self; } - (void)commonInit { _startColor = [UIColor colorWithRed:33/255.0f green:43/255.0f blue:53/255.0f alpha:1.0f]; _endColor = [UIColor colorWithWhite:1.0f alpha:1.0f]; }
We also override the setters for our 2 properties to ensure that the button gets updated when they change:
- (void)setStartColor:(UIColor *)startColor { _startColor = startColor; [self setNeedsDisplay]; } - (void)setEndColor:(UIColor *)endColor { _endColor = endColor; [self setNeedsDisplay]; }
The functionality for this one is implemented inside drawRect:
- (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB(); NSArray *colours = @[(id)self.startColor.CGColor, (id)self.endColor.CGColor]; CGGradientRef gradient = CGGradientCreateWithColors(baseSpace, (__bridge CFArrayRef)colours, NULL); CGPoint center = CGPointMake(CGRectGetWidth(self.frame) / 2, CGRectGetHeight(self.frame) / 2); CGContextDrawRadialGradient(context, gradient, center, 0.0f, center, CGRectGetWidth(self.frame), kCGGradientDrawsBeforeStartLocation); CGGradientRelease(gradient), gradient = NULL; CGColorSpaceRelease(baseSpace), baseSpace = NULL; }
In this example we’re using CGContextDrawRadialGradient
. You may also like to look at CGContextDrawLinearGradient
.
Another fairly simple one that doesn’t require much code is drawing lines.
- (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetStrokeColorWithColor(context, [UIColor colorWithRed:69/255.0f green:78/255.0f blue:87/255.0f alpha:1.0f].CGColor); CGContextSetLineWidth(context, 2.0f); // top CGContextMoveToPoint(context, CGRectGetMinX(rect), CGRectGetMinY(rect)+1); CGContextAddLineToPoint(context, CGRectGetMaxX(rect), CGRectGetMinY(rect)+1); CGContextStrokePath(context); // right CGContextMoveToPoint(context, CGRectGetMaxX(rect)-1, CGRectGetMinY(rect)+1); CGContextAddLineToPoint(context, CGRectGetMaxX(rect)-1, CGRectGetMaxY(rect)); CGContextStrokePath(context); }
This code starts by setting the colour and width then draws 2 lines. You can have as many calls as you want to draw multiple lines.
This one is done differently to the previous examples - instead of using drawRect
we add a new layer inside layoutSubviews
.
In this example we’ve created a CAGradientLayer
that transitions from a start colour, to a different colour in the middle then back to the start colour.
You can add more colours to the gradient if you wish, and use the locations property to specify the points to transition from one colour to the next (the default is to spread the transitions out evenly across the layer).
- (void)layoutSubviews { [super layoutSubviews]; // Check if we've already added the gradient layer on a previous layout pass for (CALayer *layer in self.layer.sublayers) { if ([layer isKindOfClass:[CAGradientLayer class]]) return; } UIColor *endColour = [UIColor redColor]; UIColor *midColour = [UIColor blueColor]; CAGradientLayer *gradient = [CAGradientLayer layer]; gradient.frame = self.bounds; gradient.colors = @[(id)endColour.CGColor, (id)midColour.CGColor, (id)endColour.CGColor]; gradient.startPoint = CGPointMake(0, 0.5); gradient.endPoint = CGPointMake(1, 0.5); [self.layer insertSublayer:gradient atIndex:0]; }
All this code can be found on GitHub at https://github.com/brightec/CustomDrawing
Search over 400 blog posts from our team
Subscribe to our monthly digest of blogs to stay in the loop and come with us on our journey to make things better!