How To Make A Drawing App For Ios
How To Make A Simple Drawing App with UIKit
This is a blog post by iOS Tutorial Team member Abdul Azeem, software architect and co-founder at Datainvent Systems, a software development and IT services company.
At some stage in all of our lives, we enjoyed drawing pictures, cartoons, and other stuff.
For me it was using a pen and paper when I was growing up, but these days the old pen and paper has been replaced by the computer and touch devices! Drawing can be especially fun on touch devices, as you can see by the abundance of drawing apps on the App Store.
Want to learn how to make a drawing app of your own? The good news is it's pretty easy, thanks to some great drawing APIs available in iOS.
In this tutorial, you will create an app very much like Color Pad for iPhone. In the process you'll learn how to:
- draw lines and strokes using Quartz2D,
- use multiple colors,
- set brush stroke widths and opacity,
- create an eraser,
- create a custom RGB color selector,
- save drawing to the camera roll, and
- share your drawing on Twitter!
Grab your pencils and get started; no need to make this introduction too drawn out! :]
Getting Started
Start by creating a simple app in xCode.
Start Xcode and create a new project with the iOS\Application\Single View Application template. Enter "DrawPad" for the project name, choose "iPhone" for the Device Family, make sure the "Use Storyboard" and "Use Automatic Reference Counting" options are checked, and save the project to a location of your choice.
Next, you'll add a framework that is required for this project.
Select the root of the project in the "Project Navigator" pane in the left sidebar to bring up the project information in the central pane. If the project target is not selected, select it, then switch to the "Build Phases" tab.
Now click the triangle next to the "Link Binary With Libraries" section to expand it. Here you can add additional libraries/frameworks to your project.
Click the (+) button to add frameworks. Add Twitter.framework; in this project you will only need this one additional framework in order to tweet your images.
All done? Time to sketch out some of the finer details. :]
Starting the First Screen
Your app will have two screens: the first screen (ViewController) will be the drawing area, and the second will be a popup settings screen (SettingsViewController) that allows control over features such as opacity and width of the brush stroke.
ViewController will contain 10 buttons for selecting different colors, an Eraser button, Save and Reset buttons, and a Settings button to open the settings popup screen.
Time to populate the ViewController! Click on the MainStoryboard.storyboard and Xcode will show the visual editor for the storyboard.
First, add two full-screen UIImageViews to the UIViewController's view. These will serve as your drawing areas on the screen. The top image view will contain each stroke as it is being made, and once the stroke is complete it will be merged into a final image displayed in the bottom image view.
To tell them apart, name the one on the bottom (i.e. the first item underneath the View in the sidebar) mainImage in the identity inspector, and the one on the top (i.e. the second item underneath the View in the sidebar) tempDrawImage in the identity inspector. At this point, your layout should look like this:
ViewController must be able to refer to these UIImageViews, so enable the assistant editor and make sure ViewController.h is displayed. Control-drag from each UIImageView to below the @interface, and connect them to an appropriately named outlet – mainImage and tempDrawImage.
Now you're at a point where you can add a splash of color to your app!
In Living Color
Next, you'll need to add some buttons to allow users to select the color they want to draw with. For this, you will use some images of colored pencils to make it look nice.
You can find these images in the resources for this tutorial. Download the resources, unzip the file, and drag the entire folder of graphics into your project. Make sure "Copy items into destination group's folder" is selected, "Create groups for any added folders" is selected, the DrawPad target is checked, and click Finish.
Now create a button at the bottom of the screen. Set the type to Custom, the image to Black.png, and the size to 28×185. Then align the button at the bottom left of the screen so only the top shows, like this:
Now duplicate the botton 9 more times by option-dragging the button over to the right. For each new button, change the Image to the appropriate colored pencil image name – the order is black, grey, red, blue, dark green, light green, light blue, brown, dark orange, and yellow.
Next, use the tag field to tag the buttons with 0-9. This allows all the buttons to have the same method as their IBAction, and you can distinguish each code by its tag. Note that the code you'll add later will assume you've added the colors in the same order as in the screenshot above.
Next, bring up the Assistant Editor and make sure ViewController.h is displayed. Control-drag from the black button to below the properties, set the Connection to Action, name it pencilPressed, and click Connect.
Then connect the rest of the buttons to the same outlet (you can do this by dragging from each button to the new outlet you made in the assistant editor – it should highlight in blue to show it's been selected).
Next, drag a new button into the canvas. Set it to Custom, set the Image to Eraser.png, and resize it to 28×87. Move it to the end of the row of pencils, so it looks like the following:
Connect the eraser button to a new IBAction method named eraserPressed. If you forgot how to do this, read the instructions from before :]
Finally, add three buttons to the top for "Reset", "Settings", and "Save" – there are graphics for each in the resources. Similar to before, set each to Custom, set each to the appropriate image, and resize them so it looks similar to the following:
Then connect these buttons to outlet methods called "reset", "settings" and "save".
When all the connections have been made, check the connections by right-clicking "View Controller" in the left pane of the interface editor. You will see that connections are made by the circled tray dots appearing in the bar to the left of the code.
Great! Now save, compile, and run your project! You will see the following screen:
Well, that looks pretty snazzy! However, in order to let your inner artist shine, you'll need to add some code!
Quick on the Draw
Your app will start off with a simple Drawing Feature, whereby you can swipe your finger on the screen to draw simple black lines. (Hey, even Picasso started with the basics).
Add the following instance variables so that your ViewController.h @interface section looks like this:
@interface ViewController : UIViewController { CGPoint lastPoint; CGFloat red; CGFloat green; CGFloat blue; CGFloat brush; CGFloat opacity; BOOL mouseSwiped; }
Here's a quick explanation of the variables used above:
- lastPoint stores the last drawn point on the canvas. This is used when a continuous brush stroke is being drawn on the canvas.
- red, blue, and green store the current RGB values of the selected color.
- brush and opacity store the brush stroke width and opacity.
- mouseSwiped identifies if the brush stroke is continuous.
Go to ViewController.m and initialize these variables to some reasonable defaults in viewDidLoad, as below:
- (void)viewDidLoad { red = 0.0/255.0; green = 0.0/255.0; blue = 0.0/255.0; brush = 10.0; opacity = 1.0; [super viewDidLoad]; }
Above, the RGB values are being initialized to black just for now. The default opacity is set to 1.0 and line width is set to 10.0.
Now for the drawing part! Add the following three functions to the ViewController.m file:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { mouseSwiped = NO; UITouch *touch = [touches anyObject]; lastPoint = [touch locationInView:self.view]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { mouseSwiped = YES; UITouch *touch = [touches anyObject]; CGPoint currentPoint = [touch locationInView:self.view]; UIGraphicsBeginImageContext(self.view.frame.size); [self.tempDrawImage.image drawInRect:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)]; CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y); CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y); CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound); CGContextSetLineWidth(UIGraphicsGetCurrentContext(), brush ); CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), red, green, blue, 1.0); CGContextSetBlendMode(UIGraphicsGetCurrentContext(),kCGBlendModeNormal); CGContextStrokePath(UIGraphicsGetCurrentContext()); self.tempDrawImage.image = UIGraphicsGetImageFromCurrentImageContext(); [self.tempDrawImage setAlpha:opacity]; UIGraphicsEndImageContext(); lastPoint = currentPoint; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { if(!mouseSwiped) { UIGraphicsBeginImageContext(self.view.frame.size); [self.tempDrawImage.image drawInRect:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)]; CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound); CGContextSetLineWidth(UIGraphicsGetCurrentContext(), brush); CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), red, green, blue, opacity); CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y); CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y); CGContextStrokePath(UIGraphicsGetCurrentContext()); CGContextFlush(UIGraphicsGetCurrentContext()); self.tempDrawImage.image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); } UIGraphicsBeginImageContext(self.mainImage.frame.size); [self.mainImage.image drawInRect:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height) blendMode:kCGBlendModeNormal alpha:1.0]; [self.tempDrawImage.image drawInRect:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height) blendMode:kCGBlendModeNormal alpha:opacity]; self.mainImage.image = UIGraphicsGetImageFromCurrentImageContext(); self.tempDrawImage.image = nil; UIGraphicsEndImageContext(); }
Phew! That's a lot of code, but don't worry we'll go over the important parts bit by bit.
These touch-notifying methods come from the parent class UIResponder; they are fired in response to touches began, moved, and ended events. You'll use these three methods to implement your drawing logic.
1) In touchesBegan the lastPoint variable is initialized to the current touch point.This is, so to speak, where the brush hits the paper! :]
lastPoint = [touch locationInView:self.view];
2) In touchesMoved, you get the current touch point and then draw a line with CGContextAddLineToPoint from lastPoint to currentPoint. You're right to think that this approach will produce a series of straight lines, but the lines are sufficiently short that the result looks like a nice smooth curve.
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y); CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y);
Now set our brush size and opacity and brush stroke color:
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), brush ); CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), red, green, blue, 1.0); CGContextSetBlendMode(UIGraphicsGetCurrentContext(),kCGBlendModeNormal);
Finish it off by drawing the path:
CGContextStrokePath(UIGraphicsGetCurrentContext());
3) In touchesEnded you check if mouse was swiped. If it was, then it means touchesMoved was called and you don't need to draw any further. However, if the mouse was not swiped, then it means user just tapped the screen to draw a single point. In that case, just draw a single point.
Once the brush stroke is done, merge the tempDrawImage with mainImage, as below:
[self.mainImage.image drawInRect:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height) blendMode:kCGBlendModeNormal alpha:1.0]; [self.tempDrawImage.image drawInRect:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height) blendMode:kCGBlendModeNormal alpha:opacity]; self.mainImage.image = UIGraphicsGetImageFromCurrentImageContext(); self.tempDrawImage.image = nil;
You will notice that the whole brush stroke was drawn on tempDrawImage rather than on mainImage. What's the point of an extra UIImageView — can't you just draw directly to mainImage?
You could, but the dual UIImageViews are used to preserve opacity. When you're drawing on tempDrawImage, the opacity is set to 1.0 (fully opaque). However, when you merge tempDrawImage with mainImage, the tempDrawImage opacity is set to the configured value, thus giving the brush stroke the opacity we want. If you were to draw directly on mainImage, it would be incredibly difficult to draw brush strokes with different opacity values.
Okay, time to get drawing some happy little trees! Save your work, then compile and run. You will see that you can now draw pretty black lines on your canvas!
The App of Many Colors
So far so good! However, it's time to add a splash of color to the scene – line art alone is kind of drab.
There are 10 color buttons on the screen at the moment, but if you click any button right now, nothing will happen. Let's fix that.
Go to the pencilPressed function in ViewController.m and modify it to match the following:
- (IBAction)pencilPressed:(id)sender { UIButton * PressedButton = (UIButton*)sender; switch(PressedButton.tag) { case 0: red = 0.0/255.0; green = 0.0/255.0; blue = 0.0/255.0; break; case 1: red = 105.0/255.0; green = 105.0/255.0; blue = 105.0/255.0; break; case 2: red = 255.0/255.0; green = 0.0/255.0; blue = 0.0/255.0; break; case 3: red = 0.0/255.0; green = 0.0/255.0; blue = 255.0/255.0; break; case 4: red = 102.0/255.0; green = 204.0/255.0; blue = 0.0/255.0; break; case 5: red = 102.0/255.0; green = 255.0/255.0; blue = 0.0/255.0; break; case 6: red = 51.0/255.0; green = 204.0/255.0; blue = 255.0/255.0; break; case 7: red = 160.0/255.0; green = 82.0/255.0; blue = 45.0/255.0; break; case 8: red = 255.0/255.0; green = 102.0/255.0; blue = 0.0/255.0; break; case 9: red = 255.0/255.0; green = 255.0/255.0; blue = 0.0/255.0; break; } }
Notice that you are now using the tags that were assigned to each pencil button earlier to distinguish them, and to assign the proper RGB value for the pencil.
What? Time for compiling and running already? Yup — compile and run, and get ready to let the colors fly! Now, tapping a color button changes the brush stroke to use that button's color. No more drab line art!
Tabula Rasa
Every great artist has those moments where he steps back and shakes his head muttering "No! No! This will never do!" You'll want to provide a way to clear the drawing canvas and start over again. You already have a 'Reset' button set up in your app. Complete ViewController's reset method as follows:
- (IBAction)reset:(id)sender { self.mainImage.image = nil; }
and thats it! All the code above does is set the mainImage.image to nil, and — voila — your canvas is cleared!
Compile and run your code. Click the Reset button to clear your canvas by clicking the 'Reset' button. There! No need to go tearing up canvases in frustration.
Nobody's perfect — the Eraser tool
Recall that you implemented an eraser button at the very outset of this tutorial. Complete ViewController's eraserPressed method as below:
- (IBAction)eraserPressed:(id)sender { red = 255.0/255.0; green = 255.0/255.0; blue = 255.0/255.0; opacity = 1.0; }
The above code simply sets the RGB color to white, and the opacity to full (1.0). As your background color is also white, this will give you a very handy eraser effect!
Compile and run — you're making a lot of progress in this app with a minimum of code! :] Play around with the eraser button; you'll quickly see how it works by "painting over" the desired area in white.
Finishing Touches — Settings Layout
Okay! You now have a functional drawing app, so if you're satisfied you could just bail here and call it a success!
But if you're an over-achiever, read on for some extra cool stuff – like adding a Setting screen to enable the user to specify the brush opacity, brush width, and a custom color!
Still with me? Let's continue on then :] Control-click on the 'DrawPad' group and click "New File…". Select the iOS\Cocoa Touch\Objective-C class template, and click Next.
Name the class SettingsViewController, enter UIViewController for subclass, ensure that both checkboxes are NOT selected, and click Next.
In the final popup, click Create again.
Now, open MainStoryboard.storyboard and drag a new View Controller to the right of the drawing view controller. Select it, and in the Identity Inspector set the Class to SettingsViewController.
Since this view controller will be presented modally (i.e. the popup will stay in front of the main screen), you will need a way to dismiss the settings. Drag a toolbar to the top of the settings view controller, rename the placeholder button from "Item" to "Close", and drag a flexible space button to the left of the button so it is aligned right:
Then drag a connection to a new IBAction in SettingsViewController.h. Name the action method closeSettings.
While you're at it you might as well implement the method. In SettingsViewController.m complete the method as follows:
- (IBAction)closeSettings:(id)sender { [self dismissViewControllerAnimated:YES completion:nil]; }
Next, you'll add opacity and brush width controls to the Setting screen.
For each of these, you will need to add:
- a UILabel for the title of the property that is being changed that does not change once created
- a UISlider to change the value
- a UILabel to display the current value
- a UIImageView to show the actual effect of the chosen value
Go ahead and set up these elements, as shown in the screenshot below:
Next connect the sliders, image views, and right-side labels to outlets in SettingsViewController.h. The UISlider controls should be connected to outlets named brushControl and opacityControl, and the UIImageView views should be connected to outlets named brushPreview and opacityPreview. Finally, the right-side UILabels should be connected to outlets named brushValueLabel and opacityValueLabel.
UISliders have a default range of 0.0 to 1.0 – this is perfect for the opacity slider, but the brush needs a much larger maximum value. You can tweak the values later to suit, but 80.0 is a reasonable choice for now. Set it in the Attributes inspector before moving on.
Next connect the "value changed" action on the sliders to a method by control-dragging from one of the sliders to the SettingsController.h file (using the Assistant view) – set the connection to "Action" and name the method sliderChanged. Connect the second slider to the same method by control dragging to it. Verify that both connections are made by right-clicking on File's Owner and checking that both connections appear.
Finally, let's make it so this view controller appears when you tap the Settings button on the main screen. Control-drag from the settings button in the drawing view controller to your new view controller, and select "modal" from the popup:
Build and run, and tap the Settings button to display the new view controller!
Finishing Touches – Settings Implementation
Done with the layout – time for the implementation!
First, add the following two properties to SettingsViewController.h so it can keep track of the brush size and opacity the user selects:
@property CGFloat brush; @property CGFloat opacity;
Don't forget to synthesize the property if you need to in SettingsViewController.m:
@synthesize brush; @synthesize opacity;
Now modify the sliderChanged function in the SettingsViewController.m file as follows:
- (IBAction)sliderChanged:(id)sender { UISlider * changedSlider = (UISlider*)sender; if(changedSlider == self.brushControl) { self.brush = self.brushControl.value; self.brushValueLabel.text = [NSString stringWithFormat:@"%.1f", self.brush]; UIGraphicsBeginImageContext(self.brushPreview.frame.size); CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound); CGContextSetLineWidth(UIGraphicsGetCurrentContext(), self.brush); CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 0.0, 0.0, 0.0, 1.0); CGContextMoveToPoint(UIGraphicsGetCurrentContext(),45, 45); CGContextAddLineToPoint(UIGraphicsGetCurrentContext(),45, 45); CGContextStrokePath(UIGraphicsGetCurrentContext()); self.brushPreview.image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); } else if(changedSlider == self.opacityControl) { self.opacity = self.opacityControl.value; self.opacityValueLabel.text = [NSString stringWithFormat:@"%.1f", self.opacity]; UIGraphicsBeginImageContext(self.opacityPreview.frame.size); CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound); CGContextSetLineWidth(UIGraphicsGetCurrentContext(),20.0); CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 0.0, 0.0, 0.0, self.opacity); CGContextMoveToPoint(UIGraphicsGetCurrentContext(),45, 45); CGContextAddLineToPoint(UIGraphicsGetCurrentContext(),45, 45); CGContextStrokePath(UIGraphicsGetCurrentContext()); self.opacityPreview.image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); } }
In the code above, as the slider control changes, the slider values will change appropriately to match. The preview images give the user an indication of the size of the brush or the opacity of the brush stroke. As a sharp-eyed developer, you will notice that the preview images are drawn using the same Quartz APIs we used in touchesMoved and touchesEnded.
Build and run your code, open the Settings screen, and play around with the sliders. You will see that the preview images and value labels change as you move it now!
Finishing Touches – Settings Integration
There's still one important piece missing here. Did you notice what it was?
The updated opacity and width values are still not being applied to the ViewController drawing canvas! That's because you have not yet communicated the values specified in the Settings screen to the ViewController yet. This is a perfect job for a delegate protocol.
Open SettingsViewController.h file and add the following code just below the imports:
@protocol SettingsViewControllerDelegate <NSObject> - (void)closeSettings:(id)sender; @end
Also add a delegate property to the code:
@property (nonatomic, weak) id<SettingsViewControllerDelegate> delegate;
And synthesize the property in SettingsViewController.m if you need to:
@synthesize delegate;
Previously the SettingsController dismissed itself. Now, it will need to delegate the responsibility to the ViewController that presented the modal view controller.
First, change the definition of the ViewController.h class and advertise that this protocol is supported:
#import "SettingsViewController.h" @interface ViewController : UIViewController <SettingsViewControllerDelegate> {
Now in SettingsViewController.m, call the delegate when the close button is tapped:
- (IBAction)closeSettings:(id)sender { [self.delegate closeSettings:self]; }
Then open ViewController.m and implement prepareForSegue so to set itself as the delegate and pass the current brush and opacity settings through:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { SettingsViewController * settingsVC = (SettingsViewController *)segue.destinationViewController; settingsVC.delegate = self; settingsVC.brush = brush; settingsVC.opacity = opacity; }
Finally, implement the closeSettings delegate function:
#pragma mark - SettingsViewControllerDelegate methods - (void)closeSettings:(id)sender { brush = ((SettingsViewController*)sender).brush; opacity = ((SettingsViewController*)sender).opacity; [self dismissViewControllerAnimated:YES completion:nil]; }
Notice how you are now fetching the updated brush and opacity values from the values set in SettingsController.
Compile and run! At this stage, you will see the brush and opacity values are now updated after you change them in the settings Screen.
Finishing Touches — A Custom Color Selector
Currently, you have 10 color buttons on your Drawing Canvas screen. However, with the custom RGB color selector, the discriminating artists using your app will have the ability to control to pick any available color from the RGB range.
Note: The RGB color model is an additive color model in which red, green, and blue light are added together in various ways to reproduce a broad array of colors. In iOS, the degree of intensity of red, green or blue is expressed with a floating point value between 0.0 and 1.0.
Various degrees of resolution are used in various computers – many years ago 24-bit color ("True color"), so called because each of R, G and B could have values from 0 to 255 (8 bits per channel, 24 bits total) was very high end but now it is common.
In this app, you will implement a true color 24-bit picker with 3 sliders that can have value from 0-255 for each of R, G and B channels.
Start with the RGB Color Selector control. Open MainStoryboard.storyboard and drag three UISliders and a few labels onto the Settings screen as you can see below:
Set the range of Sliders from 0-255. Drag IBOutlets to SettingsViewController.h for each of the sliders, naming them redControl, greenControl and blueControl. Drag to connect the Value Changed action for all three to sliderChanged just as you did for the brushControl and opacityControl sliders.
Also connect each of the right-side labels to outlets so you can show the current value of each, naming them redLabel, greenLabel and blueLabel.
Since you've provided a preview of the brush size and opacity, you might as well provide a preview of the new brush color! :] The brush preview will be shown in the selected RGB color – as well, the opacity preview will be shown in the RGB color. No need for an extra image; you'll re-use what you already have!
Open SettingsViewController.h and add the following three properties to save the current RGB values.
@property CGFloat red; @property CGFloat green; @property CGFloat blue;
Also synthesize these in SettingsViewController.m if you need to:
@synthesize red; @synthesize green; @synthesize blue;
Now open SettingsViewController.m and make the following changes to the SliderChanged function:
- (IBAction)sliderChanged:(id)sender { UISlider * changedSlider = (UISlider*)sender; if(changedSlider == self.brushControl) { self.brush = self.brushControl.value; self.brushValueLabel.text = [NSString stringWithFormat:@"%.1f", self.brush]; } else if(changedSlider == self.opacityControl) { self.opacity = self.opacityControl.value; self.opacityValueLabel.text = [NSString stringWithFormat:@"%.1f", self.opacity]; } else if(changedSlider == self.redControl) { self.red = self.redControl.value/255.0; self.redLabel.text = [NSString stringWithFormat:@"Red: %d", (int)self.redControl.value]; } else if(changedSlider == self.greenControl){ self.green = self.greenControl.value/255.0; self.greenLabel.text = [NSString stringWithFormat:@"Green: %d", (int)self.greenControl.value]; } else if (changedSlider == self.blueControl){ self.blue = self.blueControl.value/255.0; self.blueLabel.text = [NSString stringWithFormat:@"Blue: %d", (int)self.blueControl.value]; } UIGraphicsBeginImageContext(self.brushPreview.frame.size); CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound); CGContextSetLineWidth(UIGraphicsGetCurrentContext(),self.brush); CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), self.red, self.green, self.blue, 1.0); CGContextMoveToPoint(UIGraphicsGetCurrentContext(), 45, 45); CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), 45, 45); CGContextStrokePath(UIGraphicsGetCurrentContext()); self.brushPreview.image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); UIGraphicsBeginImageContext(self.opacityPreview.frame.size); CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound); CGContextSetLineWidth(UIGraphicsGetCurrentContext(),self.brush); CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), self.red, self.green, self.blue, self.opacity); CGContextMoveToPoint(UIGraphicsGetCurrentContext(),45, 45); CGContextAddLineToPoint(UIGraphicsGetCurrentContext(),45, 45); CGContextStrokePath(UIGraphicsGetCurrentContext()); self.opacityPreview.image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); }
In above code, notice how the variable values are updated as the sliders are being updated.
To ensure the correct values are displayed for color when settings opens, change prepareForSegue in ViewController.m to:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { SettingsViewController * settingsVC = (SettingsViewController *)segue.destinationViewController; settingsVC.delegate = self; settingsVC.brush = brush; settingsVC.opacity = opacity; settingsVC.red = red; settingsVC.green = green; settingsVC.blue = blue; }
Now that SettingsViewController has the values to display, update viewWillAppear in SettingsViewController.m to:
- (void)viewWillAppear:(BOOL)animated { // ensure the values displayed are the current values int redIntValue = self.red * 255.0; self.redControl.value = redIntValue; [self sliderChanged:self.redControl]; int greenIntValue = self.green * 255.0; self.greenControl.value = greenIntValue; [self sliderChanged:self.greenControl]; int blueIntValue = self.blue * 255.0; self.blueControl.value = blueIntValue; [self sliderChanged:self.blueControl]; self.brushControl.value = self.brush; [self sliderChanged:self.brushControl]; self.opacityControl.value = self.opacity; [self sliderChanged:self.opacityControl]; }
Finally open ViewController.m and make the following changes to the closeSettings method:
#pragma mark - SettingsViewControllerDelegate methods - (void)closeSettings:(id)sender { brush = ((SettingsViewController*)sender).brush; opacity = ((SettingsViewController*)sender).opacity; red = ((SettingsViewController*)sender).red; green = ((SettingsViewController*)sender).green; blue = ((SettingsViewController*)sender).blue; [self dismissViewControllerAnimated:YES completion:nil]; }
In the above code, as the SettingsViewController closes, the updated RGB values are being fetched as well.
All right — time for another compile and run stage! Put the Color Picker through its paces. The selected RGB color, which is displayed in RGBPreview, is now the default brush stroke color on the drawing canvas!
But what good is all of these wonderful works of art if you can't share them with the world? Since you can't stick the pictures up on your refrigerator, you'll tweet them in the next step of this tutorial! :]
Finishing Touches – Share and Enjoy!
Since iOS5 Twitter is natively incorporated in iOS, you will use iOS5 Twitter APIs to send the tweet along with your beautiful masterpiece.
Luckily this is super-easy! First, set ViewController as a UIActionSheetDelegate in ViewController.h:
@interface ViewController : UIViewController <SettingsViewControllerDelegate, UIActionSheetDelegate> {
Then open ViewController.m and make the following changes to the save function.
- (IBAction)save:(id)sender { UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"" delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles:@"Save to Camera Roll", @"Tweet it!", @"Cancel", nil]; [actionSheet showInView:self.view]; }
In above code, you are displaying an UIActionSheet with just two options, Save & Tweet.
Now you need a responder function for the UIActionSheet. Add the following code to the ViewController.m file.
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex { if (buttonIndex == 1) { //Tweet Image } if (buttonIndex == 0) { //Save Image } }
This is the delegate method for UIActionSheet. Let's implement both cases one at a time.
Finishing Touches – Saving for posterity
Before you can tweet your creations to the world, you first need to add some Save logic.
Make the following modification to ViewController.m:
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex { if (buttonIndex == 1) { //Tweet Image } if (buttonIndex == 0) { UIGraphicsBeginImageContextWithOptions(mainImage.bounds.size, NO,0.0); [mainImage.image drawInRect:CGRectMake(0, 0, mainImage.frame.size.width, mainImage.frame.size.height)]; UIImage *SaveImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); UIImageWriteToSavedPhotosAlbum(SaveImage, self,@selector(image:didFinishSavingWithError:contextInfo:), nil); } } - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo { // Was there an error? if (error != NULL) { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" message:@"Image could not be saved.Please try again" delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Close", nil]; [alert show]; } else { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Success" message:@"Image was successfully saved in photoalbum" delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Close", nil]; [alert show]; } }
In above code, you have first generated the drawing, then saved it to Camera Roll.
To generate the drawing, you simply obtain a snapshot of the main drawing canvas which is mainImage.image.
You first create a new Graphics Context, then draw the mainImage.image in that context. After that, you get a new Image from the graphics context and save it to Camera Roll.
Give it a try — build and run the app! Draw something on the screen and try to save it. If the image is successfully saved, you will see a popup displayed on screen as in the image below:
Finishing Touches – Tweet to your friends!
Important: please note that the Twitter API is deprecated in iOS6. The Twitter implementation below is for iOS5. If you want to integrate with Twitter in iOS6, make sure to use Social Framework instead – we'll have a tutorial on the subject soon!
To share your artwork with the world, make the following changes to ViewController.m:
#import "Twitter/TWTweetComposeViewController.h"
and later…
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex { if (buttonIndex == 1) { Class tweeterClass = NSClassFromString(@"TWTweetComposeViewController"); if(tweeterClass != nil) { // check for Twitter integration // check Twitter accessibility and at least one account is setup if([TWTweetComposeViewController canSendTweet]) { UIGraphicsBeginImageContextWithOptions(_mainImage.bounds.size, NO,0.0); [_mainImage.image drawInRect:CGRectMake(0, 0, _mainImage.frame.size.width, _mainImage.frame.size.height)]; UIImage *SaveImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); TWTweetComposeViewController *tweetViewController = [[TWTweetComposeViewController alloc] init]; // set initial text [tweetViewController setInitialText:@"Check out this drawing I made from a tutorial on raywenderlich.com:"]; // add image [tweetViewController addImage:SaveImage]; tweetViewController.completionHandler = ^(TWTweetComposeViewControllerResult result) { if(result == TWTweetComposeViewControllerResultDone) { // the user finished composing a tweet } else if(result == TWTweetComposeViewControllerResultCancelled) { // the user cancelled composing a tweet } [self dismissViewControllerAnimated:YES completion:nil]; }; [self presentViewController:tweetViewController animated:YES completion:nil]; } else { UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Sorry" message:@"You can't send a tweet right now, make sure you have at least one Twitter account setup and your device is using iOS5" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alertView show]; } } else { UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Sorry" message:@"You must upgrade to iOS5.0 in order to send tweets from this application" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [alertView show]; } } else if(buttonIndex == 0) { UIGraphicsBeginImageContextWithOptions(_mainImage.bounds.size, NO, 0.0); [_mainImage.image drawInRect:CGRectMake(0, 0, _mainImage.frame.size.width, _mainImage.frame.size.height)]; UIImage *SaveImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); UIImageWriteToSavedPhotosAlbum(SaveImage, self,@selector(image:didFinishSavingWithError:contextInfo:), nil); } }
In the above function, you are first generating the drawing and then Tweeting it using the built in Twitter APIs in iOS5. If you need to understand how the Twitter APIs work in IOS5, then you should read the awesome Beginning Twitter in iOS 5 Tutorial by Felipe Laso.
Getting the itch to tweet that first drawing of yours? Go ahead and compile and run the code! Draw something on the screen, and try to tweet it. If everything is setup correctly, you will see the Twitter dialogue displayed on screen. Great! In this case, a picture is definitely worth 140 characters. :]
And with that my friends, we are done!
Where To Go From Here?
Here is an example project with all of the code from the above tutorial.
You can play around a bit more with the brush strokes and also investigate the drawing of arcs and rectangles using Quartz2D. A good start would be to have a look at Quartz 2D Programming Guide . There are number of beginner and advanced-level concepts there with which you can play around to create awesome shapes and patterns.
If you want to learn how to draw more smooth lines, you should also check out this smooth line drawing article by Krzysztof Zablocki. It's based on Cocos2D but you can use the same technique in UIKit.
I hope you enjoyed this tutorial as much as I did! Feel free to post your questions and comments on the forum — and feel free to tweet your masterpieces you created with your new app! :]
This is a blog post by iOS Tutorial Team member Abdul Azeem, software architect and co-founder at Datainvent Systems, a software development and IT services company.
raywenderlich.com Weekly
The raywenderlich.com newsletter is the easiest way to stay up-to-date on everything you need to know as a mobile developer.
Get a weekly digest of our tutorials and courses, and receive a free in-depth email course as a bonus!
How To Make A Drawing App For Ios
Source: https://www.raywenderlich.com/2862-how-to-make-a-simple-drawing-app-with-uikit
Posted by: paigeprimsequiew.blogspot.com
0 Response to "How To Make A Drawing App For Ios"
Post a Comment