Wednesday, May 30, 2012

iOS Paint tool using UIBezierPath


I was going through how to use UIBezierPath and what are its possible uses.. and I must say it is one hell of a convenient tool! Ofcourse you can draw normal drawing with it as shown below and use your app as sketching pad in addition to that it adds so much convenience with the undo and redo action that I fell in love with the use of UIBezierPath. You can have simple drawing as well as pattern based brush tip. It is so easy to use this as pencil tool that I don't even have words !

You can initialize the UIBezierPath as following
UIBezierPath *myPath = [[UIBezierPath alloc] init];
myPath.lineWidth = 10;
brushPattern = [UIColor redColor]; // This is the color of my stroke
Then you have Touch methods which handle and track the coordinates of your touch. When your touch begins on the screen, you ask UIBezierPath to move to that touch point.
UITouch *myTouch = [[touches allObject] objectAtIndex:0];
[myPath moveToPoint:[myTouch locationInView:self]; 
As you move your finger around, you keep adding those points in your BezierPath in TouchMoved method by following
UITouch * mytouch = [[touches allObjects] objectAtIndex:0];
[myPath addLineToPoint:[mytouch locationInView:self]];
As we need constant refreshing of the screen, so that as soon as we draw it appears on the screen, we refresh the UIView subclass by calling following method in TouchMethod so that as soon as there any change in the BezierPath, it is reflected on the screen.
[self setNeedsDisplay];
Talking about drawRect Method which does all the drawing for you, you need to set the color of your stroke (stroke color means the color with which painting will be done on screen) on screen and also the blend mode. You can try different blend mode and see the result.
[brushPattern setStroke];
[_path strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];




Pattern Brush.. it just changes the custom pattern color of the paint brush and allows you to use a custom image-pattern to be used instead of any specific color (which was red in our case)

myPath.lineWidth = 10; //This can help you in setting width of the stroke/brush
brushPattern = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:@"pattern.jpg"]]; // You can set pattern in your color of your brush so that the stroke will appear with pattern of your image


Adding Undo/Redo feature

We don't have to do much after this step for adding the undo/redo feature in the existing code.
Add following lines to header (.h) file
{
  NSMutableArray *pathArray;
  NSMutableArray *bufferArray;
}
- (void)undoButtonClicked;
- (void)redoButtonClicked;

First, we declare two NSMutable arrays, one each for 'undo' and 'redo' steps. Then we declare two methods that gets called when we want to undo/redo.

Go to implementation file (.m) and lets start be looking into touch delegate methods:
 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    myPath = [[UIBezierPath alloc] init];
    myPath.lineWidth = 10;
    UITouch *mytouch = [[touches allObjects] objectAtIndex:0];
    [myPath moveToPoint:[mytouch locationInView:self]];
    [pathArray addObject:myPath]; 
}
Here, whenever a new touch is detected, we initialize a new UIBezierPath and add it to "pathArray" (for undo steps)
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *mytouch = [[touches allObjects] objectAtIndex:0];
    [myPath addLineToPoint:[mytouch locationInView:self];
    [self setNeedsDisplay];
}
Next, each time a movement is detected, then we call 'setNeedsDisplay' and redraws the view which in turn calls 'drawRect' method
- (void)drawRect:(CGRect)rect

{
    [brushPattern setStroke];
    for (UIBezierPath *_path in pathArray)
        [_path strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
}
Since, 'pathArray' contains all the UIBezierPaths, it loops through each of them and paints them on screen.
Below are the actions that gets called upon when we click on undo/redo buttons:
- (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];
    }
}

When undo button is clicked, we remove the last object from pathArray and save that object to 'bufferArray' - which stores all UIBezierPath for redo.
And when redo gets clicked, we remove the last object form 'bufferArray' and adds it to the 'pathArray'.
We ask the view to redraw itself again after performing any one of the above stuff.

That's all. This short code snippet lets you perform touch sensitive paint on iOS devices with properly working undo/redo functionality.

The complete code of above tutorial can be found on github.