[Tutorial] Drawing to the screen.

Discussion in 'iOS Development' started by SkylarEC, Jan 16, 2009.

  1. SkylarEC

    SkylarEC Super Moderator Emeritus Staff Member

    Joined:
    Sep 19, 2007
    Messages:
    6,642
    Likes Received:
    129
    [​IMG]

    Want to create a drawing app? Here you go. Clearly, you will need to make improvements to the drawing code, as I literally spent five minutes making this to test something for another app. Regardless, you should be able to get the gist of what's going on. What I am linking is just your basic standard xcode view based project. The only pertinent part of this are the touchsBegan, touchesMoved, and touchesEnded methods, which I will be posting in this thread.

    To draw, simply draw. To move your finger over the screen. To create a dot, tap the screen. To clear the screen, double tap the screen. Simple.


    Code:
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
     
    mouseSwiped = NO;
    UITouch *touch = [touches anyObject];
     
    if ([touch tapCount] == 2) {
    drawImage.image = nil;
    return;
    }
     
    lastPoint = [touch locationInView:self.view];
    lastPoint.y -= 20;
     
    }

    The first thing we do is set a bool to track whether or not we moved our finger across the screen. Since this is when we touch, we will set it (mouseSwiped) to NO. After getting our touch, we want to know whether the user double tapped. If they did, we set our canvas to an empty image and break out of the touchesBegan method. If we are still going through our code, that means that the user did not double tap. We will now set a CGPoint ivar we set to where we user tapped the screen. This is so we know from where the user moved their finger.


    Code:
    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    mouseSwiped = YES;
     
    UITouch *touch = [touches anyObject];
    CGPoint currentPoint = [touch locationInView:self.view];
    currentPoint.y -= 20;
     
     
    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(), 5.0);
    CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 1.0, 0.0, 0.0, 1.0);
    CGContextBeginPath(UIGraphicsGetCurrentContext());
    CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
    CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y);
    CGContextStrokePath(UIGraphicsGetCurrentContext());
    drawImage.image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
     
    lastPoint = currentPoint;
     
    }
    
    ;


    First off, before we forget, let's set our mouseSwiped flag to YES, that way we can refer to it later. Next, let's get the location where the user moved their finger. We'll set that location to currentPoint.

    Graphics Contexts are far beyond the average developer in this forum, so I won't get into them. Instead, and also because it would be overkill in this scenario, we will not be creating our own. Instead, we will be using a shortcut within UIKit called UIGraphicsGetCurrentContext() to gather the current context in which to work.

    Begin the context with the size of our view in which we want to draw, and draw an image into it. We now want to set our line caps. This is what makes our lines not end in squares and look natural. There are other caps you can use, so feel free to experiment. Next, we're going to want to set the color with which we will be working. In this case, red.

    Then, begin the actual drawing, we'll need to call CGContextBeginPath(...) and fill it in. First, we'll move to the point where we'd like to start. This is the point where the user tapped. Then, we'll call CGContextAddLineToPoint to add a line to the point we just set. The line extends from the first point you set to the point you set within AddLineToPoint function call. Finally, call CGContextStrokePath to stroke the path, ie color the line you just made.

    And you have a line in your context, congrats. Let's create an image out of that and set it as our canvas. Make sure to end the context with which we ar eworking: UIGraphicsEndImageContext(); We want to reset the the touch down point as the point to where we just drew. This way, if the user keeps swiping, we can start from where we left off.

    Lastly, and importantly, ignore that mouseMoved variable. In fact, delete it from your code. Seriously.


    Code:
    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
     
    UITouch *touch = [touches anyObject];
     
    if ([touch tapCount] == 2) {
    drawImage.image = nil;
    return;
    }
     
     
    if(!mouseSwiped) {
    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(), 5.0);
    CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 1.0, 0.0, 0.0, 1.0);
    CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
    CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
    CGContextStrokePath(UIGraphicsGetCurrentContext());
    CGContextFlush(UIGraphicsGetCurrentContext());
    drawImage.image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    }
    }

    In the touchesEnded method, the first thing we want to do is break out of the user double tapped. This is because the user has already cleared their canvas.

    Now, if we are still in the function, then the user has not double tapped. Check to make see if the user has swiped. If they have, do nothing, we've already drawn, and there's no reason to do anything else.

    Else, if the user did not swipe, we want to make a dot. Simply do exactly what we did in the touchesMoved. Only, instead of drawing a line, we draw a point.

    Congrats, you now can draw within your application. Enjoy it.



    Link: http://www.touchrepo.com/SampleCode/TEST_DRAW_APP.zip
  2. Steaps

    Steaps New Member

    Joined:
    Oct 24, 2007
    Messages:
    5,074
    Likes Received:
    41
    Device:
    iPod touch
    Your on a roll with these tutorials, i like it. Keep it up :).
    Maybe a UITableView Grouped tutorial next please? Like Settings.app UITableView kind of thing. Thanks :).
  3. Nickll9009

    Nickll9009 New Member

    Joined:
    Sep 19, 2007
    Messages:
    1,367
    Likes Received:
    8
    Device:
    iPod touch
    Are you using IB? Cause if you are, all you have to do is select the Table View, and go to attributes, and change it to grouped.
  4. Steaps

    Steaps New Member

    Joined:
    Oct 24, 2007
    Messages:
    5,074
    Likes Received:
    41
    Device:
    iPod touch
    I know how to get it to grouped. I just have no clue what so ever on how to use and create one etc... I'm not good with anything really, just trying to learn.
  5. SkylarEC

    SkylarEC Super Moderator Emeritus Staff Member

    Joined:
    Sep 19, 2007
    Messages:
    6,642
    Likes Received:
    129
    Keep this thread on topic.
  6. gojohnnyboi

    gojohnnyboi Well-Known Member

    Joined:
    Jan 25, 2008
    Messages:
    3,339
    Likes Received:
    55
    skylar, a question.

    i understand everything but what this does:
    Code:
    CGContextFlush(UIGraphicsGetCurrentContext());
    
    would you mind telling me what that does? =] thanks.
  7. SkylarEC

    SkylarEC Super Moderator Emeritus Staff Member

    Joined:
    Sep 19, 2007
    Messages:
    6,642
    Likes Received:
    129
    What it does is forces the content of the window context to be flushed immediately. In this scenario, it does nothing (as it shouldn't do anything). The reason it is in the code was because this sample app was to prep for something else I was working on. Feel free to remove it from the code. Also, you probably don't want to really cal it, as the OS flushes automatically. I believe Apple recommends avoiding using this function call.
  8. henningh

    henningh New Member

    Joined:
    Mar 4, 2009
    Messages:
    4
    Likes Received:
    0
    except for one thing

    This tutorial works great, thanks. Except for one thing that happens only when used on my iPhone device. When I draw with my finger, the paint doesn't appear until I lift or pause my finger. Then everything I did magically appears.

    Any ideas?

    QImageView.h:

    #import <UIKit/UIKit.h>

    @interface QImageView : UIImageView
    {
    BOOL mouseSwiped;
    CGPoint lastPoint;
    }

    @end

    QImageView.m:

    #import "QImageView.h"


    @implementation QImageView


    - (id)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
    // Initialization code
    }
    return self;
    }


    - (void)drawRect:(CGRect)rect {
    // Drawing code
    }


    - (void)dealloc {
    [super dealloc];
    }

    // Handles the start of a touch
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {
    mouseSwiped = NO;
    UITouch *touch = [touches anyObject];

    if ([touch tapCount] == 2)
    {
    self.image = nil;
    return;
    }

    lastPoint = [touch locationInView:self];
    //lastPoint.y -= 20;
    }

    // Handles the continuation of a touch.
    - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
    {
    mouseSwiped = YES;

    UITouch *touch = [touches anyObject];
    CGPoint currentPoint = [touch locationInView:self];
    //currentPoint.y -= 20;

    UIGraphicsBeginImageContext(self.frame.size);
    [self.image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
    CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
    CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 5.0);
    CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 1.0, 0.0, 0.0, 1.0);
    CGContextBeginPath(UIGraphicsGetCurrentContext());
    CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
    CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y);
    CGContextStrokePath(UIGraphicsGetCurrentContext());
    self.image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    lastPoint = currentPoint;
    }

    // Handles the end of a touch event when the touch is a tap.
    - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
    {
    UITouch *touch = [touches anyObject];

    if ([touch tapCount] == 2)
    {
    self.image = nil;
    return;
    }


    if(!mouseSwiped) {
    UIGraphicsBeginImageContext(self.frame.size);
    [self.image drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
    CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
    CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 5.0);
    CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 1.0, 0.0, 0.0, 1.0);
    CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
    CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
    CGContextStrokePath(UIGraphicsGetCurrentContext());
    CGContextFlush(UIGraphicsGetCurrentContext());
    self.image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    }
    }

    @end
  9. SkylarEC

    SkylarEC Super Moderator Emeritus Staff Member

    Joined:
    Sep 19, 2007
    Messages:
    6,642
    Likes Received:
    129
    I can't do all the work for you ; )

    It should be fairly simply for you to figure out a fix.
  10. henningh

    henningh New Member

    Joined:
    Mar 4, 2009
    Messages:
    4
    Likes Received:
    0
    :) You say that, but I've been trying to figure this out for 3 hours now.

Share This Page