Animating the drawing of a line programatically using Quartz 2d on IOS - ios

I'm trying to draw an animated growing line using Quartz 2d, by adding points to an existing line, gradually over time. I started drawing a new line, In the drawRect method of a UIView, by obtaining the CGContextRef, setting its draw properties, and moving the first point to (0,0).
CGContextRef context= UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context,[UIColor blueColor].CGColor);
CGContextSetLineWidth(context, 2);
CGContextMoveToPoint(context,0,0);
later, in my next drawRect call, i tried extending that line, by again, obtaining the CGContextRef, and adding a new point to it.
GContextRef context= UIGraphicsGetCurrentContext();
CGContextAddLineToPoint(context,x,y);
but it seems that the current CGContextRef doesn't have any record of my previous CGContextMoveToPoint command from the last drawRect call, therefore doesn't have any reference that i already started drawing a line.
Am i doing something wrong here? is there a way refering an already drawn line?

You basically need to treat each call to drawRect as if it was starting from scratch. Even if you are only asked to update a subrect of the view, you should assume that any state associated with the graphics context, such as drawing position and colours, will have been reset. So in your case, you need to keep track of the start position and redraw the whole line each time.

I think the better approach is to animate some thin UIView. Look my answer here.
If you need more than just horizontal line, rotate that view. I think it's better for the performance.

Related

Large UIBezierPath, slow rendering

For an app I am creating, I am using a UIBezierPath to store a path. The problem is that this path continually increases in length, and at a point, the app lags and becomes jerky. Because the path is continually increasing in length, I am constantly redrawing the view 100 times a second (if there is a better way to do this, please let me know). At a certain point, the app just becomes extremely jerky. I think it is because it takes too long to stroke the path.
You can see in my drawRect method that the graphics are translated. Very little of the path is on the screen, so is there a way to only stroke the part of the path that is visible? That is what I thought I was doing with the CGContextClip method, but it had no noticeable improvement.
- (void)drawRect:(CGRect)rect {
CGContextRef myContext = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(myContext, 0, yTranslation);
[[UIColor whiteColor] setStroke];
[bPath addLineToPoint:currentPoint];
[bPath setLineWidth:lineWidth];
CGContextClip(myContext);
[bPath stroke];
}
Thank you.
A couple of thoughts:
IMHO, you shouldn't be adding data points at a regular interval at all. You should be adding data points if and only if there are new data points to be added (e.g. touchesMoved or gesture recognizer gets called with UIGestureStateChanged). This reduces the number of points in bezier and defers the point at which performance problems impose themselves upon you. The process should be driven by the touch events, not a timer.
If some of your drawing is off screen, you can probably speed it up by checking to see if either of the points falls within the visible portion of the view (e.g CGRectContainsPoint). You should probably check for intersections of line segments with the CGRect (as it's theoretically possible for neither the start nor ends to be inside the visible rectangle, but for the line segment between them to intersect the rectangle).
You can also design this process so that it determines which segments are visible only when the view port moves. This can save you from needing to constantly iterate through a very large array.
At some point, there are diminishing returns of drawing individual paths versus a bitmap image, even just for the visible portion. Sometimes it's useful to render old paths as an image, and then draw new paths over that snapshot rather than redrawing the whole path every time. For example, I've used approach where when I'm starting a new gesture, I snapshot old image and only draw new gesture on top of that.
Caching is a possible solution: Draw the curve once on an in memory image with transparent background. Update this image only when the curve changes. Overlay this cached image on whatever you are drawing on. It should be cheaper in processing power.
The other possibility is to remove the unneeded points from the bezier curve after determining which ones will affect the current view, then render the resulting bezier curve.

How to add, rotate and drag line in IOS?

What would be the best way in accomplishing this? Let's say I have a method (not defined) that would allow a line with a fixed size and color to be drawn on the screen. This line would need to then accept rotate gestures and panning gestures in order to move around the screen. It won't resize, it only needs to rotate and translate.
What is the best way of going about this? Should lines be subviews or sublayers to parent view? What is the method for drawing a line in ios? How to handle multiple lines on screen? I just want someone to lead me down the right path in the ios graphics jungle.
Firstly, you need to consider how complex the whole drawing is. From your description it sounds like the task is relatively simple. If that is the case, then Core Graphics would be the way to go. If the drawing is significantly more complex, you should look at OpenGL ES and GLKit, though using OGL involves a fair bit more work
Assuming Core Graphics, I'd store the centre point, angle and length of the line, and change the angle and size using the gesture recognizers, and calculate the points to draw using basic trig. Loop over the points to draw in the view -drawRect method and draw each one with the appropriate CG functions - call [view setNeedsDisplay] or [view setNeedsDisplayInRect:areaToRedraw]to trigger the redraws. (The second method only redraws the part of the view you specify, and can be used to improved performance).
The first of a series of tutorials on Core Graphics is here.- the part on 'drawing lines' will be most relevant. I haven't done this one (I used the old edition of this book), but I've followed a lot of others from this site and found them very helpful
As a side note you'll probably need a way to focus on a particular line if you have more than one on the screen- an easy way would be to find the line centre point closest to the point the user touched.
It seems that the best API for drawing lines like you want is with Core Graphics. Put this code within your UIView's drawRect method:
/* Set the color that we want to use to draw the line */
[[UIColor redColor] set];
/* Get the current graphics context */
CGContextRef currentContext =UIGraphicsGetCurrentContext();
/* Set the width for the line */
CGContextSetLineWidth(currentContext,5.0f);
/* Start the line at this point */
CGContextMoveToPoint(currentContext,50.0f, 10.0f);
/* And end it at this point */
CGContextAddLineToPoint(currentContext,100.0f, 200.0f);
/* Use the context's current color to draw the line */
CGContextStrokePath(currentContext);
For the gesture recognition, use UIGestureRecognizers. Use the following methods
- (IBAction)handleRotate:(UIRotationGestureRecognizer *)recognizer
- (IBAction)handlePinch:(UIPinchGestureRecognizer *)recognizer
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer

Draw multiple lines programatically in iOS [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How To Draw line on touch event?
after lot of browsing, i couldn't find relevant answer yet. I want to draw multiple lines on iOS UIView programmatically during run time i.e allowing user to draw lines by dragging with his finger on the screen. however, the problem is when i draw second line, first drawn line is removed. I want first line to stay in its coordinate and user may draw second straight line by dragging over the screen. Attached is the code:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetLineWidth(context, 2.0);
CGContextMoveToPoint(context, touchPoint.x,touchPoint.y);
CGContextAddLineToPoint(context, lastTouchPoint.x, lastTouchPoint.y);
CGContextStrokePath(context);
in every call to drawRect you get passed a dirtyRect which defines an area you have to draw COMPLETELY, old content in that area is GONE. By default, when you have no better logic, you gotta draw everything thats in your view -- ALL lines!
it isnt an update call, old stuff is not preserved.
--
you could add all touches to an array in the order they happen and in drawRect draw every one.

How to animate size of custom drawn text?

I'm working on a educational app involving complex scripts in which I paint parts of different 'letters' different colours. UILabel is out of the question, so I've drilled down into Core Text and am having a surprisingly successful go of painting glyphs in CALayers.
What I haven't managed to do is animate the size of my custom drawn text. Basically I have text on 'tiles' (CALayers) that move around the screen. The moving around is okay, but now I want to zoom in on the ones that users press.
My idea is to try to cache a 'full resolution' tile and then draw it to scale during an animation of an image bounds. So far I've tried to draw and cache and then redraw such a tile in the following way:
UIGraphicsBeginImageContext(CGSizeMake(50, 50));
CGContextRef context = UIGraphicsGetCurrentContext();
//do some drawing...
myTextImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Then in [CALayer drawInContext:(CGContextRef)context],
I call [myTextImage drawAtPoint:CGPointZero].
When I run the app, the console shows <Error>: CGContextDrawImage: invalid context 0x0. Meanwhile I can perfectly while just continue to draw text in context in the same method even after that error is logged.
So I have two questions: (1) Why isn't this working? Should I be using CGBitmap instead?
And more important: (2) Is there a smarter way of solving the overall problem? Maybe storing my text as paths and then somehow getting CAAnimation to draw it at different scales as the bounds of the enclosing CALayer change?
Okay, this is much easier than I thought. Simply draw the text in the drawInContext: of a CALayer inside of a UIView. Then animate the view using the transform property, and the text will shrink or expand as you like.
Just pay attention to scaling so that the text doesn't get blocky. The easiest way to do that is to make sure the transform scale factors do not go above 1. In other words, make the 'default' 1:1 size of your UIView the largest size you ever want to display it.

On iOS, can something be drawn without having it done in drawRect?

Say, for an iOS app, if a user slides his finger on the screen, and then 50,000 dots are recorded. If the drawing is done for all these dots in drawRect, then next time the user touched the 50,001st dot, at the end of touchesMoved the following line
[self.view setNeedsDisplay];
will cause drawRect to run again and have all 50,001 dots drawn again. So for every 1 new dot (for any new movement of finger), all 50,001 dots will need to be redrawn and it is not an efficient method.
I tried just drawing the last dot in drawRect, and it will not "add to" the existing view, but "wipe everything out" and then draw one dot.
Is there a way to
1) draw that 1 extra dot without needing to have drawRect called?
2) or, can drawRect draw one extra dot without first wiping the whole screen out?
One approach for this would be to render into an image when the touches end, and then keep adding to the image as more paths are generated. You can store the paths in an array if you need and undo buffer or otherwise need to regenerate the drawing.
(you will need more code than this, obviously)
UIGraphicsBeginImageContextWithOptions(size, NO, 1.0);
CGContextRef context = UIGraphicsGetCurrentContext();
// do some drawing
UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Oh - fwiw, I have found setNeedsDisplayInRect: to be odd/buggy in iOS5. The first call to it is the full view rect, not the rect passed in as a param. At least that is what I found when I tried to use it. Maybe there is some implementation detail I overlooked.
Take a look at the method -(void)setNeedsDisplayInRect:. You can ask your view to redraw a specified rect of itself, but be careful when implementing drawRect: method -where you need to assume that the passed rect argument is just a piece of the whole rect of your view and probably your drawing logic will differ. Also you may consider the clearContextBeforeDrawing property of UIView.

Resources