Draw and Erase Lines with Touch - objective-c

I have a background image (ImageBG) and a foreground image (ImageFG) in separate UIImageViews. They are the same size and, as the name implies, ImageBG is behind ImageFG so you can't see ImageBG.
If the user starts to "draw" on them, instead of a line appearing where the user touches the screen, I want that portion of ImageFG to become transparent and reveal ImageBG.
The closest thing I can think of is a scratch-and-win.
I know how to do the drawing on a graphics context, but when I draw a transparent line (alpha = 0), well... I get a transparent line on top of whatever is currently in my graphics context, so basically nothing is drawn. This is what I use to set the stroke color.
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 1.0, 0.0, 0.0, 0.0)
What am I doing wrong? I know this is possible because there are apps out there that do it.
Any hints, tips or direction is appreciated.

So you have a layer that is some image like the grey stuff you want to scratch off, you write this image (in this example scratchImage which is a UIImageView and its UIImage .image )to the graphics context. You then stroke it with the blend mode set to kCGBlendModeClear then you save that image (with the cleared paths). This will reveal an image that you have underneath perhaps in another UIImageView. Note that in this instance, self is a UIView.
So outside touchesMoved create a variable to hold a CGPoint
CGPoint previousPoint;
CGPoint currentPoint;
Then in touchesBegan, set it to the previous point.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
previousPoint = [touch locationInView:self];
}
In touchesMoved, write the image to the context, stroke the image with the clear blend mode, save the image from the context, and set the previous point to the current point.
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
currentPoint = [touch locationInView:self];
UIGraphicsBeginImageContext(self.frame.size);
//This may not be necessary if you are just erasing, in my case I am
//adding lines repeatedly to an image so you may want to experiment with moving
//this to touchesBegan or something. Which of course would also require the begin
//graphics context etc.
[scratchImage.image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
CGContextSaveGState(UIGraphicsGetCurrentContext());
CGContextSetShouldAntialias(UIGraphicsGetCurrentContext(), YES);
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 5.0);
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 0.25, 0.25, 0.25, 1.0);
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, nil, previousPoint.x, previousPoint.y);
CGPathAddLineToPoint(path, nil, currentPoint.x, currentPoint.y);
CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeClear);
CGContextAddPath(UIGraphicsGetCurrentContext(), path);
CGContextStrokePath(UIGraphicsGetCurrentContext());
scratchImage.image = UIGraphicsGetImageFromCurrentImageContext();
CGContextRestoreGState(UIGraphicsGetCurrentContext());
UIGraphicsEndImageContext();
previousPoint = currentPoint;
}

Related

Clear CGContext Lines

Once I drawn some lines over an UIImage, (like a brush application), then I want to clear them, so like a rubber. I searched a lot on this site and on Google, and I tried some codes, but I couldn't find a correct solution.
Here's the code I use to write:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if(isDrawing == YES) {
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:mainImageView.superview];
UIGraphicsBeginImageContext(mainImageView.frame.size);
[mainImageView.image drawInRect:CGRectMake(0, 0, mainImageView.frame.size.width, mainImageView.frame.size.height)];
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), dimension);
const CGFloat *components = CGColorGetComponents([color CGColor]);
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), components[0], components[1], components[2], components[3]);
CGContextBeginPath(UIGraphicsGetCurrentContext());
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y);
CGContextStrokePath(UIGraphicsGetCurrentContext());
mainImageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
lastPoint = currentPoint;
} else {
//Clear current line (like a rubber would do)
}
}
In this scenario [UIColor clearColor] won't work, use CGContextClearRect(context,rect); to clear the region!
Fill desired part of the context with the [UIColor clearColor].
The correct approach is using a Bezier path that:
records every drawings ( but actually does NOT draw.. in DrawRect we will draw.. using "stroke")
has thickness, color and so on...
draws effectively on view
Clearing it (using "removeAllPoints") and invoking setNeedsDisplay will make the background reappear.
If You want to rubber-clear the path you wrote using another tool, (say You use a pencil tool to write in blue.. and a Rubber tool to clear the blue lines and make background to reappear) you can use "containsPoint:" to remove some points form bezier path.
We did in such way.
using bezier path is NOT trivial but very exciting.. :)

Objective-C: Touch Draw Opacity

I am trying to make a drawing app that has a control on the opacity of the brush but when i tried to lower the opacity the result is like this. I used core graphics. (check the image).
How will i resolve this issue?
Here are some of my codes.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event//upon touches
{
UITouch *touch = [touches anyObject];
previousPoint1 = [touch locationInView:self.view];
previousPoint2 = [touch locationInView:self.view];
currentTouch = [touch locationInView:self.view];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event//upon moving
{
UITouch *touch = [touches anyObject];
previousPoint2 = previousPoint1;
previousPoint1 = currentTouch;
currentTouch = [touch locationInView:self.view];
CGPoint mid1 = midPoint(previousPoint2, previousPoint1);
CGPoint mid2 = midPoint(currentTouch, previousPoint1);
UIGraphicsBeginImageContext(CGSizeMake(1024, 768));
[imgDraw.image drawInRect:CGRectMake(0, 0, 1024, 768)];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineCap(context,kCGLineCapRound);
CGContextSetLineWidth(context, slider.value);
CGContextSetBlendMode(context, blendMode);
CGContextSetRGBStrokeColor(context,red, green, blue, 0.5);
CGContextBeginPath(context);
CGContextMoveToPoint(context, mid1.x, mid1.y);
CGContextAddQuadCurveToPoint(context, previousPoint1.x, previousPoint1.y, mid2.x, mid2.y);
CGContextStrokePath(context);
imgDraw.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsGetCurrentContext();
endingPoint=currentTouch;
}
Rather than updating the image for every touchesMoved, I'd suggest you don't resave the image every time, but rather add each touchesMoved to an array of data points. Then, in your drawing routine (as NSResponder suggests) pull up the original image and then redraw the whole path mapped out by your array of points. If you want to update your image at some point, do it on touchesEnded, but not for every touchesMoved.
Don't do your drawing in -touchesMoved:withEvent:. In any event method, you should be updating what needs to be drawn, and then send -setNeedsDisplay.
As your code is written above, you're creating a path between each pair of locations you're getting from the event messages.
You should create a path in -touchesBegan:withEvent:, and add to it in -touchesMoved:withEvent:.

How to detect which word touched with CoreText

I am using CoreText to layout a custom view. The next step for me is to detect which word is tapped on a touch event/gesture event. I have done research on this and found advise on how to custom label a url to receive touches - but nothing generic. Does any one have any idea on how to do this?
UPDATE:
Here is the code within my drawRect: method
self.attribString = [aString copy];
CGContextRef context = UIGraphicsGetCurrentContext();
// Flip the coordinate system
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGMutablePathRef path = CGPathCreateMutable(); //1
CGPathAddRect(path, NULL, self.bounds );
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)aString); //3
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, [aString length]), path, NULL);
CTFrameDraw(frame, context); //4
UIGraphicsPushContext(context);
frameRef = frame;
CFRelease(path);
CFRelease(framesetter);
Here is where I am trying to handle the touch event
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
CGContextRef context = UIGraphicsGetCurrentContext();
CFArrayRef lines = CTFrameGetLines(frameRef);
for(CFIndex i = 0; i < CFArrayGetCount(lines); i++)
{
CTLineRef line = CFArrayGetValueAtIndex(lines, i);
CGRect lineBounds = CTLineGetImageBounds(line, context);
NSLog(#"Line %ld (%f, %f, %f, %f)", i, lineBounds.origin.x, lineBounds.origin.y, lineBounds.size.width, lineBounds.size.height);
NSLog(#"Point (%f, %f)", point.x, point.y);
if(CGRectContainsPoint(lineBounds, point))
{
It seems that CTLineGetImageBounds is returning a wrong origin (the size seems correct) here is one example of the NSLog "Line 0 (0.562500, -0.281250, 279.837891, 17.753906)".
There is no "current context" in touchesEnded:withEvent:. You are not drawing at this point. So you can't call CTLineGetImageBounds() meaningfully.
I believe the best solution here is to use CTFrameGetLineOrigins() to find the correct line (by checking the Y origins), and then using CTLineGetStringIndexForPosition() to find the correct character within the line (after subtracting the line origin from point). This works best for simple stacked lines that run the entire view (such as yours).
Other solutions:
Calculate all the line rectangles during drawRect: and cache them. Then you can just do rectangle checks in touchesEnded:.... That's a very good solution if drawing is less common than tapping. If drawing is significantly more common than tapping, then that's a bad approach.
Do all your calculations using CTLineGetTypographicBounds(). That doesn't require a graphics context. You can use this to work out the rectangles.
In drawRect: generate a CGLayer with the current context and store it in an ivar. Then use the context from the CGLayer to calculate CTLineGetImageBounds(). The context from the CGLayer will be "compatible" with the graphics context you're using to draw.
Side note: Why are you calling UIGraphicsPushContext(context); in drawRect:? You're setting the current context to the current context. That doesn't make sense. And I don't see a corresponding UIGraphicsPopContext().

Objective C: Touch Draw On UIScrollView

I am trying to make a drawing book app with different pages on it using a pageEnabled UIScrollView.
What am i gonna use?? touch events are not working.
i have this code before on touches moved:
touchSwiped = YES;
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:self.view];
currentPoint.y -= 20;
UIGraphicsBeginImageContext(self.view.frame.size);
[drawView.image drawInRect:CGRectMake(0, 0, drawView.frame.size.width, drawView.frame.size.height)];
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 15.0);
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 0, 0, 0, 1.0);
CGContextBeginPath(UIGraphicsGetCurrentContext());
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y);
CGContextStrokePath(UIGraphicsGetCurrentContext());
drawView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
lastPoint = currentPoint;
touchMoved++;
if (touchMoved == 10) {
touchMoved = 0;
}
i don't have any idea what adjustments i need to do in order to implement this.
anyone??
thanks in advance.
If you are using a UIPanGestureRecognizer as mentioned in your comments, then you have set the minimumNumberOfTouches to something greater than 1, right? Otherwise, how does it tell the difference between drawing and scrolling?
My recommendations are:
Do not use a UIScrollView unless you first figure out a way to conclusively determine if a touch is for drawing or for scrolling as intended by the user. Use a subclass of UIView instead, and then maybe use buttons to move between pages.
Or you could have a button that toggles between drawing and scrolling (paging) between the pages of your scroll view.
Check out this website for some tips and actual code to do the drawing http://www.ifans.com/forums/showthread.php?t=132024

Cocoa-touch - drawing with core graphics to a touch point

so i want to do something which seems pretty simple but is proving other wise, i want to just draw a square at the point that touch i registered. i cant seem to get it to work though. in touchesBegan i am calling a custom method called drawSquare that is sent a CGRect. i know i must be doing something simple wrong but i don't know enough about drawing primitives in xcode/cocoa-touch. any help would be greatly appreciated. also here is my code:
- (void)drawSquare:(CGRect)rect{
//Get the CGContext from this view
CGContextRef context = UIGraphicsGetCurrentContext();
//Draw a rectangle
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
//Define a rectangle
CGContextAddRect(context, rect);
//Draw it
CGContextFillPath(context);
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches) {
// gets the coordinats of the touch with respect to the specified view.
CGPoint touchPoint = [touch locationInView:self];
CGRect rect = CGRectMake(touchPoint.x, touchPoint.y, 50, 50);
[self drawSquare:rect];
}
}
Don't try to do your drawing in your event methods. Update your list of what you want to draw, and send -setNeedsDisplay.