Here is my code for free hand drawing. But when i draw the path, previous path is disappeared. I am not able to figure it out why is it happening so. Can any body help me. Here is my code.
- (void)drawRect:(CGRect)rect
{
for (NSMutableDictionary *dictionary in pathArray) {
UIBezierPath *_path = [dict objectForKey:#"Path"];
UIColor *_colors = [dict objectForKey:#"Colors"];
[_colors setStroke];
_path.lineCapStyle = kCGLineCapRound;
[_path stroke];
}
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
isEdited=YES;
myPath=[[UIBezierPath alloc]init];
myPath.lineWidth=lineWidths;
CGPoint touchPoint = [[touches anyObject] locationInView:self];
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
[myPath moveToPoint:[mytouch locationInView:self]];
[myPath addLineToPoint:CGPointMake(touchPoint.x+1, touchPoint.y+1)];
[dict setObject:myPath forKey:#"Path"];
[dict setObject:brushPattern forKey:#"Colors"];
[pathArray addObject:dict];
[self setNeedsDisplay];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
[myPath addLineToPoint:[mytouch locationInView:self]];
[self setNeedsDisplay];
}
You should create myPath and dict localy in touchesBegan: each time it's fired. Ditch their class-wide definitions.
For simpler (faster) performance you can still have class-wide currentPath and currentDict
ivars for usage in touchesMoved:
EDIT: code would look something like this:
//currentPath declared as an iVar of UIBezierPath* type
//currentDict declared as an iVar of NSMutableDictionary* type
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
isEdited=YES;
UIBezierPath *myPath=[[UIBezierPath alloc]init]; //locally created
myPath.lineWidth=lineWidths;
CGPoint touchPoint = [[touches anyObject] locationInView:self];
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
[myPath moveToPoint:[mytouch locationInView:self]];
[myPath addLineToPoint:CGPointMake(touchPoint.x+1, touchPoint.y+1)];
NSMutableDictionary *dict=[[NSMutableDictionary alloc]init]; //locally created
[dict setObject:myPath forKey:#"Path"];
[dict setObject:brushPattern forKey:#"Colors"];
[pathArray addObject:dict];
[self setNeedsDisplay];
currentPath = myPath;
currentDict = dict;
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
[currentPath addLineToPoint:[mytouch locationInView:self]];
[self setNeedsDisplay];
}
1) Create a UIImage *currentImage instance variable in your drawing view.
2) in drawRect method of your view put the following line:
[currentImage drawInRect:self.bounds];
3) In touchesEnded method put the following code:
CGRect rect = self.bounds;
UIGraphicsBeginImageContext(rect.size);
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *tempImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
currentImage = tempImage;
Related
This question already has answers here:
UIBezierPath Multiple Line Colors
(2 answers)
Closed 9 years ago.
I am working on project in which i am using UIBezierPath for drawing. My problem is that when i change the color of the path, whole UIBezierPath color changes. I want to ask is it possible to change the color of UIBezierPath with multiple lines.
Regards
For one single UIBezier path you can't AFAIK
You can do something like this
///// Add this in drawRect
for (NSMutableDictionary *dic in pathArray) {
UIBezierPath *_path = [dic valueForKey:#"path"];
[[dic valueForKey:#"fColor"] setFill];
[[dic valueForKey:#"sColor"] setStroke];
[_path stroke];
}
Populating array in touch events
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.currentPath=[[UIBezierPath alloc]init];
self.currentPath.lineWidth=5;
self.currentPath.miterLimit=-10;
self.currentPath.lineCapStyle = kCGLineCapRound;
self.currentPath.flatness = 0.0;
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
[self.currentPath moveToPoint:[mytouch locationInView:self]];
NSMutableDictionary *dic = [[NSMutableDictionary alloc] init];
[dic setObject:currentfColor forKey:#"fColor"];
[dic setObject:currentsColor forKey:#"sColor"];
[dic setObject:self.currentPath forKey:#"path"];
[pathArray addObject:dic];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
[self.currentPath addLineToPoint:[touch locationInView:self.view]];
[self.view setNeedsDisplay];
}
PS: This is just an example. I havent checked this code, yet it should work may be some alteration needed.
Please see the attached image. I have created an app in which user can draw with multiple images but when I am using the Clear Color in order to erase the drawing it is showing the black border around the erased path. I have used UIBezierPath for drawing. Is this an issue with UIBezierPath or it can be resolved by any other way? Please let me know if you want code snippet! I will post that here.
Edit: Code has been added! Please ignore the irrelevant code.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if(isFirstTapp)
{
isFirstTapp=NO;
[self.spooleteView removeHandView];
}
isdraw=YES;
mouseSwapped=NO;
UITouch *touch = [touches anyObject];
lastPoint=[touch locationInView:self];
//UITouch *touch = [touches anyObject];
CGPoint p = [touch locationInView:self];
[currentPath moveToPoint:p];
currentPath.flatness = 10.0;
NSMutableDictionary *dict=[[NSMutableDictionary alloc]init];
UIBezierPath *myPath=[[UIBezierPath alloc]init];
myPath.lineCapStyle=kCGLineJoinRound;
[dict setObject:myPath forKey:#"Path"];
colorTag= [[AppHelper userDefaultsForKey:#"ColorTag"]intValue];
self.selectedColor = [brushColor objectAtIndex:colorTag];
if(isErase)
{
myPath.lineWidth=30.0;
self.selectedColor = [UIColor whiteColor];
}
else
{
if([mode isEqualToString:#"morsecode"])
myPath.lineWidth=10.0;
else
myPath.lineWidth=5.0;
}
//[dict setObject:[brushColor objectAtIndex:colorTag] forKey:#"Colors"];
[dict setObject:self.selectedColor forKey:#"Colors"];
[myArr addObject:dict];
currentPath=myPath;
currentPath.flatness = 0.1;
currentPath.lineJoinStyle = kCGLineJoinRound;
currentDict=dict;
[myPath moveToPoint:lastPoint];
[myPath release];
[dict release];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
if(!istapped)
{
if([mode isEqualToString:#"morsecode"])
{
mouseSwapped=YES;
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
currentPoint=[mytouch locationInView:self];
[currentPath addLineToPoint:currentPoint];
[self setNeedsDisplay];
}
}
// UITouch *touch = [touches anyObject];
// CGPoint p = [touch locationInView:self];
// [currentPath addLineToPoint:p];
// [self setNeedsDisplay];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if(!istapped)
{
mouseSwapped=YES;
UITouch *objTouch=[[touches allObjects] objectAtIndex:0];
if (!mouseSwapped&&(objTouch.tapCount==1))
{
lastPoint=currentPoint;
}
}
if(isErase)
{
if ([myArr count]>50)
{
[myArr removeAllObjects];
}
}
UITouch *touch = [touches anyObject];
CGPoint p = [touch locationInView:self];
[currentPath addLineToPoint:p];
[self drawBitmap]; // (3)
[self setNeedsDisplay];
[currentPath removeAllPoints]; //(4)
}
- (void)drawRect:(CGRect)rect
{
[self.incrementalImage drawInRect:rect]; // (3)
[currentPath stroke];
//[self.incrementalImage drawInRect:rect];
for (NSMutableDictionary *dictionary in myArr)
{
UIBezierPath *_path = [dictionary objectForKey:#"Path"];
self.selectedColor = [dictionary objectForKey:#"Colors"];
[self.selectedColor setStroke];
[_path stroke];
[_path strokeWithBlendMode:kCGBlendModeCopy alpha:1.0];
_path.lineCapStyle = kCGLineJoinRound;
if([mode isEqualToString:#"morsecode"])
{
if (!isErase)
{
const float p[2] = {1,15.9};
[_path setLineDash:p count:2 phase:0.9];
}
else
{
self.selectedColor = [UIColor whiteColor];
}
}
}
}
- (void)drawBitmap // (3)
{
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0);
[self.selectedColor setStroke];
if (!self.incrementalImage) // first draw; paint background white by ...
{
UIBezierPath *rectpath = [UIBezierPath bezierPathWithRect:self.bounds]; // enclosing bitmap by a rectangle defined by another UIBezierPath object
[[UIColor clearColor] setFill];
[rectpath fill]; // filling it with white
}
[self.incrementalImage drawAtPoint:CGPointZero];
[currentPath stroke];
self.incrementalImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
I have found the reason of that issue. It was because of the second line of the following function.
- (void)drawRect:(CGRect)rect
{
[self.incrementalImage drawInRect:rect]; // (3)
[currentPath stroke];
i.e., [currentPath stroke];
I have commented that and now there is no black border around the path.
I hope this will help other.
i want to smooth curve on finger touch draw line and i want solution in UIBezierPath only my code not smoothing line completely.my code here's
#implementation MyLineDrawingView
#synthesize undoSteps;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
[super setBackgroundColor:[UIColor whiteColor]];
pathArray=[[NSMutableArray alloc]init];
bufferArray=[[NSMutableArray alloc]init];
}
return self;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
[[UIColor blackColor] setStroke];
for (UIBezierPath *_path in pathArray)
[_path strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
}
#pragma mark - Touch Methods
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[bufferArray removeAllObjects];
myPath=[[UIBezierPath alloc]init];
myPath.lineWidth=5;
myPath.miterLimit=-10;
myPath.lineCapStyle = kCGLineCapRound;
myPath.flatness = 0.0;
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
[myPath moveToPoint:[mytouch locationInView:self]];
[pathArray addObject:myPath];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
[myPath addLineToPoint:[mytouch locationInView:self]];
[self setNeedsDisplay];
}
-(void)undoButtonClicked
{
if([pathArray count]>0)
{
UIBezierPath *_path=[pathArray lastObject];
[bufferArray addObject:_path];
[pathArray removeLastObject];
[self setNeedsDisplay];
}
}
-(void)redoButtonClicked
{
if([bufferArray count]>0){
UIBezierPath *_path=[bufferArray lastObject];
[pathArray addObject:_path];
[bufferArray removeLastObject];
[self setNeedsDisplay];
}
}
- (void)dealloc
{
[pathArray release];
[bufferArray release];
[super dealloc];
}
#end
and i am getting output using my code like below screen shot:
i want smooth curve output like below screen shot:
can any one help me greatly appreciated!
Thanks in Advance!
The problem is that you're only adding points to the path using addLineToPoint:. That's the equivalent of creating a series of straight lines. You really need to add control points, like the handles you might drag while drawing curved paths in Illustrator or Sketch.
You can use addCurveToPoint:controlPoint1:controlPoint2: to add those; the trick is working out where to put the control points, relative to the points you actually get back from the touches* events.
For a good discussion of possible techniques, I recommend the following question: Drawing Smooth Curves - Methods Needed.
I am using following code to color the image using UIBezierPath. There is color picker from which user can select color. And with that color bezier path will be drawn over image. But when user fills color at some position in image.after that it selects color from color picker and again fills the color at same position. In this case previously drawn color is there. I want to remove that previously filled color and want to fill new color. This is only if user fills the color at same position. Here I am already getting the points where UIBezierPath is already drawn. There is one method of UIBezierPath removeAllPoints. But it's removing all points from path. I only want to remove only touched point from uibezierpath. How can I achieve this?
#import "MyLineDrawingView.h"
#implementation MyLineDrawingView
#synthesize selImage;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
arrBezierPaths=[[NSMutableArray alloc]init ];
globalPath=[[UIBezierPath alloc]init];
myPath=[[UIBezierPath alloc]init];
self.userInteractionEnabled = TRUE;
myPath.lineWidth=30;
brushPattern=[UIColor redColor];
NSLog(#"initWithFrame method called");
NSDictionary *dict=[NSDictionary dictionaryWithObjectsAndKeys:myPath,#"Path",brushPattern,#"Color", nil];
[arrBezierPaths addObject:dict];
}
return self;
}
- (void)drawRect:(CGRect)rect
{
for (int i=0; i<[arrBezierPaths count]; i++)
{
NSDictionary *dict=[arrBezierPaths objectAtIndex:i];
UIColor *tempBrushpatter=[[UIColor alloc]init ];
tempBrushpatter=[dict valueForKey:#"Color"];
globalPath=[dict valueForKey:#"Path"];
[globalPath strokeWithBlendMode:kCGBlendModeOverlay alpha:1.0];
[tempBrushpatter setStroke];
}
}
#pragma mark - Touch Methods
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
[globalPath moveToPoint:[mytouch locationInView:self]];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
CGPoint pointTouched = [mytouch locationInView:self];
for(int i=0;i<[arrBezierPaths count];i++)
{
NSDictionary *dictTemp=[arrBezierPaths objectAtIndex:0];
UIBezierPath *temppath=[dictTemp valueForKey:#"Path"];
if([temppath containsPoint:pointTouched])
{
// [tempPath removeAllPoints];
NSLog(#"This point already contain some path");
}
}
[globalPath addLineToPoint:[mytouch locationInView:self]];
[self setNeedsDisplay];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
}
-(void)changeColor:(UIColor *)color
{
UIBezierPath *temp=[[UIBezierPath alloc]init];
// self.userInteractionEnabled = TRUE;
temp.lineWidth=30;
UIColor *brushColor=color;
NSDictionary *dict=[NSDictionary dictionaryWithObjectsAndKeys:temp,#"Path",brushColor,#"Color", nil];
[temp release];
[arrBezierPaths addObject:dict];
brushPattern=[color retain];
[self setNeedsDisplay];
}
Need some more details. From my assumption you are drawing a line with desired color, But the next time you just wanted to replace the touch point with another color and not the whole line is it?
I'm trying to implement a very simple drawing view in my app. This is only a small part of my app but it's turning into a real hassle. This is what I have so far, but all it's displaying right now is morse code like dots and lines.
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *docsPath = [paths objectAtIndex:0];
NSString *savePath = [NSString stringWithFormat:#"%#/notePadImage.jpg",docsPath];
NSData *data = [NSData dataWithContentsOfFile:savePath];
UIImage *image = [UIImage imageWithData:data];
if (image == nil) {
NSString *pathToBlank = [[NSBundle mainBundle]pathForResource:#"blankNotePadPage" ofType:#"png"];
NSData *data = [NSData dataWithContentsOfFile:pathToBlank];
image = [UIImage imageWithData:data];
}
arrayOfTouches = [[NSMutableArray alloc] initWithCapacity:10];
self.drawImage.image = image;
mouseMoved = 0;
[self.view bringSubviewToFront:closeButton];
[self.view bringSubviewToFront:clearButton];
self.timer = [NSTimer scheduledTimerWithTimeInterval:.02 target:self selector:#selector(drawIt) userInfo:nil repeats:YES];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
[arrayOfTouches addObject:touch];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
[arrayOfTouches addObject:touch];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
}
-(void) drawIt {
NSMutableArray *tempArray = [NSMutableArray arrayWithArray:arrayOfTouches];
[arrayOfTouches removeAllObjects];
if ([tempArray count]>1) {
[arrayOfTouches removeAllObjects];
CGPoint point1 = [[tempArray objectAtIndex:0] previousLocationInView:self.view];;
CGPoint point2;
CGPoint point3;
for (int i = 0; i < [tempArray count]-1;i = i+1) {
UIGraphicsBeginImageContext(self.view.frame.size);
[drawImage.image drawInRect:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 3.0);
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 0.0, 0.0, 0.0, 1.0);
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), point1.x, point1.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), point2.x, point2.y);
CGContextStrokePath(UIGraphicsGetCurrentContext());
CGContextFlush(UIGraphicsGetCurrentContext());
drawImage.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.view bringSubviewToFront:closeButton];
[self.view bringSubviewToFront:clearButton];
point1 = point2;
}
}
}
One of my apps also needed some simple drawing. Here is a slightly modified version of it. It works basically like hotpaw2 describes. I created a "canvas" view that handles all the drawing and I just add it wherever it's needed.
The speed is fine for my purposes.
CanvasView.h:
#interface CanvasView : UIView {
NSMutableArray *points;
}
#property (nonatomic, retain) NSMutableArray *points;
#end
CanvasView.m:
#import "CanvasView.h"
#implementation CanvasView
#synthesize points;
- (id) initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor blueColor];
}
return self;
}
-(void)drawRect:(CGRect)rect
{
if (self.points.count == 0)
return;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0); //white
CGContextSetLineWidth(context, 1.0);
CGPoint firstPoint = [[self.points objectAtIndex:0] CGPointValue];
CGContextBeginPath(context);
CGContextMoveToPoint(context, firstPoint.x, firstPoint.y);
int i = 1;
while (i < self.points.count)
{
CGPoint nextPoint = [[self.points objectAtIndex:i] CGPointValue];
if (nextPoint.x < 0 && nextPoint.y < 0)
{
CGContextDrawPath(context, kCGPathStroke);
if (i < (self.points.count-1))
{
CGContextBeginPath(context);
CGPoint nextPoint2 = [[self.points objectAtIndex:i+1] CGPointValue];
CGContextMoveToPoint(context, nextPoint2.x, nextPoint2.y);
i = i + 2;
}
else
i++;
}
else
{
CGContextAddLineToPoint(context, nextPoint.x, nextPoint.y);
i++;
}
}
CGContextDrawPath(context, kCGPathStroke);
}
-(void)dealloc
{
[points release];
[super dealloc];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event touchesForView:self] anyObject];
CGPoint location = [touch locationInView:self];
if (self.points == nil)
{
NSMutableArray *newPoints = [[NSMutableArray alloc] init];
self.points = newPoints;
[newPoints release];
}
[self.points addObject:[NSValue valueWithCGPoint:(location)]];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event touchesForView:self] anyObject];
CGPoint location = [touch locationInView:self];
[self.points addObject:[NSValue valueWithCGPoint:(location)]];
[self setNeedsDisplay];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event touchesForView:self] anyObject];
CGPoint location = [touch locationInView:self];
[self.points addObject:[NSValue valueWithCGPoint:(location)]];
CGPoint endPoint = CGPointMake(-99, -99); //"end of path" indicator
[self.points addObject:[NSValue valueWithCGPoint:(endPoint)]];
[self setNeedsDisplay];
}
#end
Adding the canvasView where it's needed:
CanvasView *cv = [[CanvasView alloc] initWithFrame:CGRectMake(0, 0, 320, 640)];
[self.view addSubview:cv];
[cv release];
Don't draw while handling touches. It will slow down the touch handler so much you might get the connect-the-dots-effect you are seeing.
Save the touch coordinates in an array and plan to draw them later.
Look at some simple animation tutorials for how to draw in a drawRect based on a setNeedsDisplay called by an animation UITimer or CADisplayLink. Draw all your line segments there at a more suitable rate.