how can i move a UIBezierPath object with touchesMoved? - iphone

After drawing lines and circles from bezier path objects i want to now move these objects across the screen. First as i touch on the path object it should get selected which i have done using containsPoint: method.
Now i want that this selected object should get move as i drag my fingure. I am wondering how can i move a stroked bezierpath object to a new location ?
here is my code in touches began:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
startPoint = [touch locationInView:self];
//NSLog(#"start point:- %f, %f", startPoint.x, startPoint.y);
isHitInPath = NO;
if(!isTextMode)
{
for (NSDictionary *testDict in pathArray)
{
if([((UIBezierPath *)[testDict objectForKey:#"path"]) containsPoint:startPoint])// if starting touch is on an object of bezierpath
{
NSLog(#"touch point is in path: %# >>>>>>>>>>>>>>", [testDict objectForKey:#"path"]);
isHitInPath = YES;
currentSelectedPath = ((UIBezierPath *)[testDict objectForKey:#"path"]);
CAShapeLayer *centerline = [CAShapeLayer layer];
centerline.path = currentSelectedPath.CGPath;
centerline.strokeColor = [UIColor whiteColor].CGColor;
centerline.fillColor = [UIColor clearColor].CGColor;
centerline.lineWidth = 1.0;
centerline.lineDashPattern = [NSArray arrayWithObjects:[NSNumber numberWithInt:6], [NSNumber numberWithInt:6], nil];
[self.layer addSublayer:centerline];
// showing animation on line
CABasicAnimation *dashAnimation;
dashAnimation = [CABasicAnimation animationWithKeyPath:#"lineDashPhase"];
[dashAnimation setFromValue:[NSNumber numberWithFloat:0.0f]];
[dashAnimation setToValue:[NSNumber numberWithFloat:45.0f]];
[dashAnimation setDuration:1.0f];
[dashAnimation setRepeatCount:10000];
[centerline addAnimation:dashAnimation forKey:#"linePhase"];
break;
}
}
}
What should be the correct way of moving (or may be removing old path object and then creating new one of the same size and figure and then move it ) a path object in touches moved.

Sorry for late reply , i solved it. Here is the code from my App:
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
endPoint = [touch locationInView:self];
if(isAnyShapeSelected) // shape is selected and getting moved
{
[self deselectShape];// deselect the selected shape
// then create a new bezier path object with a new location (transformed bezierpath object)
CGPathRef _path = currentSelectedPath.CGPath;// currentSelectedPath is a path which we have selected in touches begin (or while moving)
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformTranslate(transform, endPoint.x-startPoint.x, endPoint.y-startPoint.y);
_path = (CGPathCreateMutableCopyByTransformingPath(_path, &transform));
currentSelectedPath.CGPath = _path;
// UIBezierPath *uiPath = [UIBezierPath bezierPathWithCGPath:_path];
// uiPath.lineWidth = currentSelectedPath.lineWidth;
startPoint = endPoint;
[self selectShape:currentSelectedPath];// select the shape again
[dict_path removeAllObjects];//remove all object from current drawing dictionary
// NSLog(#"pathArray: %#", pathArray);
// Add values to dictionary
if([[pathArray objectAtIndex:selectedShapeIndex] objectForKey:#"shape"])// if key 'shape' exist for the selected shape, if filled arrow is selected
[dict_path setValue:#"arrow" forKey:#"shape"]; // then there must be no affect on it while moving it
[dict_path setValue:currentSelectedPath forKey:#"path"];
[dict_path setValue:[[pathArray objectAtIndex:selectedShapeIndex] objectForKey:#"color"] forKey:#"color"];
[pathArray removeObjectAtIndex:selectedShapeIndex];// REMOVING object frm pathArray
NSDictionary *tempDict = [NSDictionary dictionaryWithDictionary:dict_path];
[pathArray insertObject:tempDict atIndex:selectedShapeIndex];// addding selected shape at every pixel movement
[dict_path removeAllObjects];
CGPathRelease(_path);
[self setNeedsDisplay];
}
}
// here is my draw rect method
- (void)drawRect:(CGRect)rect
{
for(NSDictionary *_pathDict in pathArray)
{
if([_pathDict objectForKey:#"shape"])// if shape is arrow then fill it(shape key is taken for arrow)
{
[((UIColor *)[_pathDict valueForKey:#"color"]) setFill]; // this method will choose the color from the receiver color object (in this case this object is :strokeColor)
[[_pathDict valueForKey:#"path"] fill];
}
[((UIColor *)[_pathDict valueForKey:#"color"]) setStroke]; // this method will choose the color from the receiver color object (in this case this object is :strokeColor)
[[_pathDict valueForKey:#"path"] strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
}
if([dict_path objectForKey:#"shape"])
{
[[dict_path objectForKey:#"color"] setFill]; // this method will choose the color from the receiver color object (in this case this object is :strokeColor)
[[dict_path objectForKey:#"path"] fill];
}
[[dict_path objectForKey:#"color"] setStroke]; // this method will choose the color from the receiver color object (in this case this object is :strokeColor)
[[dict_path objectForKey:#"path"] strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
}
-(void)deselectShape // deselect shape
{
[centerline removeFromSuperlayer];
isAnyShapeSelected = NO;
[btnResize removeFromSuperview];
// show hide delete button
[self.delegate showDeleteButton:isAnyShapeSelected];//delegate to active and inactive delete button
}
For how to select a shape on touchesBegin see my anwer at How do I detect a touch on a UIBezierPath and move a ball along that?

Related

iphone:How to animate uibeizserpath set stroke color?

I have an one view with horizontal and vertical beizer paths.The resulting view looks like the foloowing
Now on touch of the view if user has touched the one of the path,I have to change the color of that path by animation that means on touch of a path ,path's storke color will be changed animaticllay.
I am able to do it with the normal way ,that means when user touch the path I am able to change the color but not with the animation effect.
I have created the sub class of UIBeizerPath with properties like defaultStorkeColor,SelectedStorkeColor and I am manipulating them in the drawrect.
My drawrect is
-(void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
for(BCBeizerPath * path in pathsArray)
{
if(path.selected)
{
[path.selectedStrokeColor set];
}
else
{
[path.defaultStrokeColor set];
}
[path stroke];
}
}
Please help me in implementing that.
I didn't try this code but hit with a same scenario before, which I had resolved by doing something like the below:
In -drawRect draw these patterns over a CAShapeLayer using UIBezierPath and add it to your CustomView's layer
[self.layer addSubLayer:self.shapeLayer_main];
Then get the frame of the rect you wish to draw the stroke using
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
CGPoint touchLocation = [touch locationInView:self];
//Check in which rect in your view contains the touch location
//Then call the method to draw the stroke with the detected rect as the parameter
[self drawStrokeWithRect:touchedRect];
}
In the method,
-(void)drawStrokeWithRect:(CGRect )touchedRect {
//Create a shapeLayer with desired strokeColor, lineWidth and path(A UIBezierPath)
//This is the layer your are going to animate
//Add this shapeLayer to your self.shapeLayer_main added to the customView's layer
CAShapeLayer * shapeLayer = [CAShapeLayer layer];
shapeLayer.lineWidth = 4;
shapeLayer.strokeColor = strokeColor.CGColor;
shapeLayer.fillColor = transparentColor.CGColor;
shapeLayer.path = [[UIBezierPath bezierPathWithRect:touchedRect]CGPath];
[self.shapeLayer_main addSublayer:shapeLayer];
//Adding Animation to the ShapeLayer
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = 1.0;
drawAnimation.removedOnCompletion = NO;
drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
drawAnimation.toValue = [NSNumber numberWithFloat:1.0f];
[shapeLayer addAnimation:drawAnimation forKey:#"StrokeAnimation"];
}
Every time you tap the view, a rect is drawn with the stroke color you gave with the animation.

How to call touchesBegan on a layer that is a sublayer of a UIWebView

I have a layer that I have added as a sublayer of a UIWebView. I would like to be able to touch and drag the layer around. Before I added the UIWebView, I was able to touch and drag around the layer without a problem.
Here's where I create the layer and add it as a sublayer
- (void)viewDidLoad
{
[super viewDidLoad];
starLayer = [CALayer layer];
starLayer.backgroundColor = [[UIColor alloc] initWithPatternImage:[UIImage
imageNamed:#"star"]].CGColor;
starLayer.shadowOffset = CGSizeMake(0, 3);
starLayer.shadowRadius = 5.0;
starLayer.shadowColor = [UIColor blackColor].CGColor;
starLayer.shadowOpacity = 0.8;
starLayer.frame = CGRectMake(235.0, 200.0, 35.0, 35.0);
[self.aboutWebView.layer addSublayer:starLayer];
[self moveStar];
}
Here's my moveStar method
- (void)moveStar
{
CAKeyframeAnimation *move = [CAKeyframeAnimation animationWithKeyPath:#"position"];
NSMutableArray *values = [NSMutableArray array];
[values addObject:[NSValue valueWithCGPoint:CGPointMake(250.0, 50.0)]];
[values addObject:[NSValue valueWithCGPoint:CGPointMake(250.0, 250.0)]];
[move setValues:values];
[move setDuration:1.0];
[move setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]];
// Add the animation to the layer to be animated
[starLayer addAnimation:move forKey:#"moveAnimation"];
}
So far so good. The star falls onto the screen and lands a bit above the middle. But when I try and touch the star nothing happens. touchesBegan never gets called for some reason.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self.aboutWebView];
[starLayer setPosition:point];
}
Is the reason I can't touch the star when it's a sublayer of aboutWebView because a touch is reported to a view, not a layer?
Does anyone have any suggestions on how to make the star as a sublayer of aboutWebView touchable? Is it possible?
Thank you!
In your layer... try to set the UIGestureRecognizer property cancelsTouchesInView to NO.
"Setting this value to NO instructs the recognizer to deliver all touches to the underlying view, even when it has recognized the sequence"
something like that:
UIGestureRecognizer *touches = [UIGestureRecognizer alloc];
//or
//UIGestureRecognizer *touches = [[UIGestureRecognizer alloc] initWithTarget:self action:#selector(touchesAction:)];
touches.enabled=YES;
[yourLayer addGestureRecognizer:touches];
touches.cancelsTouchesInView=NO;

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 save a UIBezierPath to an existing UIImageView?

I can't figure out how i can save my drawn path to an UIImage?
I know that I have to use UIGraphicsBeginImageContext and something like [drawImage drawInRect:CGRectMake(0, 0, drawImage.size.width, drawImage.size.height)];
But I don't know where to put it in my touch functions.
I need to save every path to the image, because I want to be able to change the color and alpha values after each drawn line.
With this code, all the lines will be adjusted in color and width at the same time when I change the values.
- (id) initWithFrame:(CGRect)frame andImage: (UIImageView*) image
{
...
paths = [[NSMutableArray alloc]init];
drawImage = image;
self.backgroundColor=[UIColor clearColor];
[self addSubview:drawImage];
}
- (void)drawRect:(CGRect)rect
{
[brushPattern setStroke];
[myPath strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
// Drawing code
//[myPath stroke];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
myPath=[[UIBezierPath alloc]init];
[myPath moveToPoint:[mytouch locationInView:self]];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
[myPath addLineToPoint:[mytouch locationInView:self]];
[self setNeedsDisplay];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[paths addObject:myPath];
[myPath release];
}
Based on your earlier question, I am assuming that the image view is a subview of this custom view object. You will need to maintain a mutable array of paths in this class. Initialize it at touchesBegan:withEvent: and close the path at touchesEnded:withEvent:. Push the path into the array. At every touch event (began, moved, ended), call a method that updates the image view's image after adding the touch point to the path.
Updating the image will involve creating an empty image context and then iterating over all paths in your array and the current path while stroking them. Once you're done drawing, generate the image object from the context and set it to the image. Pretty straightforward until we notice that each path might differ in attributes. To deal with this you should create a container class for the path which will hold the attributes about the path. Push an object of this class for every path into the array rather than the path itself. This way you can maintain state for your view.
There is one way out and very simple infact , each time you draw a path object you can save it in an increamental image , just create it at a place where needed and then show this image in draw rect before drawing something else.
here is the sample code:
- (void)drawBitmap
{
UIGraphicsBeginImageContextWithOptions(self.bounds.size, YES, 0.0);
[strokeColor setStroke];
if (!incrementalImage) // first time draw; paint background white by ...
{
UIBezierPath *rectpath = [UIBezierPath bezierPathWithRect:self.bounds]; // enclosing bitmap by a rectangle defined by another UIBezierPath object
[[UIColor colorWithPatternImage:[UIImage imageNamed:#"two.jpg"]] setFill];
//[[UIColor whiteColor] setFill];
[rectpath fill]; // filling it with white
NSLog(#"========== ... drawBitmap .. CALLED=======");
}
[incrementalImage drawAtPoint:CGPointZero];
for(NSDictionary *_pathDict in pathArray)
{
[((UIColor *)[_pathDict valueForKey:#"color"]) setStroke]; // this method will choose the color from the receiver color object (in this case this object is :strokeColor)
[[_pathDict valueForKey:#"path"] strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
}
incrementalImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
After that just call this increamental image in drawRect: , so that user has a illusion of continuous drawing and you can then save this image point of time:
- (void)drawRect:(CGRect)rect
{
[incrementalImage drawInRect:rect];
[[dict_path objectForKey:#"color"] setStroke]; // this method will choose the color from the receiver color object (in this case this object is :strokeColor)
[[dict_path objectForKey:#"path"] strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
}
May be you need to modify some code according to your own need.Hope this help someone