How can I draw textured button in iOS? - ios

I would like to draw textured button in iOS app. I would not use Photoshop rather CoreGraphics because I want a general solution that works with buttons that has different size. What is the basic principle of it? What technology do I need?
My first step was to subclass a UIButton, but how can I add i.e. bevel effect to it?

The best approach will be subclassing you button class(your new class must inherit from UIButton). by doing this you can give some customized details too to your button.. Below is a sample ..
//interface of custom button class
#interface myButton : UIButton
{
int orderNo;
CAGradientLayer *gradientLayer;
CALayer *wrapperLayer;
CGColorRef _borderColor;
}
#property (assign) int orderNo;
#property (nonatomic) CGColorRef _borderColor;
#property (nonatomic, retain) CALayer *wrapperLayer;
#property (nonatomic, retain) CAGradientLayer *gradientLayer;
- (void)setBorderColor:(CGColorRef)color;
- (void)setCornerRadius:(float)radius;
The implementation..
#import "Button.h"
#implementation Button
#synthesize orderNo,wrapperLayer,_borderColor,gradientLayer;
- (id)initWithFrame:(CGRect)frame
{if ((self = [super initWithFrame:frame])) {
CGFloat radius = self.bounds.size.width / 2;
CGFloat borderWidth = self.bounds.size.width / 10;
self.layer.backgroundColor = [[UIColor blackColor] CGColor];
self.layer.borderColor = [[UIColor whiteColor] CGColor];
self.layer.borderWidth = borderWidth;
self.layer.cornerRadius = radius;
if ([self.layer respondsToSelector:#selector(setShadowOffset:)])
self.layer.shadowOffset = CGSizeMake(0.25, 0.25);
if ([self.layer respondsToSelector:#selector(setShadowColor:)])
self.layer.shadowColor = [[UIColor blackColor] CGColor];
if ([self.layer respondsToSelector:#selector(setShadowRadius:)])
self.layer.shadowRadius = borderWidth;
if ([self.layer respondsToSelector:#selector(setShadowOpacity:)])
self.layer.shadowOpacity = 0.75;
[self setNeedsDisplay];
}
return self;
}
- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetShouldAntialias(ctx, true);
CGFloat xsize = self.bounds.size.width / 6;
CGFloat borderWidth = self.bounds.size.width / 10;
CGContextSaveGState(ctx);
CGContextSetLineCap(ctx, kCGLineCapRound);
CGContextSetLineWidth(ctx, borderWidth);
CGContextSetStrokeColorWithColor(ctx, [[UIColor whiteColor] CGColor]);
CGFloat width = self.bounds.size.width;
CGPoint start1 = CGPointMake(width / 2 - xsize, width / 2 - xsize);
CGPoint end1 = CGPointMake(width / 2 + xsize, width / 2 + xsize);
CGPoint start2 = CGPointMake(width / 2 + xsize, width / 2 - xsize);
CGPoint end2 = CGPointMake(width / 2 - xsize, width / 2 + xsize);
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, start1.x, start1.y);
CGContextAddLineToPoint(ctx, end1.x, end1.y);
CGContextStrokePath(ctx);
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, start2.x, start2.y);
CGContextAddLineToPoint(ctx, end2.x, end2.y);
CGContextStrokePath(ctx);
CGContextRestoreGState(ctx);
}
- (void)setBorderColor:(CGColorRef)color
{
[[self layer] setBorderColor:color];
[[self layer] setNeedsDisplay];
}
- (void)setCornerRadius:(float)radius
{
[[self layer] setCornerRadius:radius];
// and get the wrapper for the gradient layer too
[wrapperLayer setCornerRadius:radius];
}
#end

Related

Circle with Vertical Dashed Line in Border

I am trying to draw a circle with vertical dashed line in border of circle.
I have tried like this, but it making dots in circle.
CAShapeLayer *circle = [CAShapeLayer layer];
circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius) cornerRadius:radius].CGPath;
circle.fillColor = [UIColor clearColor].CGColor;
circle.strokeColor = [UIColor redColor].CGColor;
circle.lineWidth = 1;
circle.lineDashPattern = #[#2, #3];
[[self.view layer] addSublayer:circle];
Please try using this below code, it worked for me.
1) Take UIView subClass, and in .h file implement below code
#import <UIKit/UIKit.h>
#interface ViewClass : UIView
#property (nonatomic) NSUInteger numberOfGraduations;
#property (nonatomic) CGFloat arcDegreeStart;
#property (nonatomic) CGFloat arcDegreeEnd;
#property (nonatomic) CGPoint arcCenter;
#property (nonatomic) CGFloat arcRadius;
#property (nonatomic) CGFloat deltaArc;
-(void)drawGraduation:(CGPoint )center Radius:(CGFloat)radius Angle:(CGFloat)angle Length:(CGFloat)length Width:(CGFloat)width colorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue;
#end
2) Now in your.m file, implement below code
#import "ViewClass.h"
#implementation ViewClass
{
CGContextRef c;
}
-(void)drawGraduation:(CGPoint )center Radius:(CGFloat)radius Angle:(CGFloat)angle Length:(CGFloat)length Width:(CGFloat)width colorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue{
c = UIGraphicsGetCurrentContext();
CGFloat radius2 = radius+length; // The radius of the end points of the graduations
CGPoint p1 = (CGPoint){cos(angle)*radius+center.x, sin(angle)*radius+center.y}; // the start point of the graduation
CGPoint p2 = (CGPoint){cos(angle)*radius2+center.x, sin(angle)*radius2+center.y}; // the end point of the graduation
CGContextMoveToPoint(c, p1.x, p1.y);
CGContextAddLineToPoint(c, p2.x, p2.y);
CGContextSetLineCap(c, kCGLineCapRound);
CGContextSetRGBStrokeColor(c, red, green, blue, 1.0);
CGContextSetLineWidth(c, width);
CGContextSetBlendMode(c,kCGBlendModeNormal);
CGContextStrokePath(c);
}
3) Below is the drawRect method..
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGRect r = self.bounds;
_numberOfGraduations = 31;
_arcCenter = (CGPoint){r.size.width*0.5, r.size.height*0.5}; // center of arc
_arcRadius = (r.size.width*0.5)-20; // radius of arc
CGFloat maxGraduationWidth = 1.0;
CGFloat maxGraduationWidthAngle = maxGraduationWidth/_arcRadius; // the maximum graduation width angle (used to prevent the graduations from being stroked outside of the main arc)
_arcDegreeStart =-M_PI*0.2;
_arcDegreeEnd = -M_PI*0.8;
// draw graduations
_deltaArc = (_arcDegreeEnd-_arcDegreeStart+maxGraduationWidthAngle)/(_numberOfGraduations-1); // the change in angle of the arc
for (int i = 0; i < _numberOfGraduations; i++) {
[self drawGraduation:_arcCenter Radius:_arcRadius Angle:_arcDegreeStart+(i*_deltaArc) Length:14 Width:1 colorWithRed:0.0 green:0.0 blue:1.0];
}
}

How to get rid of thin border in a UIView rounded with layer.cornerRadius in iOS 7.1.1?

I have a customized UIView that rounds its corners by using layer.cornerRadius like this:
- (void)layoutSubviews
{
[super layoutSubviews];
self.layer.cornerRadius = self.frame.size.width / 2.0;
self.layer.borderColor = [UIColor whiteColor].CGColor;
self.layer.borderWidth = 2.0;
self.layer.masksToBounds = YES;
self.backgroundColor = [UIColor redColor];
}
How can I get rid of the very thin outer red circle?
Well, a more efficient way to do what you need would be to copy paste this function in the .m file of your custom view and call it in the drawRect method:
- (void)drawRoundedViewWithFrame: (CGRect)frame color:(UIColor *)color
{
//// roundCircle Drawing
UIBezierPath* roundCirclePath = [UIBezierPath bezierPathWithOvalInRect: frame];
[color setFill];
[roundCirclePath fill];
[UIColor.whiteColor setStroke];
roundCirclePath.lineWidth = 2;
[roundCirclePath stroke];
}
Based on an answer in here, I figured out a way to do this with these steps:
Set the UIView background color to [UIColor clearColor]
Manually draw a smaller circular background in drawRect:
Here's the drawRect: implementation:
- (void)drawRect:(CGRect)rect
{
CGFloat margin = _borderWidth;
CGRect background = CGRectMake(margin, margin,
self.bounds.size.width - 2 * margin,
self.bounds.size.height - 2 * margin);
CGContextRef context = UIGraphicsGetCurrentContext();
[_internalBackgroundColor set];
CGContextFillEllipseInRect(context, background);
}
And the overwritten backgroundColor setter:
#property (nonatomic, strong) UIColor *internalBackgroundColor;
...
- (void)setBackgroundColor:(UIColor *)backgroundColor
{
[super setBackgroundColor:[UIColor clearColor]];
_internalBackgroundColor = backgroundColor;
}
**** Faster solution: ****
As Kujey pointed out, it's better not to use the layer.cornerRadius at all. Here's the drawRect: solution without the layer access:
- (void)drawRect:(CGRect)rect
{
CGFloat margin = _borderWidth / 2.0;
CGRect background = CGRectMake(margin, margin, self.bounds.size.width - 2 * margin, self.bounds.size.height - 2 * margin);
CGContextRef context = UIGraphicsGetCurrentContext();
[_internalBackgroundColor set];
CGContextFillEllipseInRect(context, background);
[_borderColor set];
CGContextSetLineWidth(context, _borderWidth);
CGContextStrokeEllipseInRect(context, background);
}

UILabel subclass doesn't show text

I've subclassed UILabel class to override drawRect: method. The label in IB is connected with the created subclass. It appears on the screen, but it doesn't show any text, whether it it is set in IB or programmatically. The label itself appears on the screen, when I'm logging it's text property, it shows OK.
Here's my code:
MyLabel.h
#import <UIKit/UIKit.h>
#interface MyLabel : UILabel
#end
MyLabel.m
#import "MyLabel.h"
#import <QuartzCore/QuartzCore.h>
#implementation MyLabel
- (void)drawRect:(CGRect)rect {
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = UIGraphicsGetCurrentContext();
UIColor *borderColor = [UIColor colorWithWhite: .1f alpha: 5.f];
UIColor *topColor = [UIColor colorWithWhite: .3f alpha: 5.f];
UIColor *bottomColor = [UIColor colorWithWhite: .5f alpha: 5.f];
UIColor *innerGlow = [UIColor colorWithWhite: 1.f alpha: .25f];
NSArray *gradientColors = #[(id)topColor.CGColor, (id)bottomColor.CGColor];
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef) gradientColors, NULL);
CGFloat bWidth = self.frame.size.width;
CGFloat bHeight = self.frame.size.height;
UIBezierPath *roundedRectanglePath = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(0, 0, bWidth, bHeight)
cornerRadius: 0];
[roundedRectanglePath addClip];
CGGradientRef background = gradient;
CGContextDrawLinearGradient(context, background, CGPointMake(bWidth / 2.f, 0), CGPointMake(bWidth / 2.f, bHeight), 0);
[borderColor setStroke];
roundedRectanglePath.lineWidth = 6;
[roundedRectanglePath stroke];
CGFloat glowRadius = 3.f;
UIBezierPath *innerGlowRect = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(glowRadius, glowRadius, bWidth - 2 * glowRadius, bHeight - 2 * glowRadius) cornerRadius: 0];
[innerGlow setStroke];
innerGlowRect.lineWidth = 3;
[innerGlowRect stroke];
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);
}
#end
Maybe I need to override drawTextInRect: method, but I don't know how.
Thanks for help!
You need to call:
[super drawRect:rect];
At the top or your drawRect: method. If you need more control over how the string looks you'll need to draw it your self with the NSString string drawing methods.

RGB Values retrieved from Pixels incorrect

I've rendered a circular gradient and created a method that lets me sweep over it with my finger, using a Pan gesture recognizer.
I am retrieving the pixel at my current touch position and want to retrieve it's color.
This means, the color value should constantly update while moving over the gradient.
i'm using the following code :
- (IBAction)handlePan:(UIPanGestureRecognizer *)sender {
CGPoint translation = [sender translationInView:iv];
[sender setTranslation:CGPointZero inView:self.view];
CGPoint center = sender.view.center;
center.x += translation.x;
center.y += translation.y;
sender.view.center = center;
CGPoint colorPoint = [sender.view.superview convertPoint:center toView:iv];
[sender setTranslation:CGPointMake(0, 0) inView:self.view];
CGImageRef image = img.CGImage;
NSUInteger width = CGImageGetWidth(image);
NSUInteger height = CGImageGetHeight(image);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
char *rawData = malloc(height * width * 4);
int bytesPerPixel = 4;
int bytesPerRow = bytesPerPixel * width;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(
rawData, width, height, bitsPerComponent, bytesPerRow, colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big
);
CGContextSetBlendMode(context, kCGBlendModeCopy);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), image);
CGContextRelease(context);
int byteIndex = (bytesPerRow * colorPoint.y) + colorPoint.x * bytesPerPixel;
unsigned char red = rawData[byteIndex];
unsigned char green = rawData[byteIndex+1];
unsigned char blue = rawData[byteIndex+2];
UIColor *hauptfarbe = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
ch.backgroundColor = hauptfarbe;
NSLog(#"Color value - R : %i G : %i : B %i",red, green, blue);
}
this doesn't work as intended, giving me wrong colors and not showing some colors (like red) at all
EDIT : I cannot add a picture yet due to low rep. i will now add the code for rendering the gradient
Code :
- (void)viewDidLoad
{
[super viewDidLoad];
CGSize size = CGSizeMake(self.view.bounds.size.width, self.view.bounds.size.height);
UIGraphicsBeginImageContextWithOptions(CGSizeMake(size.width, size.height), YES, 0.0);
[[UIColor whiteColor] setFill];
UIRectFill(CGRectMake(0, 0, size.width, size.height));
int sectors = 180;
float radius = MIN(size.width, size.height)/2;
float angle = 2 * M_PI/sectors;
UIBezierPath *bezierPath;
for ( int i = 0; i < sectors; i++)
{
CGPoint center = CGPointMake((size.width/2), (size.height/2));
bezierPath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:i * angle endAngle:(i + 1) * angle clockwise:YES];
[bezierPath addLineToPoint:center];
[bezierPath closePath];
UIColor *color = [UIColor colorWithHue:((float)i)/sectors saturation:1. brightness:1. alpha:1];
[color setFill];
[color setStroke];
[bezierPath fill];
[bezierPath stroke];
}
img = UIGraphicsGetImageFromCurrentImageContext();
iv = [[UIImageView alloc] initWithImage:img];
[self.view addSubview:iv];
[self.view addSubview:ch];
}
The first problem here is the way you're calculating colorPoint. They way it is now, colorPoint will always be the center point of the view. This handlePan: method should get you the point of the last touch in the view:
- (IBAction)handlePan:(UIPanGestureRecognizer *)sender
{
if (sender.numberOfTouches)
{
CGPoint lastPoint = [sender locationOfTouch: sender.numberOfTouches - 1 inView: sender.view];
NSLog(#"lastPoint: %#", NSStringFromCGPoint(lastPoint));
}
}
From there, I would probably recommend that instead of blitting the image into a bitmap context and then attempting to read back from it at that point, that you just calculate the color for the point using the same mathematical process you used to create the image in the first place. The way you're doing it now is going to be much more CPU + memory intensive.
EDIT: Here's what I came up with. It works for me. Starting from the Single View Application template in Xcode, and using the code you posted, I have the following code in ViewController.m:
#implementation ViewController
{
UIImage* img;
UIImageView* iv;
}
- (void)viewDidLoad
{
[super viewDidLoad];
CGSize size = CGSizeMake(self.view.bounds.size.width, self.view.bounds.size.height);
UIGraphicsBeginImageContextWithOptions(CGSizeMake(size.width, size.height), YES, 0.0);
[[UIColor whiteColor] setFill];
UIRectFill(CGRectMake(0, 0, size.width, size.height));
int sectors = 180;
float radius = MIN(size.width, size.height)/2;
float angle = 2 * M_PI/sectors;
UIBezierPath *bezierPath;
for ( int i = 0; i < sectors; i++)
{
CGPoint center = CGPointMake((size.width/2), (size.height/2));
bezierPath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:i * angle endAngle:(i + 1) * angle clockwise:YES];
[bezierPath addLineToPoint:center];
[bezierPath closePath];
UIColor *color = [UIColor colorWithHue:((float)i)/sectors saturation:1. brightness:1. alpha:1];
[color setFill];
[color setStroke];
[bezierPath fill];
[bezierPath stroke];
}
img = UIGraphicsGetImageFromCurrentImageContext();
iv = [[UIImageView alloc] initWithImage:img];
[self.view addSubview:iv];
colorView = [[UIView alloc] init];
colorView.frame = CGRectMake(CGRectGetMaxX(bounds) - 25, CGRectGetMaxY(bounds) - 25, 20, 20);
[self.view addSubview:colorView];
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc]
initWithTarget:self action:#selector(handlePan:)];
[self.view addGestureRecognizer: panGesture];
}
- (IBAction)handlePan:(UIPanGestureRecognizer *)sender
{
if (sender.numberOfTouches)
{
CGPoint lastPoint = [sender locationOfTouch: sender.numberOfTouches - 1 inView: sender.view];
CGRect bounds = self.view.bounds;
CGPoint center = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
CGPoint delta = CGPointMake(lastPoint.x - center.x, lastPoint.y - center.y);
CGFloat angle = (delta.y == 0 ? delta.x >= 0 ? 0 : M_PI : atan2(delta.y, delta.x));
angle = fmod(angle, M_PI * 2.0);
angle += angle >= 0 ? 0 : M_PI * 2.0;
UIColor *color = [UIColor colorWithHue: angle / (M_PI * 2.0) saturation:1. brightness:1. alpha:1];
colorView.backgroundColor = color;
CGFloat r,g,b,a;
if ([color getRed: &r green: &g blue:&b alpha: &a])
{
NSLog(#"Color value - R : %g G : %g : B %g", r, g, b);
}
}
}
#end
What I see is that as I drag my finger over the gradient, I get a steady stream of messages to console with RGB values that correspond to the location of my finger. I've also added code to show the last color in a small view in the lower right. It also doesn't use bitmap contexts. Hope this helps.

iOS Brush Hardness like Photoshop

How to get the following brush smoothness(hardness) effect like photoshop?
My attempt:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, 30);
CGContextSetStrokeColorWithColor(context, [UIColor colorWithRed:1.0f green:0.0f blue:0.0f alpha:0.5f].CGColor);
CGContextSetShadowWithColor(context, CGSizeMake(0, 0), 20.0f, [UIColor colorWithRed:1.0f green:0.0f blue:0.0f alpha:1.0f].CGColor);
CGContextAddPath(context, path);
CGContextStrokePath(context);
CGContextRestoreGState(context);
I tried adjusting alpha values, and shadow blur factor, but no successful result.
Does anybody have a solution to this? Any help would be appreciated.
On this image you can see following code result. I believe it is almost same to what you want.
Just outer shadow is not just enough to give that smooth effect that is why I add some inner shadow to shape with white color.
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
// Shadows
UIColor* shadow = UIColor.redColor;
CGSize shadowOffset = CGSizeMake(0.1, -0.1);
CGFloat shadowBlurRadius = 11;
UIColor* shadow2 = UIColor.whiteColor; // Here you can adjust softness of inner shadow.
CGSize shadow2Offset = CGSizeMake(0.1, -0.1);
CGFloat shadow2BlurRadius = 9;
// Rectangle Drawing
UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(59, 58, 439, 52) cornerRadius: 21];
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, shadowOffset, shadowBlurRadius, [shadow CGColor]);
[UIColor.redColor setFill];
[rectanglePath fill];
// Rectangle Inner Shadow
CGContextSaveGState(context);
UIRectClip(rectanglePath.bounds);
CGContextSetShadowWithColor(context, CGSizeZero, 0, NULL);
CGContextSetAlpha(context, CGColorGetAlpha([shadow2 CGColor]));
CGContextBeginTransparencyLayer(context, NULL);
{
UIColor* opaqueShadow = [shadow2 colorWithAlphaComponent: 1];
CGContextSetShadowWithColor(context, shadow2Offset, shadow2BlurRadius, [opaqueShadow CGColor]);
CGContextSetBlendMode(context, kCGBlendModeSourceOut);
CGContextBeginTransparencyLayer(context, NULL);
[opaqueShadow setFill];
[rectanglePath fill];
CGContextEndTransparencyLayer(context);
}
CGContextEndTransparencyLayer(context);
CGContextRestoreGState(context);
CGContextRestoreGState(context);
}
Regarding size of the shape you have to adjust both inner and outer shadows blur radius.
You can get an effect similar to what you're trying to achieve by blending your shadow with your stroke
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextAddPath(context, path);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, self.lineWidth);
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextSetShadowWithColor(context, CGSizeMake(0.f, 0.f), self.lineWidth/4, [self.lineColor CGColor]);
CGContextSetBlendMode(context, kCGBlendModeMultiply);
CGContextSetAlpha(context, self.lineAlpha);
CGContextStrokePath(context);
With Multiply blending mode, using white color as stroke color and setting the color of the brush you want to the shadow, you get the following result:
I've connected the drawing function to touchesMoved event, so that way the longer I take to paint a part of the image, the harder the "Brush" draws (see the black line).
This probably isn't the perfect answer, but it's the best I can do for my needs.
Grab the FXBlurView: https://github.com/nicklockwood/FXBlurView
You can either draw your strokes on an FXBlurView or convert your UIView to UIImage after you've finished drawing (using the code I took from this answer https://stackoverflow.com/a/22494886/505259):
+ (UIImage *) imageWithView:(UIView *)view
{
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0f);
[view drawViewHierarchyInRect:view.bounds afterScreenUpdates:NO];
UIImage * snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return snapshotImage;
}
and use FXBlurView's category on UIImage:
- (UIImage *)blurredImageWithRadius:(CGFloat)radius
iterations:(NSUInteger)iterations
tintColor:(UIColor *)tintColor;
to blur the resulting image, giving it a Photoshop soft brush like appearance.
I'm still looking for a real answer though. I have an OpenCV project that requires an exact replica of Photoshop's soft brush tool.
I've been working on drawing the path with inner glow, and somehow succeeded (at least for my taste).
I've implemented the drawing code on top of the levinunnick's Smooth-Line-View. The code is MIT licensed, so you'll need to add it to your project.
Currently you can assign the line color, width and the smoothness for the line you want to draw. Be careful with smoothness, use a float between 0 - 1. I've changed the touch methods cause I needed to access the drawing methods from another view. Check the original code, if you want to revert to the touch methods.
I did not optimize the code, if you've got a better idea, just edit this answer.
Here is the H file:
#interface LineView : UIView
- (instancetype)initWithFrame:(CGRect)frame andColor:(UIColor *)lineColor andWidth:(CGFloat)lineWidth andSmoothness:(CGFloat)lineSmooth;
- (void)touchStartedWith:(CGPoint)location;
- (void)touchMovedWith:(CGPoint)location;
#end
This is the M file:
#import "LineView.h"
static const CGFloat kPointMinDistance = 0.05f;
static const CGFloat kPointMinDistanceSquared = kPointMinDistance * kPointMinDistance;
#interface LineView ()
#property (strong) UIColor *lineColor;
#property (assign) CGFloat lineWidth;
#property (assign) CGFloat lineSmooth;
#property (assign) CGPoint currentPoint;
#property (assign) CGPoint previousPoint;
#property (assign) CGPoint previousPreviousPoint;
#end
#implementation LineView
{
#private
CGMutablePathRef _path;
}
- (instancetype)initWithFrame:(CGRect)frame andColor:(UIColor *)lineColor andWidth:(CGFloat)lineWidth andSmoothness:(CGFloat)lineSmooth
{
self = [super initWithFrame:frame];
if ( self ) {
_path = CGPathCreateMutable();
if ( lineSmooth < 0 ) lineSmooth = 0;
if ( lineSmooth > 1 ) lineSmooth = 1;
self.backgroundColor = [UIColor clearColor];
self.lineColor = lineColor;
self.lineWidth = lineWidth;
self.lineSmooth = lineWidth * ( lineSmooth / 4 );
self.opaque = NO;
}
return self;
}
- (void)drawRect:(CGRect)rect
{
[self.backgroundColor set];
UIRectFill(rect);
#autoreleasepool {
CGColorRef theColor = self.lineColor.CGColor;
UIColor *theClearOpaque = [[UIColor whiteColor] colorWithAlphaComponent:1];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextAddPath(context, _path);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, self.lineWidth);
CGContextSetStrokeColorWithColor(context, theColor);
// Outer shadow
CGSize shadowOffset = CGSizeMake(0.1f, -0.1f);
CGFloat shadowBlurRadius = self.lineSmooth;
CGContextSetShadowWithColor(context, shadowOffset, shadowBlurRadius, theColor);
CGContextStrokePath(context);
if ( self.lineSmooth > 0 ) {
// Inner shadow
CGRect bounds = CGPathGetBoundingBox(_path);
CGRect drawBox = CGRectInset(bounds, -2.0f * self.lineWidth, -2.0f * self.lineWidth);
CGContextSaveGState(context);
UIRectClip(drawBox);
CGContextSetShadowWithColor(context, CGSizeZero, 0, NULL);
CGContextSetAlpha(context, CGColorGetAlpha(theClearOpaque.CGColor));
CGContextBeginTransparencyLayer(context, NULL);
{
// Outer shadow
UIColor *oShadow = [theClearOpaque colorWithAlphaComponent:1];
CGContextSetShadowWithColor(context, CGSizeMake(0.1f, -0.1f), self.lineWidth / 64 * self.lineSmooth, oShadow.CGColor);
CGContextSetBlendMode(context, kCGBlendModeSourceOut);
CGContextBeginTransparencyLayer(context, NULL);
[oShadow setFill];
// Draw the line again
CGContextAddPath(context, _path);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, self.lineWidth);
CGContextSetStrokeColorWithColor(context, oShadow.CGColor);
CGContextStrokePath(context);
CGContextEndTransparencyLayer(context);
}
CGContextEndTransparencyLayer(context);
CGContextRestoreGState(context);
}
}
}
- (void)touchStartedWith:(CGPoint)location
{
self.previousPoint = location;
self.previousPreviousPoint = location;
self.currentPoint = location;
[self touchMovedWith:location];
}
- (void)touchMovedWith:(CGPoint)location
{
CGRect drawBox;
#autoreleasepool {
CGFloat dx = location.x - self.currentPoint.x;
CGFloat dy = location.y - self.currentPoint.y;
if ( ( dx * dx + dy * dy ) < kPointMinDistanceSquared ) {
return;
}
self.previousPreviousPoint = self.previousPoint;
self.previousPoint = self.currentPoint;
self.currentPoint = location;
CGPoint mid1 = midPoint(self.previousPoint, self.previousPreviousPoint);
CGPoint mid2 = midPoint(self.currentPoint, self.previousPoint);
CGMutablePathRef subpath = CGPathCreateMutable();
CGPathMoveToPoint(subpath, NULL, mid1.x, mid1.y);
CGPathAddQuadCurveToPoint(subpath, NULL, self.previousPoint.x, self.previousPoint.y, mid2.x, mid2.y);
CGRect bounds = CGPathGetBoundingBox(subpath);
drawBox = CGRectInset(bounds, -2.0f * self.lineWidth, -2.0f * self.lineWidth);
CGPathAddPath(_path, NULL, subpath);
CGPathRelease(subpath);
}
[self setNeedsDisplayInRect:drawBox];
}
- (void)dealloc
{
CGPathRelease(_path);
_path = NULL;
}
#end

Resources