I am trying to creating an application that allows the user to draw a line on the screen and it measures the distance of the drawn line. I have been able to successfully draw the line but I don't know how to measure it. The line does not have to be perfectly straight either. It is basically a squiggle. If someone could please point me in the right direction or help guide me that would be awesome. I am using Xcode 5.1.1 and objective-c. I only just started dabbling in the language this summer.
EDIT: I am looking to measure the distance in either inches or cm. I would like the measurement to be the entire line, to follow the curve of the line. The distance not the displacement.
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
mouseSwipe = YES; //swipe declared in header
UITouch *touch = [touches anyObject];
currentPoint = [touch locationInView:self.view]; //tracking finger movement on screen
UIGraphicsBeginImageContext(CGSizeMake(320, 568)); // 568 iphone 5, 480 is iphone 4 (320,525)
[drawImage.image drawInRect:CGRectMake(0, 0, 320, 568)]; // 0,0 centered in corner
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound); //round line end
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 5.0); // width of line
CGContextSetStrokeColorWithColor(UIGraphicsGetCurrentContext(), [[UIColor redColor] CGColor]); //sets color to red (change red to any color for that color)
//CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 0,1,0,1); //green color
CGContextBeginPath(UIGraphicsGetCurrentContext()); //start of when drawn path
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y);
CGContextStrokePath(UIGraphicsGetCurrentContext());
[drawImage setFrame:CGRectMake(0, 0, 320, 568)]; //(320, 568)
drawImage.image = UIGraphicsGetImageFromCurrentImageContext(); //importnant
UIGraphicsEndImageContext(); //finished drawing for time period
lastPoint = currentPoint;
[self.view addSubview:drawImage]; //adds to page
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject]; //touch fires of touch
location = [touch locationInView:touch.view];
lastClick = [NSDate date];
lastPoint = [touch locationInView:self.view]; //stops connecting to previous line
lastPoint.y -= 0;
[super touchesEnded:touches withEvent: event];
}
First add a property that cumulates the length of the path.
#property(nonatomic, assign) CGFloat pathLength;
Initialize it to 0.0 when user begins drawing the path. Maybe do this in touchesBegan, or the same place elsewhere in your code where you realize you're beginning to draw. Add a method that computes the cartesian distance between points:
- (CGFloat)distanceFrom:(CGPoint)p1 to:(CGPoint)p2 {
CGFloat x = (p2.x - p1.x);
CGFloat y = (p2.y - p1.y);
return sqrt(x*x + y*y);
}
As you get touches moved, you are already handling the current and last touch positions. All you must do now is cumulate the distance between successive points:
// in touches moved, after you have lastPoint and currentPoint
self.pathLength += [self distanceFrom:currentPoint to:lastPoint];
There are quite a few refs here and elsewhere for converting these points to inches or cm. As far as I can see all are fraught with the inability to get the device resolution at runtime from the SDK. If you're willing to add a (dangerous) constant to the code, you can get PPI here, and divide that into the pathLength computed above.
Related
i'm using the following code to draw lines over an imageview's image,
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
if ([touch tapCount] == 2)
return;
lastPoint = [touch locationInView:imageview];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:self.view];
CGContextSetLineCap(UIGraphicsGetCurrentContext(), 3);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 5);
UIGraphicsBeginImageContext(self.view.frame.size);
[imageview.image drawInRect:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
CGContextClearRect(UIGraphicsGetCurrentContext(), CGRectMake(lastPoint.x, lastPoint.y, 5, 5));
imageview.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
lastPoint = currentPoint;
}
Output
You can see the gaps/spaces between the dots. I need the output should be like below,
What should I change in my code to remove the space between two dots?
I think, touchesended code is not needed for your review. I changed the CGContextSetLineCap and width, no luck.
I would change the approach, essentially you're capturing touch points, and not "joining the dots" so to speak. You would be better off using UIBezierPaths for this task.
I answered a question some time back where the op wanted to do something similar but was hitting performance issues. There is code in the answer i posted that pretty much does what you want, completely. Review this post for full sample code, you can pretty much cut and paste the class (I see little value in transcribing). Sample drawing path
If you need more help just let me know
Need a fresh pair of eyes, mine have stopped working and can't make sense of my own code anymore...
I am trying to make a drawing app with a pen on paper style of drawing. Here's how it is suppose to work:
user touches, app grabs location
a var is set to tell drawRect to configure CGcontext, create a new path, move to point, etc (because I always gett errors/warnings in my nslog whenever I do anything with CGcontext outside the drawRect method)
var is then set to determine whether to place a dot or a line when the user lifts finger
if the user drags, the var is changed to tell drawRect to draw a line and [self setneedsdisplay] is called
every time the user places their finger on the screen a timer is activated, every 5 seconds (or until they lift their finger up) the app 'captures' the screen contents, sticks it in an image and wipes the screen, replacing the image and continues drawing (in effect cache'ing the image so all these lines don't have to be re-drawn)
UPDATE #1
So I re-wrote it (because it was obviously really bad and not working...) and ended up with this:
- (void)drawRect:(CGRect)rect {
[_incrImage drawInRect:rect]; /* draw image... this will be blank at first
and then supposed to be filled by the Graphics context so that when the view
is refreshed the user is adding lines ontop of an image (but to them it looks
like a continuous drawing)*/
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapSquare);
CGContextSetLineJoin(UIGraphicsGetCurrentContext(), kCGLineJoinRound);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), _brushW);
CGContextSetStrokeColorWithColor(UIGraphicsGetCurrentContext(), [UIColor colorWithRed:_brushR green:_brushG blue:_brushB alpha:_brushO].CGColor);
CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeNormal);
CGContextAddPath(UIGraphicsGetCurrentContext(), _pathref);
CGContextDrawPath(UIGraphicsGetCurrentContext(), kCGPathStroke);
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
_drw = 2; //set this to draw a dot if the user taps
UITouch *touch = [touches anyObject];
_cp = [touch locationInView:self];
_lp = _cp;
CGPathRelease(_pathref); /* this line and the line below is to clear the
path so the app is drawing as little as possible (the idea is that as
the user draws and lifts their finger, the drawing goes into _incrImage
and then the contents is cleared and _incrImage is applied to the
screen so there is as little memory being used as possible and so the
user feels as if they're adding to what they've drawn when they touch
the device again) */
_pathref = CGPathCreateMutable();
CGPathMoveToPoint(_pathref, NULL, _lp.x, _lp.y);
touch = nil;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
_drw = 1; //user moved their finger, this was not a tap so they want
//to draw a line
UITouch *touch = [touches anyObject];
_cp = [touch locationInView:self];
CGPathAddLineToPoint(_pathref, NULL, _cp.x, _cp.y);
[self setNeedsDisplay]; //as the user moves their finger, it draws
_lp = _cp;
touch = nil;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
_cp = [touch locationInView:self];
switch (_drw) { //logic to determine what to draw when the user
//lifts their finger
case 1:
//line
CGPathAddLineToPoint(_pathref, NULL, _cp.x, _cp.y);
break;
case 2:
//dot
CGPathAddArc(_pathref, NULL, _cp.x, _cp.y, _brushW, 0.0, 360.0, 1);
break;
default:
break;
}
/* here's the fun bit, the Graphics context doesn't seem to be going
into _incrImage and therefore is not being displayed when the user
goes to continue drawing after they've drawn a line/dot on the screen */
UIGraphicsBeginImageContext(self.frame.size);
CGContextAddPath(UIGraphicsGetCurrentContext(), _pathref); /* tried adding
my drawn path to the context and then adding the context to the image
before the user taps down again and the path is cleared... not sure
why it isn't working. */
[_incrImage drawAtPoint:CGPointZero];
_incrImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self setNeedsDisplay]; //finally, refresh the contents
touch = nil;
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[self touchesEnded:touches withEvent:event];
}
My new problem is that everything is erased when drawRect is called and that '_incrImage' is not getting the contents of the current graphics and displaying them
__old, I guess no longer needed but keeping here for reference___
here is the relevant code:
- (void)drawRect:(CGRect)rect {
/* REFERENCE: _drw: 0 = clear everything
1 = cache the screen contents in the image and display that
so the device doesn't have to re-draw everything
2 = set up new path
3 = draw lines instead of a dot at current point
4 = draw a 'dot' instead of a line
*/
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapSquare);
CGContextSetLineJoin(UIGraphicsGetCurrentContext(), kCGLineJoinRound);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), _brushW);
CGContextSetStrokeColorWithColor(UIGraphicsGetCurrentContext(), [UIColor colorWithRed:_brushR green:_brushG blue:_brushB alpha:_brushO].CGColor);
CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeNormal);
switch (_drw) {
case 0:
//clear everything...this is the first draw... it can also be called to clear the view
UIGraphicsBeginImageContext(self.frame.size);
CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeClear);
CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), [UIColor clearColor].CGColor);
CGContextFillRect(UIGraphicsGetCurrentContext(), self.frame);
CGContextFlush(UIGraphicsGetCurrentContext());
_incrImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[_incrImage drawAtPoint:CGPointZero];
[_incrImage drawInRect:rect];
break;
case 1:
//capture the screen content and stick it in _incrImage...
then apply_incrImage to screen so the user can continue drawing ontop of it
_incrImage = UIGraphicsGetImageFromCurrentImageContext();
[_incrImage drawAtPoint:CGPointZero];
[_incrImage drawInRect:rect];
break;
case 2:
//begin path and set everything up, this is called when touchesBegan: fires...
_incrImage = UIGraphicsGetImageFromCurrentImageContext();
[_incrImage drawAtPoint:CGPointZero];
[_incrImage drawInRect:rect];
CGContextBeginPath(UIGraphicsGetCurrentContext());
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), _p.x, _p.y);
break;
case 3:
//add lines, this is after the path is created and set...this is fired when touchesMoved: gets activated and _drw is set to draw lines instead of adding dots
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), _p.x, _p.y);
CGContextStrokePath(UIGraphicsGetCurrentContext());
break;
case 4:
//this is fired when touchesEnd: is activated... this sets up ready if the app needs to draw a 'dot' or the arc with a fill...
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), _p.x, _p.y);
CGContextAddArc(UIGraphicsGetCurrentContext(), _p.x, _p.y, _brushW, 0.0, 360.0, 1);
CGContextFillPath(UIGraphicsGetCurrentContext());
CGContextFlush(UIGraphicsGetCurrentContext());
break;
default:
break;
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
dTimer = [NSTimer timerWithTimeInterval:5.0 target:self selector:#selector(timeUP) userInfo:Nil repeats:YES];
_drw = 2;
UITouch *touch = [touches anyObject];
_p = [touch locationInView:self];
[self setNeedsDisplay];
_drw = 4;
}
- (void)timeUP {
_drw = 1;
[self setNeedsDisplay];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
_drw = 3;
UITouch *touch = [touches anyObject];
_p = [touch locationInView:self];
[self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event // (2)
{
[dTimer invalidate];
UITouch *touch = [touches anyObject];
_p = [touch locationInView:self];
[self setNeedsDisplay];
[self timeUP];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[self touchesEnded:touches withEvent:event];
}
My questions are:
is this efficient? or is there a better way to do this?
and
Why is this not drawing? I get nothing but I can see my memory being used...
Egad.
Your first problem is that setNeedsDisplay doesn’t draw immediately, it just marks the view to be drawn at the end of the event. So, when you set _drw=2 and call setNeedsDisplay and then set _drw=4, it’s only going to actually call -drawRect: with _drw=4 (if that, since that still might not be the end of the current event).
But, also don’t, uh, use that _drw switch thing. That’s not good.
You want to create an image and draw into the image as the touches happen, and then in drawRect: just blat the image to the screen. If you ever find yourself calling UIGraphicsGetImageFromCurrentImageContext() inside -drawRect; you are doing things backwards (as you are here). Don’t slurp the image from the screen, create an image that you blat to the screen.
The screen should never been your ‘state’. That way lies madness.
On top of the answer from #WilShipley which I agree with (don't put state management logic in drawRect:, only pure redrawing logic), you are currently never drawing drawing anything other than a minuscule line because CGContextStrokePath clears the current line from the context.
The context isn't intended to be a temporary cache of incomplete drawing operations, it's your portal to the screen (or a backing image / PDF file / ...). You need to create your drawing state outside drawRect: and then render it to the screen inside drawRect:.
To ease performance issues, only redraw the area around the newest touch (or between the newest and previous touch).
In my app, a circle is drawn based on a users drag. e.g. a user taps, that is the center of a circle that will be drawn, and as they drag their finger, the circle will grow to that point. This works, except for some reason the center moves down and to the right as the radius of the circle grows. Why is this happening? Here is what I am trying:
#implementation CircleView{
CGPoint center;
CGPoint endPoint;
CGFloat distance;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
center = [touch locationInView:self];
[self setNeedsDisplay];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
endPoint = [touch locationInView:self];
CGFloat xDist = (endPoint.x - center.x);
CGFloat yDist = (endPoint.y - center.y);
distance = sqrt((xDist * xDist) + (yDist * yDist));
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 2.0);
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CGRect rectangle = CGRectMake(center.x,center.y,distance, distance);
CGContextAddEllipseInRect(context, rectangle);
CGContextStrokePath(context);
}
What should be happening is that center point never ever moves, and the circle should just grow. Any ideas?
CGRect rectangle = CGRectMake(center.x,center.y,distance, distance);
should be:
CGRect rectangle = CGRectMake(center.x - distance, center.y - distance, distance * 2, distance * 2);
Because in CGRectMake you have to specify the origin (and the size) of the rectangle, not the center.
CGRect rectangle = CGRectMake(center.x - distance, center.y - distance, distance * 2, distance * 2);
I'm trying create an application that allows the user to move a frame over an image, so that I can apply some effects on a selected region.
I need to allow the user to precisely drag and scale the masked-frame on the image. I need this to be exact, just like any other photo app does.
My strategy is to get the touch points of the user, on a touch-moved event, and scale my frame accordingly. That was pretty intuitive. I coded the following stuff for handling the touch moved event :
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:[self view]];
float currX = touchPoint.x;
float currY = touchPoint.y;
/*proceed with other operations on currX and currY,
which is coming out quite well*/
}
But the only problem is, the coordinates of the currX and currY variables are not quite where they are supposed to be. There is a parallax error, which keeps shifting from device to device. I also think the x and y coordinates gets swapped in the case of an iPad.
Could you please help me to figure out how to get the exact touch coordinates?
My background image is in one view (imageBG) and the masked frame is in a separate one (maskBG). I have tried out :
CGPoint touchPoint = [touch locationInView:[maskBG view]];
and
CGPoint touchPoint = [touch locationInView:[imageBG view]];
...but the same problem persists. I have also noticed the error on touch being worse on an iPad than on an iPhone or iPod.
image.center = [[[event allTouches] anyObject] locationInView:self.view];
Hi your issue is the image and the iPhone screen are not necessarily in same aspect ratio.Your touch point might not translate correctly to your actual image.
- (UIImage*) getCroppedImage {
CGRect rect = self.movingView.frame;
CGPoint a;
a.x=rect.origin.x-self.imageView.frame.origin.x;
a.y=rect.origin.y-self.imageView.frame.origin.y;
a.x=a.x*(self.imageView.image.size.width/self.imageView.frame.size.width);
a.y=a.y*(self.imageView.image.size.height/self.imageView.frame.size.height);
rect.origin=a;
rect.size.width=rect.size.width*(self.imageView.image.size.width/self.imageView.frame.size.width);
rect.size.height=rect.size.height*(self.imageView.image.size.height/self.imageView.frame.size.height);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
// translated rectangle for drawing sub image
CGRect drawRect = CGRectMake(-rect.origin.x, -rect.origin.y, self.imageView.image.size.width, self.imageView.image.size.height);
// clip to the bounds of the image context
// not strictly necessary as it will get clipped anyway?
CGContextClipToRect(context, CGRectMake(0, 0, rect.size.width, rect.size.height));
// draw image
[self.imageView.image drawInRect:drawRect];
// grab image
UIImage* croppedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return croppedImage;
}
This is what i did to crop my moving view is the rect which i pass for cropping see how its being translated to reflect correctly on image.Make sure the image view on which the user sees image is aspectfit content mode.
Note:- I make the rect of image view fit the aspectFit image
use this to do it
- (CGSize)makeSize:(CGSize)originalSize fitInSize:(CGSize)boxSize
{
widthScale = 0;
heightScale = 0;
widthScale = boxSize.width/originalSize.width;
heightScale = boxSize.height/originalSize.height;
float scale = MIN(widthScale, heightScale);
CGSize newSize = CGSizeMake(originalSize.width * scale, originalSize.height * scale);
return newSize;
}
have you tried these:
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:selectedImageView];
float currX = (touchPoint.x)/selectedImageView.frame.size.width;
float currY = (touchPoint.y)/selectedImageView.frame.size.height;
/*proceed with other operations on currX and currY,
which is coming out quite well*/
}
or you can also use UIPanGestureRecognizer..
I am working on a sketching app on the iPhone.
I got it working but not pretty as seen here
And I am looking for any suggestion to smooth the drawing
Basically, what I did is when user places a finger on the screen I called
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
then I collect a single touch in an array with
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
and when the user lefts a finger from the screen, I called
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
then I draw all the points in the array using
NSMutableArray *points = [collectedArray points];
CGPoint firstPoint;
[[points objectAtIndex:0] getValue:&firstPoint];
CGContextMoveToPoint(context, firstPoint.x, firstPoint.y);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineJoin(context, kCGLineJoinRound);
for (int i=1; i < [points count]; i++) {
NSValue *value = [points objectAtIndex:i];
CGPoint point;
[value getValue:&point];
CGContextAddLineToPoint(context, point.x, point.y);
}
CGContextStrokePath(context);
UIGraphicsPushContext(context);
And now I want to improve the drawing tobe more like "Sketch Book" App
I think there is something to do with signal processing algorithm to rearrange all the points in the array but I am not sure. Any Help would be much appreciated.
Thankz in advance :)
CGPoint midPoint(CGPoint p1, CGPoint p2)
{
return CGPointMake((p1.x + p2.x) * 0.5, (p1.y + p2.y) * 0.5);
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
previousPoint1 = [touch previousLocationInView:self];
previousPoint2 = [touch previousLocationInView:self];
currentPoint = [touch locationInView:self];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
previousPoint2 = previousPoint1;
previousPoint1 = [touch previousLocationInView:self];
currentPoint = [touch locationInView:self];
// calculate mid point
CGPoint mid1 = midPoint(previousPoint1, previousPoint2);
CGPoint mid2 = midPoint(currentPoint, previousPoint1);
UIGraphicsBeginImageContext(self.imageView.frame.size);
CGContextRef context = UIGraphicsGetCurrentContext();
[self.imageView.image drawInRect:CGRectMake(0, 0, self.imageView.frame.size.width, self.imageView.frame.size.height)];
CGContextMoveToPoint(context, mid1.x, mid1.y);
// Use QuadCurve is the key
CGContextAddQuadCurveToPoint(context, previousPoint1.x, previousPoint1.y, mid2.x, mid2.y);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, 2.0);
CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0);
CGContextStrokePath(context);
self.imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
The easiest way to smooth a curve like this is to use a Bezier curve instead of straight line segments. For the math behind this, see this article (pointed to in this answer), which describes how to calculate the curves required to smooth a curve that passes through multiple points.
I believe that the Core Plot framework now has the ability to smooth the curves of plots, so you could look at the code used there to implement this kind of smoothing.
There's no magic to any of this, as these smoothing routines are fast and relatively easy to implement.
I really love the topic. Thanks for all the implementations, espesially Krzysztof Zabłocki and Yu-Sen Han.
I have modified the version of Yu-Sen Han in order to change line thickness depending on the speed of panning (in fact the distance between last touches). Also I've implemented dot drawing (for touchBegan and touchEnded locations being close to each other)
Here is the result:
To define the line thickness I've chosen such a function of distance:
(Don't ask me why... I just though it suits well, but I'm sure you can find a better one)
CGFloat dist = distance(previousPoint1, currentPoint);
CGFloat newWidth = 4*(atan(-dist/15+1) + M_PI/2)+2;
One more hint. To be sure the thickness is changing smoothly, I've bounded it depending on the thickness of the previous segment and a custom coef:
self.lineWidth = MAX(MIN(newWidth,lastWidth*WIDTH_RANGE_COEF),lastWidth/WIDTH_RANGE_COEF);
I translated kyoji's answer into Swift, as a reusable subclass of UIImageView. The subclass TouchDrawImageView allows the user to draw on an image view with her finger.
Once you've added this TouchDrawImageView class to your project, make sure to open your storyboard and
select TouchDrawImageView as the "Custom Class" of your image view
check "User Interaction Enabled" property of your image view
Here's the code of TouchDrawImageView.swift:
import UIKit
class TouchDrawImageView: UIImageView {
var previousPoint1 = CGPoint()
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
previousPoint1 = touch.previousLocation(in: self)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let previousPoint2 = previousPoint1
previousPoint1 = touch.previousLocation(in: self)
let currentPoint = touch.location(in: self)
// calculate mid point
let mid1 = midPoint(p1: previousPoint1, p2: previousPoint2)
let mid2 = midPoint(p1: currentPoint, p2: previousPoint1)
UIGraphicsBeginImageContext(self.frame.size)
guard let context = UIGraphicsGetCurrentContext() else { return }
if let image = self.image {
image.draw(in: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height))
}
context.move(to: mid1)
context.addQuadCurve(to: mid2, control: previousPoint1)
context.setLineCap(.round)
context.setLineWidth(2.0)
context.setStrokeColor(red: 1.0, green: 0, blue: 0, alpha: 1.0)
context.strokePath()
self.image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
func midPoint(p1: CGPoint, p2: CGPoint) -> CGPoint {
return CGPoint(x: (p1.x + p2.x) / 2.0, y: (p1.y + p2.y) / 2.0)
}
}
Thankz for the input.I update my quest here because I need the space for it.
I look up both corePlot and Bezier curve solutions that you suggested with little success.
For the corePlot I am able to get the graph plot from an array of int but can't find anything related to curve smoothing.BTW Here I am using CPScatterPlot with some random number.
as for Bezier curve, My quest lead me to here It is something to do with Spline implementation in iOS
CatmullRomSpline *myC = [[CatmullRomSpline alloc] initAtPoint:CGPointMake(1.0, 1.0)];
[myC addPoint:CGPointMake(1.0, 1.5)];
[myC addPoint:CGPointMake(1.0, 1.15)];
[myC addPoint:CGPointMake(1.0, 1.25)];
[myC addPoint:CGPointMake(1.0, 1.23)];
[myC addPoint:CGPointMake(1.0, 1.24)];
[myC addPoint:CGPointMake(1.0, 1.26)];
NSLog(#"xxppxx %#",[myC asPointArray]);
NSLog(#"xxppxx2 %#",myC.curves);
and the result I get is:
2011-02-24 14:45:53.915 DVA[10041:40b] xxppxx (
"NSPoint: {1, 1}",
"NSPoint: {1, 1.26}"
)
2011-02-24 14:45:53.942 DVA[10041:40b] xxppxx2 (
"QuadraticBezierCurve: 0x59eea70"
)
I am not really sure how to go from there. So I am stuck on that front as well :(
I did look up GLPaint, as a last resource. It uses OpenGLES and use a "soft dot" sprite to plot the points in the array. I know it's more like avoiding the problem rather than fixing it. But I guess I'l share my findings here anyway.
The Black is GLPaint and the white one is the old method. And the last one is the drawing from "Sketch Book" app just to compare
I am still trying to get this done right, any further suggestion are most welcome.
To get rid of the silly dot in the GLPaint code.
Change in
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
this function
//Ändrat av OLLE
/*
// Convert touch point from UIView referential to OpenGL one (upside-down flip)
if (firstTouch) {
firstTouch = NO;
previousLocation = [touch previousLocationInView:self];
previousLocation.y = bounds.size.height - previousLocation.y;
} else {
location = [touch locationInView:self];
location.y = bounds.size.height - location.y;
previousLocation = [touch previousLocationInView:self];
previousLocation.y = bounds.size.height - previousLocation.y;
}
*/
location = [touch locationInView:self];
location.y = bounds.size.height - location.y;
previousLocation = [touch previousLocationInView:self];
previousLocation.y = bounds.size.height - previousLocation.y;
//Ändrat av OLLE//
I know that this isn't the solution for our problem, but it's something.