Wednesday, May 18, 2011

UIBezierPath tutorial for iPhone SDK 4.0 - Part 1


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(will write that tutorial too next) that I fell in love with the use of UIBezierPath. You can have simple drawing as well as pattern based brush tip.   It is sooo easy to use this as pencil too that I don't even have words !





You can init 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 allObjectsobjectAtInd
    [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.
- (void)drawRect:(CGRect)rect
{
    [brushPattern setStroke];
    [myPath strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];

}








Pattern brush.. doesn't exactly work the way it is in Photoshop , because once you fill the whole screen with your drawing, the screen looks completely like a screen full of tiles of your pattern which is not what I would want but ... thats what we have so far in terms of pattern brush. I am trying to implement the brush with opengl lets see if I succeed then I will post it here !


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


















































You can download the code HERE

Update:
Thanks to Sonia for pointing it out , wanted to know about smoothing of edges of bezier path, so that it doesn't look distorted . You can change the  capStyle of line for that. I have changed it and updated the code at above link.





















Well.. Finally implemented and uploaded code implementing Undo-Redo.
POSTED HERE




34 comments:

  1. Thanks for the tutorial. But, how can we implement the undo? Any update for this?

    ReplyDelete
  2. I have posted an article about how to implement undo-redo in BezierPath
    http://soulwithmobiletechnology.blogspot.com/2011/06/redo-undo-in-paint-feature.html

    ReplyDelete
    Replies
    1. Reetu, I am having a devil of a time merging the undo/redo code with the line drawing code. The difficulty is that I have segmented controls for brushes, colors and background pictures. I have posted a question on StackOverflow here: http://stackoverflow.com/questions/10142404/xcode-undo-a-bezier-path, but no answers so far. Would you be willing to have a look at the offending methods? I would really appreciate that.

      David

      Delete
  3. great tutorial.

    is there a straightforward way to add an image to the MyLineDrawingView background to draw "over". ?

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete
  5. Hi reetu, hope u are fine...great tutorial once again.I only want to draw straight lines, start it when the user double tap on the screen & end it when the user again double tap on the screen.How can do it with UIBezierPath?

    ReplyDelete
  6. Nice drawing app!
    And

    REETU RAJ nice redoo undo

    ReplyDelete
  7. I have been searching for this technique for some time. It sure is better than the Apple GLPaint sample. Thank you so much for figuring this out and making it available. You are very generous.

    David

    ReplyDelete
  8. One thing...Reetu, how would I save the screen - either as a file or a screenshot?

    Thanks!!

    ReplyDelete
  9. I will post the little code snippet for that.

    ReplyDelete
    Replies
    1. Thanks. That would be wonderful...

      Delete
    2. Hi Reeta, I got this code to work - it saves the image to the photo album. Maybe other can use it - probably, you can improve it.. David

      ps. The flash screen method is cool. It signals that the save worked...


      - (IBAction)saveButtonTapped:(id)sender
      {
      NSLog(@"%s", __FUNCTION__);

      [self captureToPhotoAlbum];
      [self flashScreen];

      }


      -(void)captureToPhotoAlbum {
      NSLog(@"%s", __FUNCTION__);
      // UIImageWriteToSavedPhotosAlbum(drawImage.image, self, nil, nil);
      //return;

      // Get image to save
      CGImageRef screen = UIGetScreenImage();
      UIImage *img = [UIImage imageWithCGImage:screen];

      // Image cropping
      CGImageRef imageRef = CGImageCreateWithImageInRect([img CGImage], CGRectMake(0.0f, 70.0f, 768.0f, 768.0f));
      UIImage *img2Save = [UIImage imageWithCGImage:imageRef];
      CGImageRelease(imageRef);

      // Request to save the image to camera roll
      UIImageWriteToSavedPhotosAlbum(img2Save, self, nil, nil);

      }

      -(void) flashScreen {
      NSLog(@"%s", __FUNCTION__);
      UIWindow* wnd = [UIApplication sharedApplication].keyWindow;
      UIView* v = [[UIView alloc] initWithFrame: CGRectMake(0, 0, wnd.frame.size.width, wnd.frame.size.height)];
      [wnd addSubview: v];
      v.backgroundColor = [UIColor whiteColor];
      [UIView beginAnimations: nil context: nil];
      [UIView setAnimationDuration: 1.0];
      v.alpha = 0.0f;
      [UIView commitAnimations];
      }

      Delete
  10. Thank you mam for your code.
    Mam I am developing a book app in ipad where user can write notes. For that i need a pen like structure through which user can make note and save it and will see it again when he came to same page or where ever i list him notes. So how to implement them and save them so that user can see his notes same as he wrote. I never work on drawing before in iphone/ipad. I will be obliged for any guidance and help.

    ReplyDelete
  11. Hello mam,
    Mam I want to create a notebook like app like bamboo app, neu notepad etc
    I have started reading coregraphics for that
    But not getting right approach what should be read and from where.
    because in my previous app I just started working on it without knowing cons and prons of the library i used and hence results into disaster. This time i dont want to repeat that. An app in which one can write, draw , erase, color change etc. (the functionality required by a notebook.)
    Can you suggest me some tutorial or site from where I can read and able to develop such App by my own

    Thank you in advance

    ReplyDelete
  12. @Sonia, You can easily use UIBezierPath for all the needs you mentioned above - write/draw/erase/color change of pen.
    This is just an introductory tutorial, you can read more about it on Apple's website. to learn more deeply about it.
    I think you have just started in this application.
    I wish you best of luck, I am sure this reply is good enough to guide you in the right direction.

    ReplyDelete
  13. I just wanted to confirm is bezier curve is enough for writing and sketching so that i can proceed in that. From your reply i got it. As with the code you provided not able to draw smoothly and not able to put simple dots so i was little bit confused but now I started working on it.
    Thank You mam for your reply.

    ReplyDelete
  14. Thanks for your nice tutorial.
    I integrate it in my app but in my app there is extra feature i.e. to erase the path which we draw using an eraser.
    User has an option to change the size of eraser.
    I googled it & fount the solution that use pattern image similar to the background but I dont want this.
    If you have any option the please help me.
    Thanks.

    ReplyDelete
  15. @Sonia, you can change the capStyle, I have changed it and updated the code on the link to download.

    @Giri, you can use the Basic draw line method with CGContext and changing to BlendMode of CGContext to Clear.

    ReplyDelete
  16. I tried it using following code -
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGContextSetBlendMode(ctx, kCGBlendModeClear);
    CGContextBeginTransparencyLayer(ctx, NULL);
    {
    CGContextSetGrayFillColor(ctx, 0.0, 1.0); // solid black

    CGContextAddPath(ctx, pathToErase);
    CGContextFillPath(ctx);
    // the above two lines could instead be CGContextDrawImage()
    // or whatever else you're using to clear
    }
    CGContextEndTransparencyLayer(ctx);

    it draws he line but with black color.
    I googled lots with no luck.
    If you have possible then please share some amount of code with me.

    ReplyDelete
  17. Thank you very much for this. It was a great starting point. Is there anyway to validate the drawing?
    For example, lets say there are dots on the screen and we use your code to draw the line. Any way to check the final drawn image is correct?

    Thanks.

    ReplyDelete
  18. Hello Reetu, thanks for your post, it helped me a lot, but now I am trying to draw different color lines by selecting color options, below is my code, which I tried so far

    - (void)drawRect:(CGRect)rect
    {
    if(changecolor)
    {
    AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    strokeColor = appDelegate.color;
    NSLog(@"%@",strokeColor);

    SEL blackSel = NSSelectorFromString(strokeColor);
    UIColor* tColor = nil;
    if ([UIColor respondsToSelector: blackSel])
    tColor = [UIColor performSelector:blackSel];
    [tColor setStroke];
    [tColor setFill];

    for (UIBezierPath *_path in pathArray)
    {
    [_path strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
    }
    }
    else
    {
    [[UIColor redColor] setStroke];
    [[UIColor redColor] setFill];

    for (UIBezierPath *_path in pathArray)
    {
    [_path strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];

    }

    }

    But what is happening here is , if I first draw a red line and later by selecting color options , I try to draw a blue line, my previous red line also becomes blue, I know that because I am using the same array, its turning blue, but how to slove this issue, whether I should use different arrays, but that will not be a optimal solution. so I need your help. waiting for your reply

    ReplyDelete
  19. Hi Ranjit,

    Everytime you add a line on your screeen, it gets added to pathArray as a bezier path.As you can see in for(UIBezierPath *_path in pathArray), it runs loop on all those lines here without setting stroke color [[UIColor redColor] setStroke];. If you want to make different bezier paths of differnt color, you will have to keep a color array too. Or Another wise way way will be to make a custom class(say MyColoredBezier) with 2 members UIBezierPath and UIColor, then everytime you add a new line or new color, a new object of MyColoredBezier is created, later on you run the loop with setStroke & strokeWithBlendMode as for (MyColoredBezier *obj in coloredpathArray). Hope this answers your query.


    for (MyColoredBezier *obj in coloredpathArray)
    {
    [obj.color setStroke];
    [obj.path strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];

    }

    ReplyDelete
    Replies
    1. Could you help a beginner in the community and tell me how did you implemented this? How did you wrote the m file of MyColoredBezier and then implemented the change of colors?

      Delete
  20. Thanks Reetu, I will try it and get back to you.

    ReplyDelete
  21. This comment has been removed by the author.

    ReplyDelete
    Replies
    1. Hi reetu, thanks for your suggestion, It works perfectly, I had one more doubt, how to erase a part of line drawn and not the whole line?

      Delete
  22. HI.....Reetu

    I'd like to find a way to be able to draw on UIImageView AND ZOOM on it
    and draw.

    It is weird because I don't have idea to implement this. Why I say weird is because I manage to create a zoom system for UIImageView, using the UIScrollView. But how to be able to do the drawing on the imageview after zooming in? because when you touch and move your finger on the image to draw, then it becomes to pan function for the image. Or, if I put a button to toggle between Pan & draw, would that be acceptable to you (as user point of view)?
    can u help me out?

    ReplyDelete
  23. Hi reetu, I am following your approach for a drawing project, everything works fine, but as we are using an array to store bezeirpaths, after some drawing, their is a lag in the line drawn, I have implemented an erase function, if I erase the line I draw and then again I try to draw a line, I get serious performance issues, I googled about this issue, and the solution is to use CGLayers, Can you throw some light on this? It would be very helpful for me. I am confused on how to use CgLayers.

    ReplyDelete
  24. For me the first line between the first two points is a straight line but then after that it is nice and curved. Is there a way to make it curved between the first two points as well?

    ReplyDelete
  25. On drawing continuously without lifting your finger up, drawing becomes very very slow. And it also increases the memory allocation. What is the solution to that?

    ReplyDelete
  26. The overseas designer for the devoted iPhone development
    should be able to provide you the appropriate programs with the highest skills with the extremely innovative technological innovation like Cocoa/Objective C as well as Xcode / Interface Designer with excellent skills, which can be possible when they have excellent encounter and information in this technological innovation.

    ReplyDelete
  27. At first i would say Thank you for this great tutorial. But how can i add a single Point on the screen.

    ReplyDelete
  28. The case is made of flexible thermoplastic material that is agile and elastic, suitable to protect your iPhone 4 Parts without adding to its weight or size...
    Thanks for sharing...!!!!

    ReplyDelete