I'm currently working on a iPad project where I need the functionality to let the user write on a piece of paper with a stylus.
I've tested a couple of styluses and found out that the bamboo was the best. They also have a free application which you can use to write.
The problem I'm facing is that the method I use does not delivers smooth curves. The bamboo paper app provides perfect looking lines. This is the code I have so far:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
UIGraphicsBeginImageContext(self.frame.size);
// draw accumulated lines
if ([self.lines count] > 0) {
for (Line *tempLine in self.lines){
CGContextSetAlpha(context, tempLine.opacity);
CGContextSetStrokeColorWithColor(context, tempLine.lineColor.CGColor);
CGContextSetLineWidth(context, tempLine.lineWidth);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineJoin(context, kCGLineJoinRound);
CGContextAddPath(context, tempLine.linePath);
CGContextStrokePath(context);
}
}
//draw current line
CGContextSetAlpha(context, self.currentLine.opacity);
CGContextSetStrokeColorWithColor(context, self.currentLine.lineColor.CGColor);
CGContextSetLineWidth(context, self.currentLine.lineWidth);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineJoin(context, kCGLineJoinRound);
CGContextBeginPath(context);
CGContextAddPath(context, self.currentLine.linePath);
CGContextStrokePath(context);
UIGraphicsEndImageContext();
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint cPoint = [touch locationInView:self];
CGPathMoveToPoint(self.currentLine.linePath, NULL, cPoint.x, cPoint.y);
[self setNeedsDisplay];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:self];
CGPathAddLineToPoint(self.currentLine.linePath, NULL, currentPoint.x, currentPoint.y);
[self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint cPoint = [touch locationInView:self];
CGPathAddLineToPoint(self.currentLine.linePath, NULL, cPoint.x, cPoint.y);
[self setNeedsDisplay];
[self.lines addObject:self.currentLine];
Line *nextLine = [[Line alloc] initWithOptions:self.currentLine.lineWidth color:self.currentLine.lineColor opacity:self.currentLine.opacity];
self.currentLine = nextLine;
[nextLine release];
}
Here are the images that makes it clear what problem I'm facing. This is the image what gets generated when writing with the code provided above:
This is the image if I write the same thing on the Mamboo paper app:
Does anyone have an idea how to get the nice writing like in the mamboo app?
Instead of joining the points using straight lines (CGPathAddLineToPoint) you should try using bezier curves: CGPathAddCurveToPoint or CGPathAddQuadCurveToPoint.
This is what will do the smoothing.
If you are not familliar with bézier curves, you will probably find the wikipedia page about Bézier curves interesting (not specifically the math equations, but look at the sketches and animated images). It will give your the general idea of how control points affect the smoothing of the lines around the key points of your line.
For your case, Quadratic curves (only one control point for each subsegment between two of your key points) should be sufficient.
(One line from P0 to P1, smoothed using control point P1)
One example (out of the box, only a suggestion out of my mind, never tested, adapt the coefficients to your needs) to compute this control point C between each of your keypoints A and B is to make the AC being, say, 2/3rd of the length of AB, and (AB,AC) making an angle of 30 degrees or sthg similar.
You may even propose in the settings of your app to adjust the smoothing strengh with a slider, which will impact the values you choose to compute each control points and see which coefficients fits best.
Related
just like the path in the picture
(new users aren't allowed to post images, sorry i can only give the link)
http://cdn.dropmark.com/30180/3bd0f0fc6ee77c1b6f0e05e3ea14c821f8b48a82/questiong1-1.jpg
Thanks a lot for any help
Try this on touches event delegate (if you want to rotate the view with touch event)-
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
touches = [event allTouches];
UITouch *touch = [[event allTouches] anyObject];
CGPoint Pageposition = [touch locationInView:self];
CGPoint prevPoint = [[[touches allObjects] objectAtIndex:0] previousLocationInView:self];
CGPoint curPoint = [[[touches allObjects] objectAtIndex:0] locationInView:self];
float prevAngle = atan2(prevPoint.x, prevPoint.y);
float curAngle= atan2(curPoint.x, curPoint.y);
float angleDifference = curAngle - prevAngle;
CGAffineTransform newTransform3 = CGAffineTransformRotate(self.transform, angleDifference);
self.transform = newTransform3;
}
use transform rotation , and tell me if it works..
Please look at my answer for this question about "rotating a label around an arbitrary point" for a more detailed explanation about using the an anchor point when rotating (ignore the keyframe animation answers since they are more complicated and not the "right tool for this job" even though the answers says so).
In short: set the anchor point to the point (in the unit-coordinate system of your views layer) to the point outside of your view and just apply a normal rotation animation.
EDIT: One thing to remember
The frame of your layer (which is also the frame of your view) is calculated using the position, bounds and anchor point. Changing the anchorPoint will change where your view appears on screen. You can counter this by re-setting the frame after changing the anchor point (this will set the position for you). Otherwise you can set the position to the point you are rotating to yourself. The Layer Geometry and Transforms (linked to in the other question) also mentions this.
The code that I provided in that answer (generalized to a UIView and Core Animation instead of UIView-animations):
#define DEGREES_TO_RADIANS(angle) (angle/180.0*M_PI)
- (void)rotateView:(UIView *)view
aroundPoint:(CGPoint)rotationPoint
duration:(NSTimeInterval)duration
degrees:(CGFloat)degrees {
CABasicAnimation *rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform"];
[rotationAnimation setDuration:duration];
// Additional animation configurations here...
// The anchor point is expressed in the unit coordinate
// system ((0,0) to (1,1)) of the label. Therefore the
// x and y difference must be divided by the width and
// height of the view (divide x difference by width and
// y difference by height).
CGPoint anchorPoint = CGPointMake((rotationPoint.x - CGRectGetMinX(view.frame))/CGRectGetWidth(view.bounds),
(rotationPoint.y - CGRectGetMinY(view.frame))/CGRectGetHeight(view.bounds));
[[view layer] setAnchorPoint:anchorPoint];
[[view layer] setPosition:rotationPoint]; // change the position here to keep the frame
CATransform3D rotationTransform = CATransform3DMakeRotation(DEGREES_TO_RADIANS(degrees), 0, 0, 1);
[rotationAnimation setToValue:[NSValue valueWithCATransform3D:rotationTransform]];
// Add the animation to the views layer
[[view layer] addAnimation:rotationAnimation
forKey:#"rotateAroundAnchorPoint"]
}
I would like to develop an app when the user can draw lines... but I do not want to draw straight lines but want to show the line as the users draws it. When the user gets from point A to B I would like to straighten the line (if the users wants this).
To be able to do this I want to change my view into a grid starting at 0,0 (top left) and ending at 320,480 (for iPhone) and 768,1024 (for iPad) (bottom right).
For this question I have point A at 10,10 and point B at 100,100.
My question:
- How do I create this grid?
- How do I create these points?
- How do I draw this line without straightening it?
- How do I draw the straighten line?
My problem is that I am familiar with creating "normal" UI apps. I am not familiar with Open-GL ect.
I hope someone can help me with this.
Best regards,
Paul Peelen
You subclass your UIView and override the - (void)drawRect:(CGRect)rect method.
In there you grab a graphics context:
CGContextRef context = UIGraphicsGetCurrentContext();
And you use that to make Core Graphics calls, like:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextBeginPath (context);
for (k = 0; k < count; k += 2) {
CGContextMoveToPoint(context, s[k].x, s[k].y);
CGContextAddLineToPoint(context, s[k+1].x, s[k+1].y);
}
CGContextStrokePath(context);
Look up the Quartz 2D Programming Guide for all the details.
You can drag straight line when user drag it based on starting and ending point draw a line using UIBezierPath and CAShapeLayer:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
startingPoint = [touch locationInView:baseHolderView];
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
endingPoint = [touch locationInView:baseHolderView];
[self makeLineLayer:baseHolderView.layer lineFromPointA:startingPoint toPointB:endingPoint];
}
-(void)makeLineLayer:(CALayer *)layer lineFromPointA:(CGPoint)pointA toPointB:(CGPoint)pointB
{
CAShapeLayer *line = [CAShapeLayer layer];
UIBezierPath *linePath=[UIBezierPath bezierPath];
[linePath moveToPoint: pointA];
[linePath addLineToPoint:pointB];
line.path=linePath.CGPath;
line.fillColor = nil;
line.opacity = 2.0;
line.strokeColor = [UIColor blackColor].CGColor;
[layer addSublayer:line];
}
Hope this will help to achieve your goal.
I recently downloaded the sample application, GLPaint from apple. This app showed me how to implement drawing, but now I would like to animate a image over the drawn lines. Similarly to how the application "Flight Control" works, I would like to be able to draw a path for an image and then have the image animate over this path.
any ideas?
you could create your path
CGMutablePathRef thePath = CGPathCreateMutable();
and then in the touchesBegan and touchesMoved add to your path
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
if(touch){
CGPoint tapPoint = [touch locationInView: self.view];
CGPathMoveToPoint(thePath, NULL,tapPoint.x,tapPoint.y);
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
if(touch){
CGPoint tapPoint = [touch locationInView: self.view];
CGPathAddLineToPoint(thePath, NULL,tapPoint.x,tapPoint.y);
}
}
and on touchedEnded, as Joshua said, create a CAKeyframeAnimation and set its path to thePath and set the other properties of your animation (duration, etc) and apply it to your object
Look at CAKeyframeAnimation, this allows you to set a path (which is a CGPathRef) for the animation to run along.
Based on code from AtomRiot, I created a simple project on my blog. See this post, also posted a short video showing the result.
However when the animation ends, the image will somehow repositioned back to (0,0), so might need to add extra code to reset the image location after animation finished.
Plus, found that you can't change the animation once it started, have to wait for it to end before you can change it.
Hope this helps!
I am trying to make an application for drawing shapes on screen by touching it.
I can draw a line from one point to another- but it erases on each new draw.
Here is my code:
CGPoint location;
CGContextRef context;
CGPoint drawAtPoint;
CGPoint lastPoint;
-(void)awakeFromNib{
//[self addSubview:noteView];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
location = [touch locationInView:touch.view];
[self setNeedsDisplayInRect:CGRectMake(0, 0, 320, 480)];
}
- (void)drawRect:(CGRect)rect {
context = UIGraphicsGetCurrentContext();
[[UIColor blueColor] set];
CGContextSetLineWidth(context,10);
drawAtPoint.x =location.x;
drawAtPoint.y =location.y;
CGContextAddEllipseInRect(context,CGRectMake(drawAtPoint.x, drawAtPoint.y, 2, 2));
CGContextAddLineToPoint(context,lastPoint.x, lastPoint.y);
CGContextStrokePath(context);
lastPoint.x =location.x;
lastPoint.y =location.y;
}
Appreciate your help-
Nir.
As you have discovered, -drawRect is where you display the contents of a view. You will only 'see' on screen what you draw here.
This is much more low-level than something like Flash where you might add a movieclip containing a line to the stage and some time later add another movieclip containing a line to the stage and now you see - two lines!
You will need to do some work and prdobably set up something like..
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
location = [touch locationInView:touch.view];
[self addNewLineFrom:lastPoint to:location];
lastPoint = location;
[self setNeedsDisplayInRect:CGRectMake(0, 0, 320, 480)];
}
- (void)drawRect:(CGRect)rect {
context = UIGraphicsGetCurrentContext();
for( Line *eachLine in lineArray )
[eachLine drawInContext:context];
}
I think you can see how to flesh this out to what you need.
Another way to approach this is to use CALayers. Using this approach you dont draw inside - - (void)drawRect at all - you add and remove layers, draw what you like inside them, and the view will handle compositing them together and drawing to the screen as needed. Probably more what you are looking for.
Every time that drawRect is called, you start with a blank slate. If you don't keep track of everything you have drawn before in order to draw it again, then you end up only drawing the latest swipe of your finger, and not any of the old ones. You will have to keep track of all of your finger swipes in order to redraw them every time drawRect is called.
Instead of redrawing every line, you can draw into an image and then just display the image in your drawRect: method. The image will accumulate the lines for you. Of course, this method makes undo more difficult to implement.
From the iPhone Application Programming Guide:
Use the UIGraphicsBeginImageContext
function to create a new image-based
graphics context. After creating this
context, you can draw your image
contents into it and then use the
UIGraphicsGetImageFromCurrentImageContext
function to generate an image based on
what you drew. (If desired, you can
even continue drawing and generate
additional images.) When you are done
creating images, use the
UIGraphicsEndImageContext function to
close the graphic context. If you
prefer using Core Graphics, you can
use the CGBitmapContextCreate function
to create a bitmap graphics context
and draw your image contents into it.
When you finish drawing, use the
CGBitmapContextCreateImage function to
create a CGImageRef from the bitmap
context. You can draw the Core
Graphics image directly or use this it
to initialize a UIImage object.
I am trying to better understand touches by writing a few liner which which would track touches:
- (void)drawRect:(CGRect)rect {
NSLog (#"My draw");
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 2);
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextMoveToPoint(context, prev.x, prev.y);
CGContextAddLineToPoint(context, cur.x, cur.y);
CGContextStrokePath(context);
return;
}
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
prev = [touch previousLocationInView:self];
cur = [touch locationInView:self];
[self setNeedsDisplayInRect:CGRectMake (prev.x, prev.y, fabs (cur.x-prev.x), fabs (cur.y - prev.y) )];
}
It clear that my setNeedsDisplay is not correct, as it only works for movement from positive to negative coordinates (from upper left to lower right). With this questions:
It appears that I have to individually write setNeedsDisplay for 4 different cases of potential movement directions (from pos X, Y to neg X,Y, from pos X to neg X, etc). Is this a correct approach, or I am missing some fundamental piece?
Even for the correct movement the line is not solid, but rather broken (depends on the speed of finger tracking). Why is not is solid line to track movement?
Thanks!
setNeedsDisplay does not immediately call drawRect. In fact, touchesMoved may be called several times before the next time the view is drawn, which is why you are seeing broken lines.
Your call to CGRectMake should be:
CGRectMake (MIN(cur.x, prev.x), MIN (cur.y, prev.y), fabs (cur.x-prev.x), fabs (cur.y - prev.y))
In my code, I generalize this with a function to make a rect from any two points, e.g.:
CGRect rectWithPoints(CGPoint a, CGPoint b)
{
return CGRectMake(
MIN (a.x, b.x),
MIN (a.y, b.y),
fabs (a.x - b.x),
fabs (a.y - b.y));
}