Chapter     12

Graphics Recipes

In this chapter, we will explore UIKit, Quartz 2D, and Core Graphics. We’ve already used UIKit to create windows, buttons, and views. Quartz 2D is a rendering engine on which UIKit is built. Quartz 2D is part of the Core Graphics framework, and together they can help a developer create impressive views and interfaces. Any time you see an impressive custom interface such as Evernote, which looks completely different from the standard Apple interface, you can bet Core Graphics played a big part in making it happen. As a developer, you can use Core Graphics to customize virtually any aspect of your interface by subclassing existing Apple elements, such as buttons and table view cells.

While this chapter does not go deep into creating custom interfaces, we will provide you with a good foundation to build on. Throughout the next few recipes, we’ll build a custom view that will include rectangles, ellipses, arcs, shadows, gradients, and custom text. We’ll do this by creating a single view that will get more complex as the chapter progresses.

Recipe 12-1: Drawing Simple Shapes

Most artists start with simple shapes and build upon them until they have a completed work. In iOS, you also can start off with the basics of drawing simple shapes in a view and build upon them until you have a design.

In this recipe, we’ll create a new view with a header bar and a circle. Start by creating a new single view application project. Before building the user interface, you will create a custom view that will implement some simple drawing code. Create a new subclass of UIView called “GraphicsRecipesView” and add the code in Listing 12-1 to its drawRect: method.

Listing 12-1.  Implementing some drawing

//
//  GraphicsRecipesView.m
//  Recipe 12-1 Drawing Simple Shapes
//

#import "GraphicsRecipesView.h"

@implementation

// ...

// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{

    CGContextRef context = UIGraphicsGetCurrentContext();

    //Set color of current context
    [[UIColor lightGrayColor] set];
    
    //Draw rectangle
    CGRect drawingRect = CGRectMake(0.0, 0.0f, 320.0f, 60.0f);
    CGContextFillRect(context, drawingRect);

    //Set color of current context
    [[UIColor whiteColor] set];
    
    //Draw ellipse <- I know we’re drawing a circle, but a circle is just a special ellipse.
    CGRect ellipseRect = CGRectMake(60.0f, 150.0f, 200.0f, 200.0f);
    CGContextFillEllipseInRect(context, ellipseRect);
}

The following steps were added to the method in Listing 12-1 to draw basic shapes:

  1. Obtain a reference to the current “context” represented by a CGContextRef.
  2. Set the color of current context.
  3. Define a CGRect in which to draw.
  4. Fill in the current shape using the CGContextFillEllipseInRect function.
  5. Repeat Steps 2 to 3 for each additional shape.

To display this in your preconfigured view, you must add an instance of this class to your user interface. This is done programmatically or through Interface Builder, the latter of which we demonstrate.

In the view controller found in the Main.storyboard file, drag out a UIView from the object library in the utilities pane into your view. Place the view in the window and drag it to fill the entire window, as shown in Figure 12-1. With the view selected, choose a different background from the attributes inspector. For this example, I used a light blue color.

9781430259596_Fig12-01.jpg

Figure 12-1. Building your .xib file with a UIView

While your UIView is selected, go to the identity inspector in the right panel. Under the Custom Class section, change the Class field from “UIView” to “GraphicsRecipesView,” as shown in Figure 12-2. This connects the view you added in Interface Builder with the custom class you created earlier.

9781430259596_Fig12-02.jpg

Figure 12-2. Connecting a custom class to a UIView

When running this application, you should see the output of your drawing commands converted into a visual display, resulting in the simulated view shown in Figure 12-3.

9781430259596_Fig12-03.jpg

Figure 12-3. A custom view drawing a gray square at the top of screen as well as a white circle

Refactoring a Bit

Now we will clean up the code a bit and break each element into a separate function. That way we have the ability to reuse a function, and we can clean up our drawRect: method as well. The code we previously wrote in the drawRect method of the GraphicsRecipesView now looks like Listing 12-2.

Listing 12-2.  Refactoring draw code to move the rectangle and circle drawings dedicated methods

- (void)drawRect:(CGRect)rect
{
    
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    //Call function to draw rectangle
    [self drawRectangleAtTopOfScreen:context];
    
    //Call function to draw circle
    [self drawEllipse:context];

}

-(void)drawRectangleAtTopOfScreen:(CGContextRef)context
{
    
    CGContextSaveGState(context);
    //Set color of current context
    [[UIColor lightGrayColor] set];
    
    //Draw rectangle
    CGRect drawingRect = CGRectMake(0.0, 0.0f, 320.0f, 60.0f);
    CGContextFillRect(context, drawingRect);
    CGContextRestoreGState(context);
    
}

-(void)drawEllipse:(CGContextRef)context
{
    
    CGContextSaveGState(context);
    
    //Set color of current context
    [[UIColor whiteColor] set];
    
    //Draw ellipse <- I know we’re drawing a circle, but a circle is just a special ellipse.
    CGRect ellipseRect = CGRectMake(60.0f, 150.0f, 200.0f, 200.0f);
    CGContextFillEllipseInRect(context, ellipseRect);
    CGContextRestoreGState(context);
    
}

In Listing 12-2, you’ll see that we create two new functions and give an input parameter of type CGContextRef. This allows us to pass in the current context. Function calls CGContextSaveGState and CGContextRestoreGState have also been added. These will ensure that if we make a change inside these functions calls, such as by setting a color or shadow, it will apply only to this one function. Note that these functions save the state but not the current drawing. Restoring a state does not undo changes to a drawing, such as adding a rectangle or ellipse; it only restores values such as current fill color and shadowing properties.

Moving forward with the rest of these recipes, we’ll be creating new functions and calling them from the drawRect: method.

Recipe 12-2: Drawing Paths

Thankfully, you are not limited to drawing only rectangles and ellipses. There are a few other functions. You can draw custom shapes by creating “paths” of points connected by lines or curves. To start, we’ll draw a semi-transparent triangle that resembles a play button at the bottom of the screen below the white circle we created in Recipe 12-1. Create a new function titled “drawTriangle” and call that function from the drawRect: method, as shown in Listing 12-3.

Listing 12-3.  Building your .xib file with a UIView

- (void)drawRect:(CGRect)rect
{
    
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    //Call function to draw rectangle
    [self drawRectangleAtTopOfScreen:context];
    
    //Call function to draw circle
    [self drawEllipse:context];

    //Call function to draw triangle
    [self drawTriangle:context];
    
}

//...

-(void)drawTriangle:(CGContextRef)context
{
    
    CGContextSaveGState(context);
    
    //Set color of current context
    [[UIColor colorWithRed:0.80f
                     green:0.85f
                      blue:0.95f
                     alpha:1.0f] set];
    
    // Draw Triangle
    CGContextBeginPath(context);
    CGContextMoveToPoint(context, 140.0f, 380.0f);
    CGContextAddLineToPoint(context, 190.0f, 400.0f);
    CGContextAddLineToPoint(context, 140.0f, 420.0f);
    CGContextClosePath(context);
    CGContextSetGrayFillColor(context, 0.1f, 0.85f);
    CGContextSetGrayStrokeColor(context, 0.0, 0.0);
    CGContextFillPath(context);
    CGContextRestoreGState(context);
}

Listing 12-4 creates a shape by implementing the following steps:

  1. Declare a new path inside the context.
  2. Specify the starting point for the path.
  3. Specify all points along the path.
  4. Close the path.
  5. Set the fill color and stroke color.
  6. Fill the path.

Note   We are using a different method of filling the path. We could have just as easily declared a new color by using [[UIColor colorWithRed:0.40f green:0.40f blue:0.40f alpha:0.85f] set]; at the beginning of the code segment and removing CGContextSetGrayFillColor. CGcontextSetGrayFillColor is a convenience function for using gray; we can use CGContextSetFillColor to choose any color we want.

Build and run the project and you should see a screen that resembles Figure 12-4 in the simulator.

9781430259596_Fig12-04.jpg

Figure 12-4. A custom view with a path drawing of a triangle added

Now let’s take this one step further and draw an arc. This is a tad more complicated, but not too difficult. First we’ll need to do a bit of math. Here is what you need to know to draw an arc using the CGContextAddArc function:

  1. Determine the X and Y point coordinates of where the center of the arc is.
  2. Decide on the radius of the arc.
  3. Pick the start and end angles for the arc, both starting from the 3 o’clock position.
  4. Determine whether the arc will sweep clockwise or counter clockwise.

Again, we’ll create a new method to draw the arc and call it from within the drawRect: method, as shown in Listing 12-4.

Listing 12-4.  Implementing a drawArc: method and calling it from the drawRect: method

- (void)drawRect:(CGRect)rect
{
    
    CGContextRef context = UIGraphicsGetCurrentContext();

    
    //Call function to draw rectangle
    [self drawRectangleAtTopOfScreen:context];
    
    //Call function to draw circle
    [self drawEllipse:context];
    
    //Call function to draw triangle
    [self drawTriangle:context];
    
    //Call function to draw arc
    [self drawArc:context];
    
}
-(void)drawArc:(CGContextRef)context
{
    
    CGContextSaveGState(context);
    
    //Set color of current context
    [[UIColor colorWithRed:0.30f
                     green:0.30f
                      blue:0.30f
                     alpha:1.0f] set];
    
    //Draw Arc
    CGContextAddArc(context, 160.0f, 250.0f, 70.0f, 0.0f, 3.14, 0);
    CGContextSetLineWidth(context, 50.0f);
    CGContextDrawPath(context, kCGPathStroke);
    CGContextRestoreGState(context);
}

In Listing 12-4 we’re drawing an arc that is centered at X = 160 points and Y = 250 points. The radius is 70 points and the arc starts at 0 radians and ends at 3.14 (pi) radians, sweeping in a clockwise direction. To move in a counter-clockwise direction, replace 0 with 1. For this example, we set the line width for the arc to 50 points to give it a status-bar type of look.

If you build and run the project, you should now have a result that looks like Figure 12-5.

9781430259596_Fig12-05.jpg

Figure 12-5. A custom view with an arc drawn over the circle

Recipe 12-3: Adding Fonts and Drawing Text

Current trends in design are putting more and more emphasis on typography and less emphasis on images, textures, and gradients. This is a consequence of having to deal with multitudes of screen sizes and having crisp displays that make text look much better. For this reason, making use of available iOS fonts as well as importing custom fonts is absolutely necessary for creating visually appealing applications.

Making Use of Available Fonts

There is a large list of system fonts installed with iOS 7, as Apple has put a lot more emphasis on typography for this iOS release. Apple has added literally hundreds of fonts since iOS 6.

Note   In iOS 7, Apple also added the Text framework that allows even more control over fonts and gives you the ability to do everything from changing leading to creating custom font types. This type of customization is beyond the scope of this book, but you can learn more about it at https://developer.apple.com/library/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/Introduction/Introduction.html#//apple_ref/doc/uid/TP40009542.

To create a font object, you will need to make use of the UIFont class, which is built on the UIKit framework, the primary framework for displaying buttons, text, views, and the like. To declare a UIFont object you will need to know the name of the font family (such as Helvetica or Times New Roman) and the font face (such as Bold, Italic, and BoldItalic).

Declaring a UIFont object is easy once you know this information. The format should be as follows:

 UIFont *fontObjectName = [UIFont fontWithName:@"fontFamily-fontFace" size:<#(CGFloat)#> ];

Thus, if you want to use the Helvetica family with the bold font face and a size of 12 points, the declaration would be the following:

UIFont *fontObjectName = [UIFont fontWithName: @"Helvetica-Bold" size: 12.0f];

Importing Your Own Fonts

First, you’ll need a font to add to your project. Fortunately, you can find many open source fonts on the Google website at http://www.google.com/fonts. For this example, we downloaded the Oleo Script Swash Caps family with the bold face.

Now that we have a custom font, add it to your project by dragging and dropping it from the finder to the project navigator in Xcode. Once you are done, you should see it added to the project navigator, as shown in Figure 12-6.

9781430259596_Fig12-06.jpg

Figure 12-6. Adding a font to the supporting files group

After you drag and drop the file, you will be presented with a dialog box. Make sure you select the check box “Copy items into destination group’s folder (if needed)” and make sure you click the check box to add it to your target.

Next, from the project navigator expand the “Supporting Files” folder and open the GraphicsRecipes-Info.plist file.

Add a new key to the GraphicsRecipes-Info.plist file by selecting the “+” next to “Information Property List” and title it “Fonts provided by application.” When you start typing in the field, it should come up as one of the selections. Make sure the Type is “Array.”

Now create a new item under “Font provided by application” and give it a string value that matches the filename. Make sure you choose “String” for the Type.

9781430259596_Fig12-07.jpg

Figure 12-7. Adding a font to info.plist

Now you should be ready to use your font. Add a new method titled “drawTextAtTopOfScreen” to the GraphicsRecipesView file, as shown in Listing 12-5.

Listing 12-5.  Implementing the drawTextAtTopOfScreen: method

-(void)drawTextAtTopOfScreen:(CGContextRef)context{
    
    CGContextSaveGState(context);
    
    //Create UIColor to pass into text attributes
    UIColor *textColor = [UIColor colorWithRed:0.80f
                     green:0.85f
                      blue:0.95f
                     alpha:1.0f];
    
    //Set font
    UIFont *customFont = [UIFont fontWithName:@"OleoScriptSwashCaps-Bold" size:40.0f ];
    //UIFont *customFont = [UIFont systemFontOfSize:20.0f];
    NSString *titleText = @"iOS Recipes!";

    [titleText drawAtPoint:CGPointMake(55,5) withAttributes:@{NSFontAttributeName:customFont,
                                                              NSForegroundColorAttributeName:textColor}];
    CGContextRestoreGState(context);
}

Of course, you’ll need to call this from your drawRect: method, as shown in Listing 12-6.

Listing 12-6.  Updating the drawRect method to include the drawTextAtTopOfScreen: method

- (void)drawRect:(CGRect)rect
{
    
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    //Call function to draw rectangle
    [self drawRectangleAtTopOfScreen:context];
    

    //Call function to draw circle
    [self drawEllipse:context];
    
    
    //Call function to draw triangle
    [self drawTriangle:context];
    
    //Call function to draw arc
    [self drawArc:context];
    
    //Call function to draw text
    [self drawTextAtTopOfScreen:context];
    }

Build and run your project. You should see some new text with very nice typography, as shown in Figure 12-8.

9781430259596_Fig12-08.jpg

Figure 12-8. Custom view with custom font

Recipe 12-4: Adding Shadows

Shadows can make graphical elements really pop. They provide a separation between graphics in a more stylish way than with a border or a stroke. In this recipe, we will add shadows to some of the components we’ve already created in the preceding recipes of this chapter.

Shadows are actually quite easy to implement because they can be drawn with one line of code. As you can see from the screen shot in Figure 12-9, we’ll need to pass in a few parameters to get this done.

9781430259596_Fig12-09.jpg

Figure 12-9. Setting a context shadow

The first parameter should be familiar by now; in our example, this is simply the context we are currently working with.  CGSize offset is the offset the shadow will have from the object. To set an offset, you can either pass in some CGSizeMake parameters, or if you want to have the shadow without an offset you can call a constant CGSizeZero, which effectively creates a CGSizeMake(0, 0). To see the difference, following are a couple of screen shots (Figures 12-10 and 12-11), one with a zero offset and one with CGSizeMake(10.0f, 15.0f) applied to the circle in our example.

9781430259596_Fig12-10.jpg

Figure 12-10. Shadow with CGSizeZero

9781430259596_Fig12-11.jpg

Figure 12-11. Shadow with CGSizeMake(10.0f, 15.0f):

The other parameters needed are blur amount and color. By using the CGSizeZero option with this and changing the color to a light color, you can create a glow effect as well. In the following code, we modify two of our methods to apply a shadow to the text and a white circle, as shown in Listing 12-7.

Listing 12-7.  Modifying the drawEllipse: and drawTextAtTopOfScreen: methods to include shadows

-(void)drawEllipse:(CGContextRef)context
{
    
    CGContextSaveGState(context);
    
    //Set color of current context
    [[UIColor whiteColor] set];
    
    //Set shadow and color of shadow
    CGContextSetShadowWithColor(context, CGSizeZero, 10.0f, [[UIColor blackColor] CGColor]);
    
    //Draw ellipse <- I know we’re drawing a circle, but a circle is just a special ellipse.
    CGRect ellipseRect = CGRectMake(60.0f, 150.0f, 200.0f, 200.0f);
    CGContextFillEllipseInRect(context, ellipseRect);
    CGContextRestoreGState(context);
    
}

-(void)drawTextAtTopOfScreen:(CGContextRef)context
{
    
    CGContextSaveGState(context);
    
    //Set color of current context
    [[UIColor colorWithRed:0.80f
                     green:0.85f
                      blue:0.95f
                     alpha:1.0f] set];
    
    //Set shadow and color of shadow
    CGContextSetShadowWithColor(context, CGSizeZero, 10.0f, [[UIColor blackColor] CGColor]);
    
    //Set font
    UIFont *customFont = [UIFont fontWithName:@"OleoScriptSwashCaps-Bold" size:40.0f ];
    NSString *titleText = @"iOS Recipes!";
    
    //Draw text on screen
    [titleText drawAtPoint:CGPointMake(55,5)
                  withFont:customFont];
    CGContextRestoreGState(context);
    
}

That’s it! Creating a simple shadow is actually pretty easy. We encourage you to vary the blur size and the colors. The view should look like Figure 12-12 when finished.

9781430259596_Fig12-12.jpg

Figure 12-12. Custom view with a shadow applied to circle and text

Recipe 12-5: Creating Gradients

Gradients are another nice way to add good eye candy to an otherwise boring view. This recipe shows you what you need in order to create a gradient, which creates nice transitions from one color to the next.

Of all the graphical elements, gradients are a little tricky because they require quite a bit of input. You have to make several decisions, such as whether to make a gradient vertical or horizontal or whether to make a gradient circular or linear. In this recipe, we will focus on creating a vertical, linear gradient.

To create a gradient you’ll need to perform the following steps:

  1. Define start and end colors.
  2. Set up a color space and a gradient space.
  3. Define the gradient direction.
  4. Create and draw the gradient.

Let’s follow these steps to create yet another method to handle creating a gradient. Add the code in Listing 12-8 to your GraphicsRecipesView.m file.

Listing 12-8.  Adding a drawGradient: method to the GraphicsRecipesView.m file

-(void)drawGradient:(CGContextRef)context{
    
    //Define start and end colors
    CGFloat colors [8] = {
        0.0, 0.0, 1.0, 1.0, // Blue
        0.0, 1.0, 0.0, 1.0 }; //Green
    
    //Setup a color space and gradient space
    CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB();
    CGGradientRef gradient = CGGradientCreateWithColorComponents(baseSpace, colors, NULL, 2);
    
    //Define the gradient direction
    CGPoint startPoint = CGPointMake(160.0f,100.0f);
    CGPoint endPoint = CGPointMake(160.0f, 360.0f);
    
    //Create and Draw the gradient
    CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
}

Now add the call for this method to the drawRect: method, as shown in Listing 12-9.

Listing 12-9.  Adding the drawGradient: method call to the drawRect: method

- (void)drawRect:(CGRect)rect
{
    
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    //Call function to draw rectangle
    [self drawRectangleAtTopOfScreen:context];
    
    /*
    //Set shadow and color of shadow
    CGContextSetShadowWithColor(context, CGSizeZero, 10.0f, [[UIColor blackColor] CGColor]);
     */
    
    //Call function to draw circle
    [self drawEllipse:context];
    
    //Call function to draw triangle
    [self drawTriangle:context];
    
    //Call function to draw arc
    [self drawArc:context];
    
    //Call function to draw text
    [self drawTextAtTopOfScreen:context];

    //draw gradient
    [self drawGradient:context];
  
}

Build and run the application. You should end up with the result in Figure 12-13.

9781430259596_Fig12-13.jpg

Figure 12-13. Custom view with a gradient applied

Of course, this covers up all the other elements we added in the preceding recipes. Fear not, though, because we’ll be addressing this in the next recipe.

Recipe 12-6: Clipping a Drawing to a Mask

At the end of the preceding recipe, we were left with the gradient covering the other drawings from the earlier recipes in this chapter. It would be better if we could clip the gradient to a small portion so we can see most of what is behind the gradient. Clipping to a mask is a lot like creating a window in which we can see a portion of what is drawn behind that window. The mask in this case is the shape of the window, which we will then clip everything around. We have the option of making the mask any shape we want, but in this example we’ll create a small circle by creating a new method called drawEllipseWithGradient and calling it from our drawRect: method. This will give us a result where the gradient is completely contained within the circle.

Creating a mask from a drawing, or using a drawing to create a window, will require the following steps:

  1. Create an Image context.
  2. Create a new graphics context.
  3. Translate and flip the context upside-down to compensate for Quartz’s inverted coordinate system.
  4. Draw the shapes we want to mask with.
  5. Create a bitmap from our new image context drawing.
  6. Use that bitmap to start masking.
  7. Draw anything you want to mask, in this case a gradient.

We’ll start first by created a new method, drawEllipseWithGradient, which takes a CGContextRef as an input and uses the steps we listed to create a circle with a gradient in it. Listing 12-10 shows the implementation of this method.

Listing 12-10 does exactly what we said we would do: we’re creating a circle that is 100 points high by 100 points wide and is located at the center of the large white circle from Recipe 12-1. The reason we have to translate and scale the image is because Quartz uses a coordinate system that references the lower-left corner of the screen instead of the top left. This obviously causes problems when it comes to mixing coordinate systems. If the user switches from a 3.5” screen to a 4” screen, the vertical position of the circle would move downward. To fix this, we translate and scale so the coordinate systems match.

Listing 12-10.  Implementation of the drawEllipseWithGradient: method

-(void)drawEllipseWithGradient:(CGContextRef)context{
    
    CGContextSaveGState(context);

    //UIGraphicsBeginImageContextWith(self.frame.size);
    UIGraphicsBeginImageContextWithOptions((self.frame.size), NO, 0.0);
    
    CGContextRef newContext = UIGraphicsGetCurrentContext();

    // Translate and scale image to compensate for Quartz's inverted coordinate system
    CGContextTranslateCTM(newContext,0.0,self.frame.size.height);
    CGContextScaleCTM(newContext, 1.0, -1.0);
    
    //Set color of current context
    [[UIColor blackColor] set];
    
    //Draw ellipse <- I know we’re drawing a circle, but a circle is just a special ellipse.
    CGRect ellipseRect = CGRectMake(110.0f, 200.0f, 100.0f, 100.0f);
    CGContextFillEllipseInRect(newContext, ellipseRect);
    
    CGImageRef mask = CGBitmapContextCreateImage(UIGraphicsGetCurrentContext());
    UIGraphicsEndImageContext();
    
    CGContextClipToMask(context, self.bounds, mask);

    [self drawGradient:context];
    
    CGContextRestoreGState(context);
    
}

As usual, we want to call the new method created in Listing 12-10 from the drawRect: method, as shown in Listing 12-11. You might notice that we removed the previous method call to drawGradient and replaced it with the new function call drawEllipseWithGradient. This is because we are now calling the drawGradient: method from within the drawEllipseWithGradient method.

Listing 12-11.  Removing the drawGradient: method call and replacing it with the drawEllipseWithGradient: call

- (void)drawRect:(CGRect)rect
{
    
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    //Call function to draw rectangle
    [self drawRectangleAtTopOfScreen:context];
    
    //Call function to draw circle
    [self drawEllipse:context];
    
    //Call function to draw triangle
    [self drawTriangle:context];
    
    //Call function to draw arc
    [self drawArc:context];
    
    //Call function to draw text
    [self drawTextAtTopOfScreen:context];
    
    //Call function to draw ellipse filled with a gradient
    [self drawEllipseWithGradient:context];
      
}

If you build and run your application, you should be presented with a nice circular gradient in the middle of the larger circle.

9781430259596_Fig12-14.jpg

Figure 12-14. A completed custom view with a gradient circle

Recipe 12-7: Programming Screen Shots

Just as you can put drawings and images into a graphics context, you also can easily take them out of a context. You can do this by making use of the UIGraphicsGetImageFromCurrentImageContext() function. This allows you to create a copy from the current collection of drawings and copy them into an image object. In Recipes 12-1 through 12-6, we created several drawings in the context. This recipe will make use of this collection of drawings.

We will build on the preceding recipes of this chapter and add a feature that takes a snapshot of the current view whenever the user shakes the device. The feature will display the snapshot in the lower-right corner of the screen, causing a nice, double-mirror effect on subsequent shakes.

Start by adding a property to the GraphicsRecipesView class to hold the latest snapshot. Open GraphicsRecipesView.h and add the declaration, as shown in Listing 12-12.

Listing 12-12.  Creating a UIImage property in the GraphicsRecipesView.h file

//
//  GraphicsRecipesView.h
//  Recipe 12-7 Programming Screenshots
//

#import <UIKit/UIKit.h>

@interface GraphicsRecipesView : UIView

@property (strong, nonatomic)UIImage *image;

@end

Next, select the Main.storyboard file to edit the view controller using Interface Builder. Open the assistant editor and create an outlet for the custom view by Ctrl-dragging a blue line from it onto the ViewController.h file. Name the outlet “myView.” For the outlet declaration to compile, you’ll need to import GraphicsRecipesView.h, as shown in Listing 12-13.

Listing 12-13.  Including the import statement in the ViewController.h file

//
//  ViewController.h
//  Recipe 12-7 Programming Screenshots
//

#import <UIKit/UIKit.h>
#import "GraphicsRecipesView.h"

@interface ViewController : UIViewController

@property (weak, nonatomic) IBOutlet GraphicsRecipesView *myView;

@end

Now, add the code that will draw the snapshot to the drawRect: method in GraphicsRecipesView.m, as shown in Listing 12-14.

Listing 12-14.  Adding code to draw the snapshot in the drawRect: method

- (void)drawRect:(CGRect)rect
{
    // ...

    if (self.image)
    {
        CGFloat imageWidth = self.frame.size.width / 2;
        CGFloat imageHeight = self.frame.size.height / 2;
        CGRect imageRect = CGRectMake(imageWidth, imageHeight, imageWidth, imageHeight);
        [self.image drawInRect:imageRect];
    }
}

@end

As the next step, add the methods in Listing 12-15 to the view controller. These methods implement shake recognition that triggers the screen shot.

Listing 12-15.  Adding methods to ViewController.m to handle shake recognition

//
//  ViewController.m
//  Recipe 12-7 Programming Screenshots
//

#import "ViewController.h"

@implementation ViewController

// ...

- (BOOL) canBecomeFirstResponder
{
    return YES;
}

- (void) viewWillAppear: (BOOL)animated
{
    [self.view becomeFirstResponder];
    [super viewWillAppear:animated];
}

- (void) viewWillDisappear: (BOOL)animated
{
    [self.view resignFirstResponder];
    [super viewWillDisappear:animated];
}

- (void) motionEnded: (UIEventSubtype)motion withEvent: (UIEvent *)event
{
    if (event.subtype == UIEventSubtypeMotionShake)
    {
        // Device was shaken

        // TODO: Take a screen shot
    }
}

@end

You will need to import the QuartzCore framework. Failing to do so causes a compiler error later when you access a layer on the view to draw the screen shot. So, open ViewController.h again and add the code shown in Listing 12-16.

Listing 12-16.  Importing the QuartzCore framework

//
//  ViewController.h
//  Recipe 12-7 Programming Screenshots
//

#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#import "GraphicsRecipesView.h"

@interface ViewController : UIViewController

@property (weak, nonatomic) IBOutlet MyView *myView;

@end

Finally, implement the code for taking a snapshot and setting it to the image view, as shown in Listing 12-17. Take note that the setNeedsDisplay method in the UIView class instructs it to re-call its drawRect: method to incorporate any recent changes.

Listing 12-17.  Implementing the motionEnded: withEvent: method

- (void) motionEnded: (UIEventSubtype)motion withEvent: (UIEvent *)event
{
    if (event.subtype == UIEventSubtypeMotionShake)
    {
        // Device was shaken
        
        // Acquire image of current layer
        UIGraphicsBeginImageContext(self.view.bounds.size);
        CGContextRef context = UIGraphicsGetCurrentContext();
        [self.view.layer renderInContext:context];
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        
        self.myView.image = image;
        [self.myView setNeedsDisplay];
    }
}

After testing the application again, you should see a screen similar to Figure 12-15 when you shake the device a couple of times.

9781430259596_Fig12-15.jpg

Figure 12-15. An application showing a screen shot of a screen that was displaying a screen shot already

Note   If you run the app in the iOS simulator, you can simulate a shake by pressing Ctrl + Cmd + Z.

Summary

Throughout this chapter, you have learned most of the ways you can create shapes, gradients, and fonts using Core Graphics. It doesn’t take a stretch of the imagination to see how these techniques could be useful when creating custom UI controls or to dynamically generate other types of feedback. In this chapter, we have only touched the surface of what is possible with Core Graphics. It’s up to you as a developer to use this instruction as a foundation for creating awesome custom interfaces.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset