I am trying to draw multiple UIBezierPaths on a custom view, with the ability to manipulate them individually.
path and the NSMutableArray to store the paths are instance variables declared like:
#interface MyCustomView : UIView {
UIBezierPath *path;
NSMutableArray *paths; // this is initialized in the init method
}
The path is initialized in touchesBegan as follows:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
path = [[UIBezierPath alloc] init];
[path moveToPoint:[touch locationInView:self];
}
It is moved in the touchesMoved method as follows:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[path addLineToPath:[touch locationInView:self]];
[self setsNeedsDisplay];
}
And I want to store it in the NSMutableArray in touchesEnded:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[path closePath];
[paths addObject:path];
[self setNeedsDisplay];
}
The problem is, after I draw one uibezierpath, and begin to draw one after that, the one i drew first disappears. I'm not sure why this is happening. Thanks in advance!
Note: I know a possible solution is storing all the points of each uibezierpath in a NSMutableArray and redrawing it everytime drawRect is called, but I feel like that is an inefficient implementation.
This is happening because you are using a global instance path . Instead of using a global instance add your path object to mutable array and get wherever you want.
Try replacing your code something like this.
#interface MyCustomView : UIView {
NSMutableArray *paths; // this is initialized in the init method
}
The path is initialized in touchesBegan as follows:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UIBezierPath *path = [[UIBezierPath alloc] init];
[path moveToPoint:[touch locationInView:self];
[paths addObject:path];
}
It is moved in the touchesMoved method as follows:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UIBezierPath *path = [paths lastObject];
[path addLineToPath:[touch locationInView:self]];
[self setsNeedsDisplay];
}
And I want to store it in the NSMutableArray in touchesEnded:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UIBezierPath *path = [paths lastObject];
[path closePath];
[self setNeedsDisplay];
}
You didn't show you drawRect: method, but be aware that in your drawRect:, you need to draw all paths that you want displayed. Every time drawRect: is entered, everything you were drawing before is cleared and has to be drawn again, so just drawing the new one will just give the new one and nothing else.
Related
I am trying to draw lines on a UIView with finger and it worked fine with one color. If I try to change the color and draw again, the previous UIBezierPath color is also changed to new color. So, I am unable to draw a different color line keeping the previous color line on the UIView
I set all the properties(path,linecolor) as nonatomic and strong in my UIView
For reference:
my first draw:
my second draw:
my third draw:
After I choose the color, I change the stroke color of my UIView in color picker delegate method:
#pragma mark - FCColorPickerViewControllerDelegate Methods
-(void)colorPickerViewController:(FCColorPickerViewController *)colorPicker didSelectColor:(UIColor *)color {
self.drawView.lineColor = color; //this works fine
// self.drawView.path=[UIBezierPath bezierPath]; tried this to create new bezier path with new color, but this erases the olde bezier path and return new
// [self.drawView.lineColor setStroke]; tried this
// [self.drawView.lineColor setFill]; tried this
[self dismissViewControllerAnimated:YES completion:nil]; //dismiss the color picker
}
Here are my view methods for drawing:
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder])
{
[self setBackgroundColor:[UIColor whiteColor]];
path = [UIBezierPath bezierPath];
[path setLineWidth:self.lineWidth];
}
return self;
}
- (void)drawRect:(CGRect)frame
{
[self.lineColor setStroke];
[path stroke];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
dispatch_async(dispatch_get_main_queue(), ^{
UITouch *touch = [touches anyObject];
CGPoint p = [touch locationInView:self];
[path moveToPoint:p];
});
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
dispatch_async(dispatch_get_main_queue(), ^{
UITouch *touch = [touches anyObject];
CGPoint p = [touch locationInView:self];
[path addLineToPoint:p];
[self setNeedsDisplay];
});
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesMoved:touches withEvent:event];
}
I tried this: adding the old bezier paths to the array and redraw them, but it didnt work , this time I cant make another bezier path with new color.:
- (void)drawRect:(CGRect)frame // (5)
{
//load the path from array
for (int i = 0; i < [pathArray count]; i++){
NSLog(#"Path: %#",[pathArray objectAtIndex:0]);
NSLog(#"Color: %#",[pathArray objectAtIndex:1]);
UIBezierPath *oldpath = [pathArray objectAtIndex:0];
//color
[[pathArray objectAtIndex:1] setStroke];
//path
[oldpath stroke];
}
UIBezierPath *newPath = [self pathForCurrentLine];
if (newPath)
{
// set the width, color, etc, too, if you want
[lineColor setStroke];
[newPath stroke];
}
}
- (UIBezierPath*)pathForCurrentLine {
if (CGPointEqualToPoint(startPoint, CGPointZero) && CGPointEqualToPoint(endPoint, CGPointZero)){
return nil;
}
UIBezierPath *newpath = [UIBezierPath bezierPath];
[newpath moveToPoint:startPoint];
[newpath addLineToPoint:endPoint];
return newpath;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
dispatch_async(dispatch_get_main_queue(), ^{
UITouch *touch = [touches anyObject];
CGPoint p = [touch locationInView:self];
[path moveToPoint:p];
startPoint=p;
});
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
dispatch_async(dispatch_get_main_queue(), ^{
UITouch *touch = [touches anyObject];
CGPoint p = [touch locationInView:self];
[path addLineToPoint:p]; // (4)
endPoint=p;
[self setNeedsDisplay];
});
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesMoved:touches withEvent:event];
[pathArray addObject:path];
[pathArray addObject:lineColor];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesEnded:touches withEvent:event];
}
You actually can't stroke different parts of the same path in different colors.
You can think of the drawing context as a state machine. Each time you issue a draw command (such as calling stroke on a UIBezierPath) it will check the current stroke/fill color and use that to perform the draw. To create multiple strokes in different colors you would need to have multiple paths to stroke, and set the stroke color in between each invocation of stroke.
- (void)drawRect:(CGRect)frame {
UIBezierPath* pathOne = // create path one
UIBezierPath* pathTwo = // create path two
UIBezierPath* pathThree = // create path three
[[UIColor redColor] setStroke];
[pathOne stroke];
[[UIColor greenColor] setStroke];
[pathTwo stroke];
[[UIColor blueColor] setStroke];
[pathThree stroke];
}
I am working on a photo editing application and need the user to be able to draw a path to crop out the photo. I have a class that works with a UIView for drawing smooth UIBezierPath lines. However I really need to apply this to a UIImageView so that the drawing can be done over the image. If I change the class to subclass UIImageView instead it no longer works. Any ideas on what I can do to fix this? Or better options for achieving the same goal? Below is my implementation:
#import "DrawView.h"
#implementation DrawView
{
UIBezierPath *path;
}
- (id)initWithCoder:(NSCoder *)aDecoder // (1)
{
if (self = [super initWithCoder:aDecoder])
{
[self setMultipleTouchEnabled:NO]; // (2)
[self setBackgroundColor:[UIColor whiteColor]];
path = [UIBezierPath bezierPath];
[path setLineWidth:2.0];
}
return self;
}
- (void)drawRect:(CGRect)rect // (5)
{
[[UIColor blackColor] setStroke];
[path stroke];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint p = [touch locationInView:self];
[path moveToPoint:p];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint p = [touch locationInView:self];
[path addLineToPoint:p]; // (4)
[self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesMoved:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesEnded:touches withEvent:event];
}
#end
If I place a break point on touchesBegan or touchesMoved it does fire when expected.
First problem - userInteractionEnabled property should be set to YES for receiving touch events.
Second one - drawRect: method is not called for UIImageView subclasses:
The UIImageView class is optimized to draw its images to the display.
UIImageView will not call drawRect: a subclass. If your subclass needs
custom drawing code, it is recommended you use UIView as the base
class.
So, DrawView should be UIView subclass, that will draw UIImage in drawRect:, before bezier path drawing. Something like that (only changed parts of code):
// DrawView.h
#interface DrawView : UIView
#property (nonatomic, strong) UIImage* image;
#end
// DrawView.m
#implementation DrawView
- (void)drawRect:(CGRect)rect
{
[image drawInRect:self.bounds];
[[UIColor blackColor] setStroke];
[path stroke];
}
#end
my app has a ViewController which contains inside a UIView (CanvasView) where you can draw your signature (I added a label "Hi Little John" to differentiate it), and I also added a UIImage below it for making a capture by touching a camera button
Now when I touch the camera button it only captures the UIView and UILabel but not the signature as it shows
I have a two classes: my UIView class (CanvasView) and my UIViewController, in my CanvasView I have this code for the screenshot:
#implementation CanvasView
-(UIImage *)getCanvasScreenshot{
//first it makes an UIImage from the view
UIGraphicsBeginImageContext(self.bounds.size);
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *sourceImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//now it will position the image, X/Y away from top left corner to get the portion required
UIGraphicsBeginImageContext(self.frame.size);
[sourceImage drawAtPoint:CGPointMake(0, 0)];
UIImage *croppedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return croppedImage;
}
//some other code goes here...
#end
in my ViewController's class I have a my IBAction which triggers the event
- (IBAction)captureSignature:(id)sender {
self.imageFirma.image = [canvasView getCanvasScreenshot];
}
I want to capture the signature on my picture
any help I'll appreciate
thanks in advance
***** EDIT** the code for rendering the signature is in CanvasView as well, it's three methods
(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
depending of the event if the touch is:
at the begining (touchesBegan)
if is it moving (touchesMoved)
or if is finished (touchesEnded)
for rendering the stroke all of them call to a method named:
(void) addLineToAndRenderStroke:(SmoothStroke*)currentStroke toPoint:(CGPoint)end toWidth:(CGFloat)width toColor:(UIColor*)color
I can go deeper if you want but it's kinda long because it is a framework for making strokes on the canvas, and the code of all these methods are:
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
if(![JotStylusManager sharedInstance].enabled){
for (UITouch *touch in touches) {
[self addLineToAndRenderStroke:[self getStrokeForTouchHash:touch.hash]
toPoint:[touch locationInView:self]
toWidth:[self widthForPressure:JOT_MIN_PRESSURE]
toColor:[self colorForPressure:JOT_MIN_PRESSURE]];
}
}
}
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
if(![JotStylusManager sharedInstance].enabled){
for (UITouch *touch in touches) {
// check for other brands of stylus,
// or process non-Jot touches
//
// for this example, we'll simply draw every touch if
// the jot sdk is not enabled
[self addLineToAndRenderStroke:[self getStrokeForTouchHash:touch.hash]
toPoint:[touch locationInView:self]
toWidth:[self widthForPressure:JOT_MIN_PRESSURE]
toColor:[self colorForPressure:JOT_MIN_PRESSURE]];
}
}
}
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
if(![JotStylusManager sharedInstance].enabled){
for(UITouch* touch in touches){
SmoothStroke* currentStroke = [self getStrokeForTouchHash:touch.hash];
// now line to the end of the stroke
[self addLineToAndRenderStroke:currentStroke
toPoint:[touch locationInView:self]
toWidth:[self widthForPressure:JOT_MIN_PRESSURE]
toColor:[self colorForPressure:JOT_MIN_PRESSURE]];
// this stroke is now finished, so add it to our completed strokes stack
// and remove it from the current strokes, and reset our undo state if any
[_stackOfStrokes addObject:currentStroke];
[currentStrokes removeObjectForKey:#([touch hash])];
[stackOfUndoneStrokes removeAllObjects];
}
}
}
- (void) addLineToAndRenderStroke:(SmoothStroke*)currentStroke toPoint:(CGPoint)end toWidth:(CGFloat)width toColor:(UIColor*)color{
// fetch the current and previous elements
// of the stroke. these will help us
// step over their length for drawing
AbstractBezierPathElement* previousElement = [currentStroke.segments lastObject];
// Convert touch point from UIView referential to OpenGL one (upside-down flip)
end.y = self.bounds.size.height - end.y;
if(![currentStroke addPoint:end withWidth:width andColor:color]) return;
//
// ok, now we have the current + previous stroke segment
// so let's set to drawing it!
[self renderElement:[currentStroke.segments lastObject] fromPreviousElement:previousElement includeOpenGLPrep:YES];
}
I have some troubles with drawing app in IOS. I have created the free hand drawing with the help of some tutorials. But I found some difficulties in erasing the drawing. In my app, I have button with eraser as background image. After I clicked the eraser button, when I swipes over the drawing, it will erase the drawing wherever I swipes. Can anyone help me to do this.
Thanks in advance.
Given below is my code:
#implementation LinearInterpView
{
UIBezierPath *path;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder {
if(self = [super initWithCoder:aDecoder]) {
[self setMultipleTouchEnabled:YES];
[self setBackgroundColor:[UIColor whiteColor]];
path=[UIBezierPath bezierPath];
[path setLineWidth:2.0];
}
return self;
}
-(void)drawRect:(CGRect)rect{
[[UIColor blackColor] setStroke];
[path stroke];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch=[touches anyObject];
CGPoint p=[touch locationInView:self];
[path moveToPoint:p];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch=[touches anyObject];
CGPoint p=[touch locationInView:self];
[path addLineToPoint:p];
[self setNeedsDisplay];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesMoved:touches withEvent:event];
}
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event{
[self touchesEnded:touches withEvent:event];
}
// This is the button action to erase the drawing.
- (IBAction)erase:(id)sender {
CGContextRef cgref=UIGraphicsGetCurrentContext();
CGContextSetBlendMode(cgref, kCGBlendModeClear);
}
Kindly clear me, what mistake I did.
So by drawing you mean you have drew lines on the screen say with some color you can do the same by setting white color and alpha 1 so that white lines replace the existing colored lines. A better tutorial here . This also seemed important.
First of all your logic should be make a layer on ImageView.
then you can draw on that layer then pass white color to erase.
It'll look like erase and your view will look like according to requirement.
That will surly work.
Try this to erase drawing in iOS:
- (IBAction)eraserPressed:(id)sender {
red = 255.0/255.0;
green = 255.0/255.0;
blue = 255.0/255.0;
opacity = 1.0;
}
why not you implement the same logic in the erasor button as you did in the draw button. just make the default color of the stroke in the eraser as white color or what ever color your background is.
I wrote a simple drawing code using UIBezierPath and the -touches... methods of UIView. The drawing works great, but I made some bad experiences.
When I'm painting very slow, it works great. But the more fast I draw, the more edgy the line gets. So how do I like "smooth" them to become less edgy (more points)?
When I use setLineWidth with high line widths, the line become very ugly.
Here is an image to show what ugly actually means:
Why does it draw fat line in that way?!
EDIT: here some code
- (void)drawInRect:(CGRect)rect
{
for(UIBezierPath *path in pathArray) {
[[UIColor blackColor] setStroke];
[path setLineWidth:50.0];
[path stroke];
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
startPoint = [[touches anyObject] locationInView:self];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:startPoint];
[path addLineToPoint:[[touches anyObject] locationInView:self]];
if(isDrawing) {
[pathArray removeLastObject];
}
else {
isDrawing = YES;
}
[pathArray addObject:path];
[path close];
[self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
isDrawing = NO;
}
I hope that somebody can help me with these issues. THANKS a lot, greets, Julian
Mmmmh, I'm not going to address performance issues with your implementation in this thread, but take a look at setNeedsDisplayInRect: in UIView once you get the basics working.
I think you're basically trying to take out the last created path from your array and replace it with a new one for as long as you're moving.
You should try to put a CGMutablePathRef in your array instead (take a look here for that CGMutablePathRef to NSMutableArray).
The basic flow would be something like:
touchesBegan:
Create the CGMutablePathRef
Call moveToPoint
Store in the array
touchesMoved:
Get the lastObject from your array (i.e. the CGMutablePathRef)
Call addLineToPoint
[self setNeedsDisplay]
Then in drawInRect, iterate through your paths and paint them.
Again, this will be slow at first, then you need to optimize. CGLayerRef can help. setNeedsDisplayInRect most definitely will also.