Redrawing a path using CGContextStrokePath after initial load - iphone

I have a basic map view in my app which contains a set of user definable way points. When the view is loaded I draw a path connecting the way points. This works well.
However, when the user drags a way point around the view, I would like it to re-draw the paths and that is my problem. I dont know how to hacant get it to draw anything after the first time. Here's my code:
- (void)drawRect:(CGRect)rect { ////// works perfectly
context = UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(context, 1, 0, 1, .7);
CGContextSetLineWidth(context, 20.0);
WaypointViewController *w = [arrayOfWaypoints objectAtIndex:0];
CGPoint startPoint = w.view.center;
CGContextMoveToPoint(context, startPoint.x, startPoint.y);
for (int i = 1; i<[arrayOfWaypoints count]; i++) {
WaypointViewController *w2 = [arrayOfWaypoints objectAtIndex:i];
CGPoint nextPoint = w2.view.center;
CGContextAddLineToPoint(context,nextPoint.x, nextPoint.y);
}
CGContextStrokePath(context);
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if (moving) {
UITouch *touch = [touches anyObject];
currentWaypoint.view.center = [touch locationInView:self];
[delegate setUserInteraction:NO];
[self drawInContext:context];
[NSThread detachNewThreadSelector:#selector(drawInContext:) toTarget:self withObject:nil];
}
}
- (void)drawInContext:(CGContextRef)context { ///gets called, but does nothing
CGContextSetRGBStrokeColor(context, 1, 0, 1, .7);
CGContextSetLineWidth(context, 20.0);
WaypointViewController *w = [arrayOfWaypoints objectAtIndex:0];
CGPoint startPoint = w.view.center;
CGContextMoveToPoint(context, startPoint.x, startPoint.y);
for (int i = 1; i<[arrayOfWaypoints count]; i++) {
WaypointViewController *w2 = [arrayOfWaypoints objectAtIndex:i];
CGPoint nextPoint = w2.view.center;
CGContextAddLineToPoint(context,nextPoint.x, nextPoint.y);
}
CGContextStrokePath(context);
}

You can not use the context that is active in drawRect: anywhere else.
Instead of calling [self drawInContext:context] call [self setNeedsDisplay]
The context that you are pointing to in your drawRect: might be freed or might still be active you don't know, but either way there is no way to get the bits you put into that context onto the screen.
Also, please read the docs on drawing on iOS here

You may not save the context for use after drawRect: returns. What you need to do is call [self setNeedsDisplay] (or setNeedsDisplayInRect:, if you can determine the bounding rectangle of the change), which will cause the system to call drawRect: again for you.

You can do all your drawing only in drawRect ...Hope this helps ... hence - (void)drawInContext:(CGContextRef)context will not be able to do any drawing. if after user's touch action , you need to do some redrawing then you need to call [self setNeedsDisplay] in your touchmethods.

Related

Draw straight line on uiview

I'm writing a small word search game for the iPad.
i have a UIViewController and I tile the letters random in a UIView (lettersView). I set the label's tag in a 10x10 size matrix, using a UILabel for each letter.
Here is my problem: I don't know how to draw over letters (UILabels).
I have researched the matter and am confused which way to use (touch, opengl, quartz, hittest).
I just want to draw a straight line from the first point to the last point (touch down to touch up).
when user taps on the lettersView, I must find which letter was tapped. I can find the letter if I get the which UILabel was tapped first.
while the user moves their finger, it should draw the line, but while the user moves it must delete the old ones.
when the user touches up how can I get the last UILabel? i can check if its drawable, if I get the first and last uilabel.
(i found a small drawing sample written in ARC mode. but i cant convert it to non-ARC.)
i did all what you said. but i have some problems.
it draws the line on drawRect method. when i draw second line, it deletes the old one.
here is the codes:
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
touchStart = [[touches anyObject] locationInView:self];
float xPos=touchStart.x;
float yPos=touchStart.y;
startIndexCol = floor(xPos / letterWidth);
startIndexRow = floor(yPos / letterHeight);
xPos = (startIndexCol * letterWidth) + letterWidth/2;
yPos = (startIndexRow * letterHeight) + letterHeight/2;
touchStart = CGPointMake(xPos, yPos);
touchCurrent = touchStart;
[self setNeedsDisplay];
}
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
touchCurrent = [[touches anyObject] locationInView:self];
//CGRect frame = CGRectMake(touchStart.x, touchStart.y, touchCurrent.x, touchCurrent.y);
//[self setNeedsDisplayInRect:frame];
[self setNeedsDisplay];
}
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
touchCurrent = [[touches anyObject] locationInView:self];
//[self hitTest:touchCurrent withEvent:event];
float xPos=touchCurrent.x;
float yPos=touchCurrent.y;
endIndexCol = floor(xPos / letterWidth);
endIndexRow = floor(yPos / letterHeight);
xPos = (endIndexCol * letterWidth) + letterWidth/2;
yPos = (endIndexRow * letterHeight) + letterHeight/2;
touchCurrent = CGPointMake(xPos, yPos);
//CGRect frame = CGRectMake(touchStart.x, touchStart.y, touchCurrent.x, touchCurrent.y);
//[self setNeedsDisplayInRect:frame];
//check if it can be drawn
if ((startIndexCol == endIndexCol) && (startIndexRow == endIndexRow)) {
//clear, do nothing
touchStart = touchCurrent = (CGPoint) { -1, -1 };
startIndexCol = startIndexRow = endIndexCol = endIndexRow = -1;
}
else{
if (startIndexRow == endIndexRow) {//horizontal
if (endIndexCol > startIndexCol) {
[delegate checkWordInHorizontal:startIndexRow start:startIndexCol end:endIndexCol];
}
else{
[delegate checkWordInHorizontalBack:startIndexRow start:startIndexCol end:endIndexCol];
}
}
else if (startIndexCol == endIndexCol){ //vertical
if (endIndexRow > startIndexRow) {
[delegate checkWordInVertical:startIndexCol start:startIndexRow end:endIndexRow];
}
else{
[delegate checkWordInVerticalBack:startIndexCol start:startIndexRow end:endIndexRow];
}
}
}
[self setNeedsDisplay];
}
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
UIView *subview = [super hitTest:point withEvent:event];
if ([subview isEqual:self]) {
NSLog(#"point:%f - %f", point.x, point.y);
return self;
}
return nil;
}
-(void)drawLine{
NSLog(#"draw it");
drawIt=YES;
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
if (drawIt) {
NSLog(#"drawit");
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(ctx, 20.0);
CGContextSetRGBStrokeColor(ctx, 1.0, 2.0, 0, 0.5);
CGContextMoveToPoint(ctx, touchStart.x, touchStart.y);
CGContextAddLineToPoint(ctx, touchCurrent.x, touchCurrent.y);
CGContextStrokePath(ctx);
drawIt=NO;
touchStart = touchCurrent = (CGPoint) { -1, -1 };
startIndexCol = startIndexRow = endIndexCol = endIndexRow = -1;
}
[[UIColor redColor] setStroke];
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:touchStart];
[path addLineToPoint:touchCurrent];
[path setLineWidth:20.0];
[path strokeWithBlendMode:kCGBlendModeNormal alpha:0.5];
// [path stroke];
}
First, the UILabels are likely to cause you more trouble than good. For such as simple grid, it is likely easier to just draw the letters by hand in drawRect:, along with your connecting line. The overall structure would look like this:
In touchesBegan:withEvent:, you would make a note of the starting point in an ivar.
In touchesMoved:withEvent:, you would update the end-point in an ivar and call [self setNeedsDisplayInRect:]. Pass the rectangle defined by your two points.
In touchesEnded:withEvent:, you would make a hit-test, do whatever processing that entails, and then clear your starting and end points and call [self setNeedsDisplayInRect:].
By "make a hit-test," I mean calculate which letter is under the touch. Since you're laying these out in a grid, this should be very simple math.
In drawRect:, you would use [NSString drawAtPoint:withFont:] to draw each letter. You would then use UIBezierPath to draw the line (you could also use CGPathRef, but I'd personally use the UIKit object).

Trying to fill a path with colour in CGContext without much luck

I currently have the following code to try and allow the user to draw a dotted path and make a custom shape. Once they have made this shape, I wanted it to automatically be filled with colour. That isn't happening.
At the moment I am getting this error for the code below:
<Error>: CGContextClosePath: no current point.
Here's the code I'm using:
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint previous = [touch previousLocationInView:self];
CGPoint current = [touch locationInView:self];
#define SQR(x) ((x)*(x))
//Check for a minimal distance to avoid silly data
if ((SQR(current.x - self.previousPoint2.x) + SQR(current.y - self.previousPoint2.y)) > SQR(10))
{
float dashPhase = 5.0;
float dashLengths[] = {10, 10};
CGContextSetLineDash(context,
dashPhase, dashLengths, 2);
CGContextSetFillColorWithColor(context, [[UIColor lightGrayColor] CGColor]);
CGContextFillPath(context);
CGContextSetLineWidth(context, 2);
CGFloat gray[4] = {0.5f, 0.5f, 0.5f, 1.0f};
CGContextSetStrokeColor(context, gray);
self.brushSize = 5;
self.brushColor = [UIColor lightGrayColor];
self.previousPoint2 = self.previousPoint1;
self.previousPoint1 = previous;
self.currentPoint = current;
// calculate mid point
self.mid1 = [self pointBetween:self.previousPoint1 andPoint:self.previousPoint2];
self.mid2 = [self pointBetween:self.currentPoint andPoint:self.previousPoint1];
if(self.paths.count == 0)
{
UIBezierPath* newPath = [UIBezierPath bezierPath];
CGContextBeginPath(context);
[newPath moveToPoint:self.mid1];
[newPath addLineToPoint:self.mid2];
[self.paths addObject:newPath];
CGContextClosePath(context);
}
else
{
UIBezierPath* lastPath = [self.paths lastObject];
CGContextBeginPath(context);
[lastPath addLineToPoint:self.mid2];
[self.paths replaceObjectAtIndex:[self.paths indexOfObject:[self.paths lastObject]] withObject:lastPath];
CGContextClosePath(context);
}
//Save
[self.pathColors addObject:self.brushColor];
self.needsToRedraw = YES;
[self setNeedsDisplayInRect:[self dirtyRect]];
//[self setNeedsDisplay];
}
}
Why is this happening and why is the inside of the path not being filled with colour?
Your code has a few issues:
You should be doing your drawing in your view's drawRect: method, not the touch handler.
You never set the variable context with a value for the current context. Do that with the UIGraphicsGetCurrentContext() method. Again, within your drawRect: method.
You go through the trouble of creating a UIBezierPath object, but you never use it. Do that by calling CGContextAddPath( context, newPath.CGPath ), changing the variable name as needed in the two places you appear to the using a UIBezierPath.
Keep the call to setNeedsDisplayInRect: in your touch handler method. That tells the system to do the work to update your view using the drawing that your (yet to be implemented) drawRect: method draws.

how to draw continuous lines by QuartzCore in iOS

I have got a question about quartz core on iOS
When I use Quartz to draw lines, the following happens and I can't find the reason:
when I draw the second line the first line would disappear
when I draw the third line the second would the disappear, and the 1st and 3rd would show
..
when I draw the 2n+1th line, 1,3,5,...2n-1th lines shows, and 2,4,6,8... 2n dissappears
see the code below. I don't save contexts and paths
as my understanding, I think it should be one of the two cases
display all lines I drawn
display the last line I drawn and the previous lines should disappear
But the two cases don't happen.
- (void)drawInContext:(CGContextRef)context {
// 橡皮擦
//CGContextClearRect(context, CGRectMake(0, 0, 320, 480));
CGContextSetLineWidth(context, 4.0);
CGContextMoveToPoint(context, previousPoint.x, previousPoint.y);
CGContextSetRGBStrokeColor(context, 0, 1.0, 1.0, 1.0);
CGContextAddLineToPoint(context, nextPoint.x,nextPoint.y);
CGContextStrokePath(context);
//NIF_TRACE(#"began : %# moved : %#", NSStringFromCGPoint(previousPoint),NSStringFromCGPoint(nextPoint));
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
previousPoint = [touch locationInView:self];
nextPoint = [touch locationInView:self];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
previousPoint = nextPoint;
nextPoint = [touch locationInView:self];
[self setNeedsDisplay];
}
I saw a demo that use NSMutableArray to save all UIBezierPaths it drawn, when the View redraw, it transfer paths saved in array, restores them in drawRect:
UIBezierPath is a wrapper of Objective C, and it only works in 3.2+
I need do something make it work in 3.0+
I think it must be exists a better method to save contexts and paths (Colors,Paths,strokeWidths)
Anybody have ideas?
Every time you "drawInContext" you are clearing the drawing area and placing the new line.... This clear code:
CGContextClearRect(context, CGRectMake(0, 0, 320, 480));
Needs to be somewhere else in your code... somewhere where it will only be activated 1 time (or if you need to clear the whole drawing (perhaps an erase all function))
You need to save the image on canvas:
CGImageRef imageRef;
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
if (imageRef) {
// Restore the screen that was previously saved
CGContextTranslateCTM(context, 0, rect.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextDrawImage(context, rect, imageRef);
CGImageRelease(imageRef);
CGContextTranslateCTM(context, 0, rect.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
}
// Your drawing code here...
imageRef = CGBitmapContextCreateImage(context);
}

(iphone) a very simple Iphone game

Hi I want to implement a very simple game on iphone. I have a image and a text. If a user draw a line between the image and the text then it says "correct"
Now what I am thinking is that whenever a user touch the screen I will get the location (like X,Y) of that touching spot ( which method to call to get the location? ) and then I will draw a black dot there. If the user continuously and smoothly touch the screen then a line will form. Finally I will only need to judge whether the starting point of the line locates within the image and the ending point locates within the text.
I dont know whether my idea is a correct way to implement this kinda game. If it is how to get coordinate of touching spot and draw a black dot in that specific location?
Thanks for helping!
You may get the touch coordinates in touchesMoved of your view controller:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint touch_point = [touch locationInView:self.view];
NSLog(#"x: %.0f y: %.0f", touch_point.x, touch_point.y);
}
To draw the line, you have add a view and draw line in drawRect:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 1.0);
CGContextSetStrokeColorWithColor(context, [UIColor orangeColor].CGColor);
// ...
CGContextMoveToPoint(context, one_x, one_y);
CGContextAddLineToPoint(context, two_x, two_y);
CGContextStrokePath(context);
// ...
}
where one_x one_y two_x two_x are coordinates for two points ..
This is exactly how the iPhone touch delegation works. It calls touchesBegan:, touchesMoved: and touchesEnded:. To draw the lines, you need this code:
CGContextRef context = UIGraphicsGetCurrentContext();
[current set];
CGContextSetLineWidth(context, 4.0f);
for (int i = 0; i < (self.points.count - 1); i++)
{
CGPoint pt1 = POINT(i);
CGPoint pt2 = POINT(i+1);
CGContextMoveToPoint(context, pt1.x, pt1.y);
CGContextAddLineToPoint(context, pt2.x, pt2.y);
CGContextStrokePath(context);
}
And with the help of this code, you should be ready to make it. If you need any help, just comment on this or make another question.
CGPoint pt = [[touches anyObject] locationInView:self];
[self.points addObject:[NSValue valueWithCGPoint:pt]];
[self setNeedsDisplay];

Draw rectangle using Core Graphics with live preview

I'm creating a simple drawing application and would like some help with drawing rectangles based on the user's touch events. I'm able to draw a rectangle using Core Graphics from the point in touchesBegan to the point in touchesEnded. This rectangle is then displayed once the touchesEnded event is fired.
However, I would like to be able to do this in a 'live' fashion. Meaning, the rectangle is updated and displayed as the user drags their finger. Is this possible? Any help would be greatly appreciated, thank you!
UPDATE:
I was able to get this to work by using the following code. It works perfectly fine for small images, however it get's very slow for large images. I realize my solution is hugely inefficient and was wondering if anyone could suggest a better solution. Thanks.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
_firstPoint = [touch locationInView:self.view];
_firstPoint.y -= 40;
_lastPoint = _firstPoint;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:self.view];
currentPoint.y -= 40;
[self drawSquareFrom:_firstPoint to:currentPoint];
_lastPoint = currentPoint;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:self.view];
currentPoint.y -= 40;
[self drawSquareFrom:_firstPoint to:currentPoint];
[_modifiedImage release];
_modifiedImage = [[UIImage alloc] initWithCGImage:_imageView.image.CGImage];
}
- (void)drawSquareFrom:(CGPoint)firstPoint to:(CGPoint)lastPoint
{
_imageView.image = _modifiedImage;
CGFloat width = lastPoint.x - firstPoint.x;
CGFloat height = lastPoint.y - firstPoint.y;
_path = [UIBezierPath bezierPathWithRect:CGRectMake(firstPoint.x, firstPoint.y, width, height)];
UIGraphicsBeginImageContext( _originalImage.size );
[_imageView.image drawInRect:CGRectMake(0, 0, _originalImage.size.width, _originalImage.size.height)];
_path.lineWidth = 10;
[[UIColor redColor] setStroke];
[[UIColor clearColor] setFill];
[_path fill];
[_path stroke];
_imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
What you are doing right now is creating a paired CGImage and CGBitmapContext for every call to drawSquareFrom:to: and disposing them each time. Instead, create a single paired CGImage and CGBitmapContext that you reuse for each call.
-(void) drawSquareFrom:(CGPoint)from to:(CGPoint)to {
CGContextRef context = [self offscreenContext];
CGRect draw , full = [self offscreenContextBounds];
draw.origin = from;
draw.size.width = to.x - from.x;
draw.size.height = to.y - from.y;
draw = CGRectStandardize( draw );
[[UIColor redColor] setStroke];
[[UIColor clearColor] setFill];
CGContextClearRect( context , full );
CGContextFillRect( context , draw );
CGContextStrokeRectWithWidth( context , draw , 10 );
[_imageView setNeedsDisplay];
}
You create a paired CGImage and CGContext like this, although there are some ... to fill in. All the myX variables are intended to be class members.
-(CGContextRef) offscreenContext {
if ( nil == myContext ) {
size_t width = 400;
size_t height = 300;
CFMutableDataRef data = CFDataCreateMutable( NULL , width * height * 4 ); // 4 is bytes per pixel
CGDataProviderRef provider = CGDataProviderCreateWithCFData( data );
CGImageRef image = CGImageCreate( width , height , ... , provider , ... );
CGBitmapContextRef context = CGBitmapContextCreate( CFDataGetMutableBytePtr( data ) , width , height , ... );
CFRelease( data ); // retained by provider I think
CGDataProviderRelease( provider ); // retained by image
myImage = image;
myContext = context;
myContextFrame.origin = CGPointZero;
myContextFrame.size.width = width;
myContextFrame.size.height = height;
_imageView.image = [UIImage imageWithCGImage:image];
}
return myContext;
}
-(CGRect) offscreenContextBounds {
return myContextFrame;
}
This sets the _imageView.image to the newly created image. In drawSquareFrom:to: I assume that setNeedsDisplay is sufficient to draw the changes to made, but it may be that you need to assign a new UIImage each time that wraps the same CGImage.
Just use two Contexts.
In one you just "preview" draw the Rectangle and you can clear it on TouchesMoved.
On TouchesEnd you finally draw on your original context and then on the Image.