Draw a 5 pixel line on UIView [duplicate] - ios

This question already has answers here:
What's the best approach to draw lines between views?
(2 answers)
Closed 9 years ago.
I have a very simple (well hopefully very simple) question. In Objective-C, how do you draw a line between two points and add it to a UIView? I have tried using a UIImageView and manipulating its Transform property, but that ends up turning the line into a square or a rectangle when using the following code:
[[self tline] setFrame:CGRectMake(start.x, start.y, width, 5)];
[[self tline] setTransform:CGAffineTransformMakeRotation(angle)];
I have two CGPoints, start and end, and I would like to draw a dynamic 5px line between the two points and add it to my subview.
BK:
The point start is the point where the user begins touching the screen, and the point end is the point where the user's finger is currently. Obviously this will move a lot during gameplay. I need to be able to move this line to connect these two points.
I am using the touchesBegan:, Moved:, and Ended: methods to create, move, and destroy the line.
CoreGraphics
I have the following code; how do I add this line to self.view?
CGContextRef c = UIGraphicsGetCurrentContext();
CGFloat color[4] = {1.0f, 1.0f, 1.0f, 0.6f};
CGContextSetStrokeColor(c, color);
CGContextBeginPath(c);
CGContextMoveToPoint(c, start.x, start.y);
CGContextAddLineToPoint(c, end.x, end.y);
CGContextSetLineWidth(c, 5);
CGContextSetLineCap(c, kCGLineCapRound);
CGContextStrokePath(c);
Custom UIView:
#import <UIKit/UIKit.h>
#interface DrawingView : UIView
#property (nonatomic) CGPoint start;
#property (nonatomic) CGPoint end;
- (void)drawRect:(CGRect)rect;
#end
#import "DrawingView.h"
#implementation DrawingView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextSetLineCap(context, kCGLineCapSquare);
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor); //change color here
CGFloat lineWidth = 5.0; //change line width here
CGContextSetLineWidth(context, lineWidth);
CGPoint startPoint = [self start];
CGPoint endPoint = [self end];
CGContextMoveToPoint(context, startPoint.x + lineWidth/2, startPoint.y + lineWidth/2);
CGContextAddLineToPoint(context, endPoint.x + lineWidth/2, endPoint.y + lineWidth/2);
CGContextStrokePath(context);
CGContextRestoreGState(context);
NSLog(#"%f",_end.x);
}
- (void)setEnd:(CGPoint)end
{
_end = end;
[self setNeedsDisplay];
}
#end
drawRect: is only called when I initialize the view...
Draw method in UIViewController:
- (void)drawTLine:(CGPoint)start withEndPoint:(CGPoint)end
{
[[self dview] setStart:start];
[[self dview] setEnd:end];
[[self dview] drawRect:[self dview].frame];
}
This is how I add the drawing view:
DrawingView* dview = [[DrawingView alloc] initWithFrame:self.view.frame];
[dview setBackgroundColor:[UIColor clearColor]];
[self.view addSubview:dview];

Subclass UIView and perform custom drawing using the drawRect: method
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextSetLineCap(context, kCGLineCapSquare);
CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor); //change color here
CGFloat lineWidth = 1.0; //change line width here
CGContextSetLineWidth(context, lineWidth);
CGPoint startPoint = rect.origin; //change start point here
CGPoint endPoint = {.x = CGRectGetMaxX(rect), .y = startPoint.y} //change end point here
CGContextMoveToPoint(context, startPoint.x + lineWidth/2, startPoint.y + lineWidth/2);
CGContextAddLineToPoint(context, endPoint.x + lineWidth/2, endPoint.y + lineWidth/2);
CGContextStrokePath(context);
CGContextRestoreGState(context);
}
This will draw a black 1px line at the top of your UIView.
If you need to update the line you can just use some properties like
#property (nonatomic) CGPoint startPoint;
and provide a custom implementation for the setter like
- (void)setStartPoint:(CGPoint)point {
_point = point;
[self setNeedsDisplay]; // this will cause drawRect: to be called again
}
Do that for every property that you wish to control and make your drawRect: use such properties for drawing.

you can create and UIBezierPath like this
UIBezierPath path = [[UIBezierPath alloc] init]
[path moveToPoint: CGPoint(x,y)] // X and Y, start point
[path addLineToPoint:CGPoint(x2,y2) // X2 and Y2, end point
if you want to create a shape you can put more points with addLineToPoint: method and finish use
closePath method
I hope help you

Related

Draw Triangles inside Circle Objective-C [duplicate]

For an iPhone application I want to draw a circle, that is only for an x percentage filled.
Something like this:
I have no problems calculating the radius, the degrees or the radians, that is no problem. Also drawing the circle is already done. But how do I get the iPhone SDK to draw the part that is filled.
I can draw a rectangle that size, but not part of a circle.
I just want to draw that on a a normal context.
Hope someone can give me any pointers here.
A lot of people have showed you how this can be done in Core Graphics but it can also be done with Core Animation which gives the big addition of easily being able to animate the percentage of the pie shape.
The following code will create both the ring and the partly filled layers (even though you said that you already can draw the ring) since its nice to have both the ring and the pie shape to be drawn using the same method.
If you animate the strokeStart or strokeEnd properties of the pieShape layer you will have the percentage animate. As with all Core Animation code you will need to add QuartzCore.framework to your project and include <QuartzCore/QuartzCore.h> in your code.
// Create a white ring that fills the entire frame and is 2 points wide.
// Its frame is inset 1 point to fit for the 2 point stroke width
CGFloat radius = MIN(self.frame.size.width,self.frame.size.height)/2;
CGFloat inset = 1;
CAShapeLayer *ring = [CAShapeLayer layer];
ring.path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(self.bounds, inset, inset)
cornerRadius:radius-inset].CGPath;
ring.fillColor = [UIColor clearColor].CGColor;
ring.strokeColor = [UIColor whiteColor].CGColor;
ring.lineWidth = 2;
// Create a white pie-chart-like shape inside the white ring (above).
// The outside of the shape should be inside the ring, therefore the
// frame needs to be inset radius/2 (for its outside to be on
// the outside of the ring) + 2 (to be 2 points in).
CAShapeLayer *pieShape = [CAShapeLayer layer];
inset = radius/2 + 2; // The inset is updated here
pieShape.path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(self.bounds, inset, inset)
cornerRadius:radius-inset].CGPath;
pieShape.fillColor = [UIColor clearColor].CGColor;
pieShape.strokeColor = [UIColor whiteColor].CGColor;
pieShape.lineWidth = (radius-inset)*2;
// Add sublayers
// NOTE: the following code is used in a UIView subclass (thus self is a view)
// If you instead chose to use this code in a view controller you should instead
// use self.view.layer to access the view of your view controller.
[self.layer addSublayer:ring];
[self.layer addSublayer:pieShape];
Use CGContext's arc functions:
CGContextAddArc(context,
centerX,
centerY,
radius,
startAngleRadians,
endAngleRadians,
clockwise ? 1 : 0);
See the documentation for CGContextAddArc().
Try this:
CGContextMoveToPoint(the center point)
CGContextAddLineToPoint(the starting point of the fill path on the circumference)
CGContextAddArcToPoint(the ending point of the fill path on the circumference)
CGContextAddLineToPoint(the center point)
CGContextFillPath
I implemented a pie progress view that looks similar to what you are doing. It's open source. Hopefully the source code will help.
SSPieProgressView.h source
SSPieProgressView.m source
CircleViewController.h
#import <UIKit/UIKit.h>
#interface CircleViewController : UIViewController
#end
CircleViewController.m
#import "CircleViewController.h"
#import "GraphView.h"
#interface CircleViewController ()
#end
#implementation CircleViewController
- (void)viewDidLoad {
[super viewDidLoad];
GraphView *graphView = [[GraphView alloc] initWithFrame:CGRectMake(100, 100, 200, 200)];
graphView.backgroundColor = [UIColor whiteColor];
graphView.layer.borderColor = [UIColor redColor].CGColor;
graphView.layer.borderWidth = 1.0f;
[self.view addSubview:graphView];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
GraphView.h
#import <UIKit/UIKit.h>
#interface GraphView : UIView
#end
GraphView.m
#import "GraphView.h"
#implementation GraphView
- (void)drawRect:(CGRect)rect {
CGPoint circleCenter = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);
[self drawCircleWithCircleCenter:(CGPoint) circleCenter radius:80 firstColor:[UIColor blueColor].CGColor secondeColor:[UIColor redColor].CGColor lineWidth:2 startDegree:0 currentDegree:90];
//[self drawCircleWithCircleCenter2:(CGPoint) circleCenter radius:80 firstColor:[UIColor blueColor].CGColor secondeColor:[UIColor redColor].CGColor lineWidth:2 startDegree:0 currentDegree:90];
}
- (void)drawCircleWithCircleCenter:(CGPoint) circleCenter
radius:(CGFloat)radius
firstColor:(CGColorRef)firstColor
secondeColor:(CGColorRef)secondeColor
lineWidth:(CGFloat)lineWidth
startDegree:(float)startDegree
currentDegree:(float)endDegree {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, lineWidth);
CGContextMoveToPoint(context, circleCenter.x, circleCenter.y);
CGContextAddArc(context, circleCenter.x , circleCenter.y, radius, [self radians:startDegree], [self radians:endDegree], 0);
CGContextSetFillColorWithColor(context, firstColor);
CGContextFillPath(context);
CGContextMoveToPoint(context, circleCenter.x, circleCenter.y);
CGContextAddArc(context, circleCenter.x, circleCenter.y, radius, [self radians:endDegree], [self radians:startDegree], 0);
CGContextSetFillColorWithColor(context, secondeColor);
CGContextFillPath(context);
}
- (void)drawCircleWithCircleCenter2:(CGPoint) circleCenter
radius:(CGFloat)radius
firstColor:(CGColorRef)firstColor
secondeColor:(CGColorRef)secondeColor
lineWidth:(CGFloat)lineWidth
startDegree:(float)startDegree
currentDegree:(float)endDegree {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, lineWidth);
CGContextMoveToPoint(context, circleCenter.x, circleCenter.y);
CGContextAddArc(context, circleCenter.x , circleCenter.y, radius, [self radians:startDegree], [self radians:endDegree], 0);
CGContextSetFillColorWithColor(context, firstColor);
CGContextFillPath(context);
CGContextMoveToPoint(context, circleCenter.x, circleCenter.y);
CGContextAddArc(context, circleCenter.x, circleCenter.y, radius, [self radians:endDegree], [self radians:startDegree], 0);
CGContextSetStrokeColorWithColor(context, secondeColor);
CGContextStrokePath(context);
}
-(float) radians:(double) degrees {
return degrees * M_PI / 180;
}
#end
note: you can use one of the 2 methods:
"drawCircleWithCircleCenter" or "drawCircleWithCircleCenter2"
this code if you want to split cell on 2 parts only
if you want to split cell on more than 2 parts you can check this : "Drawing a circle ,filled different parts with different color" and check the answer start with this Phrase "we have 6 class"
Well, since nobody used NSBezierPath so far, I figured I could provide the solution I recently used for the same problem:
-(void)drawRect:(NSRect)dirtyRect
{
double start = -10.0; //degrees
double end = 190.0; //degrees
NSPoint center = NSMakePoint(350, 200);
double radius = 50;
NSBezierPath *sector = [NSBezierPath bezierPath];
[sector moveToPoint:center];
[sector appendBezierPathWithArcWithCenter:center radius:radius startAngle:start endAngle:end];
[sector lineToPoint:center];
[sector fill];
}
Below is a full method I am using that does this with Core Graphics, adapting and expanding on mharper's comment above.
This code is for OSX Cocoa, but could easily be changed to iOS, by modifying how you get the context.
- (void)drawPieShapedCircleWithRadius:(CGFloat)radius
strokeColor:(CGColorRef)strokeColor
fillColor:(CGColorRef)fillColor
lineWidth:(CGFloat)lineWidth
currentDegrees:(float)currentDegrees
startDegrees:(float)startDegrees {
// get the context
CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort];
// Set the color of the circle stroke and fill
CGContextSetStrokeColorWithColor(context, strokeColor);
CGContextSetFillColorWithColor(context, fillColor);
// Set the line width of the circle
CGContextSetLineWidth(context, 1);
// Calculate the middle of the circle
CGPoint circleCenter = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2);
// Move the bezier to the center of the circle
CGContextMoveToPoint(context, circleCenter.x, circleCenter.y); // move to the center point
// Draw the arc from the start point (hardcoded as the bottom of the circle) to the center
CGContextAddLineToPoint(context, circleCenter.x, circleCenter.y + radius);
// Draw the arc around the circle from the start degrees point to the current degrees point
CGContextAddArc(context, circleCenter.x , circleCenter.y, radius, [self radians:startDegrees], [self radians:startDegrees + currentDegrees], 0);
// Draw the line back into the center of the circle
CGContextAddLineToPoint(context, circleCenter.x, circleCenter.y);
// Fill the circle
CGContextFillPath(context);
// Draw the line around the circle
CGContextStrokePath(context);
}
Try this code in a UIView, Example "MyChartClass"...
- (void)drawRect:(CGRect)rect {
int c=(int)[itemArray count];
CGFloat angleArray[c];
CGFloat offset;
int sum=0;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetAllowsAntialiasing(context, false);
CGContextSetShouldAntialias(context, false);
for(int i=0;i<[itemArray count];i++) {
sum+=[[itemArray objectAtIndex:i] intValue];
}
for(int i=0;i<[itemArray count];i++) {
angleArray[i]=(float)(([[itemArray objectAtIndex:i] intValue])/(float)sum)*(2*3.14);
CGContextMoveToPoint(context, radius, radius);
if(i==0)
CGContextAddArc(context, radius, radius, radius, 0,angleArray[i], 0);
else
CGContextAddArc(context, radius, radius, radius,offset,offset+angleArray[i], 0);
offset+=angleArray[i];
CGContextSetFillColorWithColor(context, ((UIColor *)[myColorArray objectAtIndex:i]).CGColor);
CGContextClosePath(context);
CGContextFillPath(context);
}
}
Implementation in your UIViewController
MyChartClass *myChartClass=[[MyChartClass alloc]initWithFrame:CGRectMake(0, 0, 200, 200)];
myChartClass.backgroundColor = [UIColor clearColor];
myChartClass.itemArray=[[NSArray alloc]initWithObjects:#"75",#"25", nil];
myChartClass.myColorArray=[[NSArray alloc]initWithObjects:[UIColor blackColor],[UIColor whiteColor], nil];
myChartClass.radius=100;
[self.view addSubview:myChartClass];
Regards.

Using a For Loop To Draw Lines Over a UISlider

I have a UISlider that I use to play a video, and I would like to put red "break indication" lines in my UISlider to visually show the user when the break is coming up, as visualized below. My slider has a set .duration property, and I have an array full of timestamps that contain the times that the video will need to pause for a break. I'm still getting the hang of iOS, so I don't know how to go about drawing the lines over UISlider's. I would like it to appear similar to this:
In my research I've read in the Apple Docs that UISlider luckily provides a method to the sliders coordinates based upon a float value. This is perfect because now I can determine where in the slider I can draw the yellow line based upon the timestamps in the array, right?
So, I created a for loop to call the method and am (attempting to) draw a line.
The drawing the line part is what I'm having issues with. I've been reading through Apple's docs and other questions on this site, but I cannot seem to figure out the drawing logic. It draws just fine, the issue is that it's coordinates are all wrong. It draws and overlaps the majority of its lines in one specific location, the top left of my view. This is what I'm trying to do:
Updated Code In correlation with #Bannings answer (Many thanks to you).
- (void)breakStarted:(NSNotification *)notification {
self.lines = [NSMutableArray new];
for (int i = 0; i < myArray.count; i++) {
long long nanoSeconds = [[myArray.adMarkerTimes objectAtIndex:i] floatValue];
float minutes = nanoSeconds / 60000000000;
[self sliderThumbCenter:self.scrubberSliderView forValue:minutes];
NSLog(#"Minute Readings: %f", minutes);
}
}
- (float)sliderThumbCenter:(UISlider *)slider forValue:(float)value {
CGRect trackRect = [slider trackRectForBounds:slider.bounds];
CGRect thumbRect = [slider thumbRectForBounds:slider.bounds trackRect:trackRect value:value];
CGFloat centerThumb = CGRectGetMidX(thumbRect);
NSLog(#"Center Thumb / Line Placements on slider are: %f", centerThumb);
[self.lines addObject:#(centerThumb)]; // Added the rect values to an array which we will loop through to draw the lines over the slider
[self setNeedsDisplay];
return centerThumb;
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetLineWidth(context, 2);
for (NSNumber *x in self.lines) {
CGContextMoveToPoint(context, x.floatValue, 0);
CGContextAddLineToPoint(context, x.floatValue, rect.size.height);
CGContextStrokePath(context);
}
}
I forgot to add: I'm manually converting CMTimeValue to seconds in the loop. That's what myArray.adMarkerTimes. Maybe I did that wrong...
You can override the drawRect of UISlider.
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetLineWidth(context, 2);
CGContextMoveToPoint(context, 20, 0);
CGContextAddLineToPoint(context, 20, rect.size.height);
CGContextStrokePath(context);
}
or insert UIView to UISlider.
- (UIView *)lineViewForRect:(CGRect)rect {
UIView *lineView = [[UIView alloc] initWithFrame:rect];
lineView.backgroundColor = [UIColor redColor];
return lineView;
}
- (void)viewDidLoad {
[super viewDidLoad];
[slider addSubview:[self lineViewForRect:CGRectMake(20, 0, 2, slider.bounds.size.height)]];
}
You also can set lineView.layer.zPosition = 1:
before:
after:
EDIT:
You can store lines in an array, and draw each in context.
It seems like this:
// YourSlider.m
- (void)addLineToX:(CGFloat)x {
[self.lines addObject:#(x)];
// This will cause drawRect to be called
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetLineWidth(context, 2);
for (NSNumber *x in self.lines) {
CGContextMoveToPoint(context, x.floatValue, 0);
CGContextAddLineToPoint(context, x.floatValue, rect.size.height);
}
CGContextStrokePath(context);
}

Multicolor line with Core Graphics

I'm trying to draw a bar that has 4 color segments (not a gradient - 4 distinct colors). Those 4 UIColors are stored in an array (I have debugged and checked that those colors are properly set). For some reason, This loop only draws the first color (at the correct width). This class inherits from UIView and is being invoked via
TopBar* bar = [[TopBar alloc]initWithFrame:CGRectMake(0, 200, self.view.frame.size.width, 20)];
[self.view addSubview:bar];
The drawRect is as follows:
-(void)drawRect:(CGRect)rect{
NSLog(#"draw rect");
[super drawRect:rect];
CGContextRef ctx = UIGraphicsGetCurrentContext();
NSInteger fullBarWidth = self.frame.size.width;
NSInteger individualBarWidth = fullBarWidth/self.barColors.count;
NSInteger currentColorIndex = 0;
CGContextSetLineWidth(ctx, self.frame.size.height);
CGContextSetLineCap(ctx, kCGLineCapButt);
for (UIColor *color in self.barColors)
{
NSInteger currentDrawLoc = currentColorIndex * individualBarWidth;
currentColorIndex++;
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, currentDrawLoc, 0);
CGContextAddLineToPoint(ctx, individualBarWidth, 0);
CGContextSetStrokeColorWithColor(ctx,color.CGColor);
CGContextStrokePath(ctx);
}
}
Any help would be appreciated
In this line:
CGContextAddLineToPoint(ctx, individualBarWidth, 0);
You meant to say:
CGContextAddLineToPoint(ctx, currentDrawLoc + individualBarWidth, 0);

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

Trouble drawing CoreGraphics lines in drawRect() method

I'm working on the Stanford CS193p course and am trying to draw a graph. Drawing the axes works, but I can't draw the graph itself. I'm getting the message
CGContextAddLineToPoint: no current point.
when I try to draw. Here's the code.
- (void)drawRect:(CGRect)rect
{
NSLog(#"Drawing Graph View");
CGPoint origin = CGPointMake(20, 350);
CGRect rect2 = CGRectMake(origin.x, origin.y, 250, -250);
[AxesDrawer drawAxesInRect:rect2 originAtPoint:origin scale:[self scale]];
CGContextRef context = UIGraphicsGetCurrentContext();
UIGraphicsPushContext(context);
CGContextSetLineWidth(context, 2);
[[UIColor redColor] setStroke];
CGContextMoveToPoint(context, origin.x, origin.y);
for (CGFloat i=0; i<250; i++) {
CGFloat yChange = [self.dataSource deltaY:self];
CGContextAddLineToPoint(context, origin.x+i, origin.y-yChange);
CGContextStrokePath(context);
}
UIGraphicsPopContext();
}
You have to place CGContextStrokePath(context); outside the for loop. Otherwise it will create a fresh path on every run through the loop and that fails.
Do you not have a problem with:
CGRectMake(origin.x, origin.y, 250, -250)
You specify a negative height!

Resources