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.
Related
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
I recently came across this brilliant article about improving scroll performance with UITableViewCells: http://engineering.twitter.com/2012/02/simple-strategies-for-smooth-animation.html -- While many great tips can be found in this article, there is one in particular that has me intrigued:
Tweets in Twitter for iPhone 4.0 have a drop shadow on top of a subtle textured background. This presented a challenge, as blending is expensive. We solved this by reducing the area Core Animation has to consider non-opaque, by splitting the shadow areas from content area of the cell.
Using the iOS Simulator, clicking Debug - Color Blended Layers would reveal something like this:
The areas marked in red are blended, and the green area is opaque. Great. What the article fails to mention is: How do I implement this? It is my understanding that a UIView is either opaque or it's not. It seems to me that the only way to accomplish this would be with subviews, but the article explicitly states that as being a naive implementation:
Instead, our Tweet cells contain a single view with no subviews; a single drawRect: draws everything.
So how do I section off what is opaque, and what is not in my single drawRect: method?
In the example you show, I don't believe they're showing a background through the view. I think they're simulating a background in core graphics. In other words, in each cell they draw a light gray color for the background. They then draw the shadow (using transparency), and finally they draw the rest of the opaque content on the top. I could be wrong, but I don't believe you can make portions of the view transparent. If so, I'd be very, very interested in it because I use core graphics all the time, but I avoid rounded corners because blending the entire view for it just doesn't seem to be worth it.
Update
After doing some more research and looking through Apple's docs, I don't believe it's possible for only part of a view to be opaque. Also, after reading through Twitter's blog post, I don't think they are saying that they did so. Notice that when they say:
Instead, our Tweet cells contain a single view with no subviews; a single drawRect: draws everything.
They were specifically talking about UILabel and UIImageView. In other words, instead of using those views they're drawing the image directly using Core Graphics. As for the UILabels, I personally use Core Text since it has more font support but they may also be using something simpler like NSString's drawAtPoint:withFont: method. But the main point they're trying to get across is that the content of the cell is all one CG drawing.
Then they move to a new section: Avoid Blending. Here they make a point of saying that they avoid blending by:
splitting the shadow areas from content area of the cell.
The only way to do this is to use different views. There are two approaches they could be using, but first note that the cell dividers are themselves overlays (provided by the tableView). The first way is to use multiple views inside the cell. The second way is to underlay/overlay the shadows/blended-views behind/over the cells by inserting the appropriate views into the UIScrollView. Given their previous statement about having only one view/drawRect for each cell, this is probably what they're doing. Each method will have its challenges, but personally I think it would be easier to split the cell into 3 views (shadow, content, shadow). It would make it a lot easier to handle first/last cell situations.
I'd have to guess something along these lines
http://developer.apple.com/library/mac/#documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_shadows/dq_shadows.html
CGContextRef context = UIGraphicsGetCurrentContext();
UIBezierPath* path = [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:10.0f];
CGContextSaveGState(context);
CGRect leftRect = CGRectZero;
CGContextClipToRect(context, leftRect );
CGContextSetBlendMode(context, kCGBlendModeNormal);
// draw shadow
// Call the function CGContextSetShadow, passing the appropriate values.
// Perform all the drawing to which you want to apply shadows.
CGContextSetShadowWithColor(context, CGSizeMake(1.0f, 1.0f), 10.0f, [UIColor blackColor].CGColor);
CGContextAddPath(context, path.CGPath);
CGContextDrawPath(context, kCGPathStroke);
CGContextRestoreGState(context);
CGContextSaveGState(context);
CGRect middleSection = CGRectZero;
CGContextClipToRect(context, middleSection);
CGContextSetFillColorWithColor(context, self.backgroundColor.CGColor);
CGContextFillRect(context, self.bounds);
// draw opaque
CGContextSetBlendMode(context, kCGBlendModeCopy);
CGContextRestoreGState(context);
My opinion is: Don't let Core Animation draw shadows using the various layer properties. Just draw a prerendered image to both sides, which is in fact a shadow. To factor variable height of a cell in a stretch draw may do the trick.
EDIT:
If the background is plain a prerendered shadow can be applied to both sides without know it is affecting visual appeal.
In case that is not applicable the tableview has to be shrunk to be of the size without the shadow. Then the shadow can be blended without doing it for every cell but just "on top". It really doesn't scroll. This will only work if the shadow is without any "texture", else one will notice it's just applied on top.
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.
I'm trying to write a finger painting type app. I am starting a path in touchesBegan and adding to that path in touchesMoved. In touchesMoved, I use the following code:
CGContextMoveToPoint(context, lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(context, currentPoint.x, currentPoint.y);
CGContextStrokePath(context);
I call CGContextStokePath so that the path shows up in realtime as the user draws. The problem is that when using low alpha values, I get dots between the successive path segments where the end cap is essentially drawn twice - once for the previous segment, and once for the current segment.
I've tried using different line caps but the result isn't very pretty. I've also tried using the CGContextDrawPath function with all the various constants and I get the same result.
You can see the results here: http://www.idea-asylum.com/pathwithdots/index.html - It shows a line with alpha = 1.0 and one with alpha = 0.2.
Any ideas? Thanks in advance!
First, I hope you're drawing each shape into a separate layer (and I don't mean CALayer, I mean an internal construct unique to your app). This not only simplifies this task, it makes undo more or less painless (just move the last/topmost layer into a different array and hide it, and empty that array when the user draws a new layer).
Second, during the construction of the shape, don't only remember the last point. Create a CGMutablePath when the user begins the shape and add each subsequent point as another lineto. This also lets you keep the path around in that layer, which means you can throw the rendered image away if a low-memory warning arrives and re-create it the next time you need it.
Third, each time you update the shape during its creation, get its area so far, invalidate that section, and redraw all the layers under it as well as the shape being drawn (as it exists so far). That is, redraw the background, clobbering the new shape, and then draw the up-to-date version of the new shape on top.
Once you are constructing the shape as a single path, and stroking that single path in each draw cycle, the intersections between segments will disappear.
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.