iOS SDK: UIAlertView Custom Graphics

iOS SDK: UIAlertView Custom Graphics

Tutorial Details
  • Technology: iOS SDK
  • Difficulty: Intermediate
  • Completion Time: 30 - 45 Minutes

The UIAlertView class is easy to implement, but unless you get under the hood and tinker around it will look the same each time. This Quick Tip shows you how to create custom graphics programmatically and apply them to an alert. Changing the graphics in your app may be just the ticket for a fresh and engaging design!


Step 1: Set Up the Xcode File

Launch Xcode and create a new project by clicking File > New > New Project. We want to create an
iOS Empty Application. Name your product "Custom Alert" and enter a Company Identifier of your choosing. Select "iPhone" from the "Device Family" menu and remove any checks in front of "Use Core Data," "Use Automatic Reference Counting," and "Include Unit Tests." Click "Next" and choose a location to save your project. Make sure the box next to "Source Control" is not checked and click "Create" to make your project.


Step 2: Create a UIAlertView

We first want to subclass UIAlertView. Click File > New > New File to create a new subclass. Choose an iOS Cocoa Touch Objective-C class and click "Next." Name your class "CustomAlertView." and choose UIView from the "Subclass of" menu. Before clicking "Next.", choose the "CustomAlert" folder from the "Groups" menu and make sure the "CustomAlert" is checked in the "Targets" box. Click "Create" to add the new subclass.

The newly-created class is a UIView class, but we need it to inherit from UIAlertView. Click on the "CustomAlertView.h" file and change the inheritance to UIAlertView.

#import <UIKit/UIKit.h>
@interface CustomAlertView : UIAlertView
@end

In the AppDelegate header file, import the "CustomAlertView.h" file and conform to the UIAlertViewDelegate protocol.

#import <UIKit/UIKit.h>
#import "CustomAlertView.h"
@interface AppDelegate : UIResponder <UIApplicationDelegate, UIAlertViewDelegate>
@property (strong, nonatomic) UIWindow *window;
@end

Navigate to the AppDelegate implementation file and include the instance method alertView:clickedButtonAtIndex:.

The buttonIndex is used to determine which button was pressed.

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (buttonIndex == 0) {
        NSLog(@"THE 'NO' BUTTON WAS PRESSED");
    }
    if (buttonIndex == 1) {
        NSLog(@"THE 'YES' BUTTON WAS PRESSED");
    }
}

Now, insert the following code to instantiate the alert object in the application:didFinishLaunchingWithOptions: method.

Note that the delegate is set to self in the init method in order to receive the callback when a button is pressed.

    CustomAlertView *customAlertView = [[CustomAlertView alloc] initWithTitle:@"Custom Alert View"
                                                                message:@"Customize the look of your app's alert view programmatically."
                                                                delegate:self
                                                   cancelButtonTitle:@"NO"
                                                   otherButtonTitles:@"YES",nil];
    [customAlertView show];
    [customAlertView release];

If you build and run the app right now, you will get the typical graphics of a standard UIAlertView.


Step 3: Customize the Graphics

Let’s remove the standard graphics and customize the UIAlertView.

Overriding layoutSubviews:

First, we need to override UIView's layoutSubviews: method in the "CustomAlertView.m" file where we will hide the blue background and find the labels to change the appearance of the text.

- (void)layoutSubviews
{
    for (UIView *subview in self.subviews){ //Fast Enumeration
        if ([subview isMemberOfClass:[UIImageView class]]) {
            subview.hidden = YES; //Hide UIImageView Containing Blue Background
        }
        if ([subview isMemberOfClass:[UILabel class]]) { //Point to UILabels To Change Text
            UILabel *label = (UILabel*)subview;	//Cast From UIView to UILabel
            label.textColor = [UIColor colorWithRed:210.0f/255.0f green:210.0f/255.0f blue:210.0f/255.0f alpha:1.0f];
            label.shadowColor = [UIColor blackColor];
            label.shadowOffset = CGSizeMake(0.0f, 1.0f);
        }
    }
}

Because layoutSubviews: is called after the object is initialized, but before the object is drawn, you have the opportunity to add code that will provide additional customization before it appears on the screen. In the above code, we use fast enumeration to iterate through the array of existing subviews of the alert looking for the UIImageView, which holds the background and two UILabels which hold the text. By using the convenience method isMemberOfClass:, a Boolean value of true or false is returned depending on whether the subview is an instance of the specified class. When the classes are located, the UIImageView is hidden and the UILabels' text colors, shadow colors, and shadow offsets are set.

Overriding drawRect

Importing pre-made vector graphics can be problematic. Between changing screen resolutions and organizing files, importing images tends to be more work than it is worth in some cases. If you are looking for flexible, pixel-perfect graphics, overriding the view’s drawRect: method is usually the best solution.

The graphics for the UIAlertView created by the following code provide a subtle difference in color, shadow, and texture, but keep the same general shape of a standard alert. Insert the sections of code explained in this tutorial into the drawRect: method in a subclass of UIAlertView.

Get a Reference to the Current Graphics Context

CGContextRef context = UIGraphicsGetCurrentContext();

The UIGraphicsGetCurrentContext() function returns a reference to the current view's graphics context, allowing you to make changes to its state.

Create a Base Shape

CGRect activeBounds = self.bounds;
CGFloat cornerRadius = 10.0f;
CGFloat inset = 6.5f;
CGFloat originX = activeBounds.origin.x + inset;
CGFloat originY = activeBounds.origin.y + inset;
CGFloat width = activeBounds.size.width - (inset*2.0f);
CGFloat height = activeBounds.size.height - (inset*2.0f);
CGRect bPathFrame = CGRectMake(originX, originY, width, height);
CGPathRef path = [UIBezierPath bezierPathWithRoundedRect:bPathFrame cornerRadius:cornerRadius].CGPath;

We start by creating a basic shape with rounded corners. Notice that the parameters for the shape are based on the current bounds of the UIAlertView. This allows the shape to be drawn in a consistent manner with the alert's bounds, compensating for any changes that occur. Two important components when creating this shape are cornerRadius and inset. The cornerRadius determines the roundness of the corners, while the inset defines how far inside the bounds the shape is drawn. Finally, we create the shape with a UIBezierPath constructed from the convenience method bezierPathWithRoundedRect: cornerRadius:.

Accessing the CGPath property of the newly created UIBezierPath object allows it to be used in the context.

Add a Fill and Outer Drop Shadow

CGContextAddPath(context, path);
CGContextSetFillColorWithColor(context, [UIColor colorWithRed:210.0f/255.0f green:210.0f/255.0f blue:210.0f/255.0f alpha:1.0f].CGColor);
CGContextSetShadowWithColor(context, CGSizeMake(0.0f, 1.0f), 6.0f, [UIColor colorWithRed:0.0f/255.0f green:0.0f/255.0f blue:0.0f/255.0f alpha:1.0f].CGColor);
CGContextDrawPath(context, kCGPathFill);

When drawing in Core Graphics, think in terms of layering pieces on top of each other. In this project, the first layer consists of drop shadows because they are the outermost and bottom layer. We set the parameters of the first graphics state, specifically its shape, fill color, and shadow color. Then we draw the context using CGContextDrawPath() and the CGPathDrawingMode kCGPathFill which will essentially create the exterior drop shadow.

Clip the Context

CGContextSaveGState(context); //Save Context State Before Clipping To "path"
CGContextAddPath(context, path);
CGContextClip(context);

Now we want to draw inside the base shape. In order to accomplish this, we need to clip the context to the shape prior to drawing the next state. Anything drawn afterward but before we call CGContextRestoreGState() stays inside the clipping area and doesn't cover the exterior drop shadow.

Draw a Gradient

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
size_t count = 3;
CGFloat locations[3] = {0.0f, 0.57f, 1.0f};
CGFloat components[12] =
    {
        70.0f/255.0f, 70.0f/255.0f, 70.0f/255.0f, 1.0f,     //1
        55.0f/255.0f, 55.0f/255.0f, 55.0f/255.0f, 1.0f,     //2
        40.0f/255.0f, 40.0f/255.0f, 40.0f/255.0f, 1.0f      //3
    };
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, count);
CGPoint startPoint = CGPointMake(activeBounds.size.width * 0.5f, 0.0f);
CGPoint endPoint = CGPointMake(activeBounds.size.width * 0.5f, activeBounds.size.height);
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
CGColorSpaceRelease(colorSpace);
CGGradientRelease(gradient);

Next, we add a subtle gradient to the background. In order to draw a gradient, we will need a gradient reference which specifies the colors and their locations within the gradient. The startPoint and endPoint of the gradient are based on the bounds of the alert view, covering the entire alert without going outside of the clipping area. Note that we've released the CGColorSpaceRef and CGGradientRef now that we are done with them. In general, when you see a Core Graphics function call with the word &quotcreate&quot in it, you'll need to release the reference when you are finished.

Create a Hatched Background

CGFloat buttonOffset = 92.5f; //Offset buttonOffset by half point for crisp lines
CGContextSaveGState(context); //Save Context State Before Clipping "hatchPath"
CGRect hatchFrame = CGRectMake(0.0f, buttonOffset, activeBounds.size.width, (activeBounds.size.height - buttonOffset+1.0f));
CGContextClipToRect(context, hatchFrame);
CGFloat spacer = 4.0f;
int rows = (activeBounds.size.width + activeBounds.size.height/spacer);
CGFloat padding = 0.0f;
CGMutablePathRef hatchPath = CGPathCreateMutable();
for(int i=1; i<=rows; i++) {
    CGPathMoveToPoint(hatchPath, NULL, spacer * i, padding);
    CGPathAddLineToPoint(hatchPath, NULL, padding, spacer * i);
}
CGContextAddPath(context, hatchPath);
CGPathRelease(hatchPath);
CGContextSetLineWidth(context, 1.0f);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetStrokeColorWithColor(context, [UIColor colorWithRed:0.0f/255.0f green:0.0f/255.0f blue:0.0f/255.0f alpha:0.15f].CGColor);
CGContextDrawPath(context, kCGPathStroke);
CGContextRestoreGState(context); //Restore Last Context State Before Clipping "hatchPath"

The next bit of code deals with the hatched diagonal lines behind the buttons. In order to get the lines to draw only underneath the buttons we need to create a new rectangle shape, clip the context to the rectangle, and add the hatched lines to the context. The buttonOffset is the height of the space for the hatched lines to be drawn. The moving pattern created by the for loop is based on the alert view's bounds. To change the appearance of the hatched lines simply alter the spacer CGContextSetLineWidth(), or the hatchFrame values.

Draw a Separating Line

CGMutablePathRef linePath = CGPathCreateMutable();
CGFloat linePathY = (buttonOffset - 1.0f);
CGPathMoveToPoint(linePath, NULL, 0.0f, linePathY);
CGPathAddLineToPoint(linePath, NULL, activeBounds.size.width, linePathY);
CGContextAddPath(context, linePath);
CGPathRelease(linePath);
CGContextSetLineWidth(context, 1.0f);
CGContextSaveGState(context); //Save Context State Before Drawing "linePath" Shadow
CGContextSetStrokeColorWithColor(context, [UIColor colorWithRed:0.0f/255.0f green:0.0f/255.0f blue:0.0f/255.0f alpha:0.6f].CGColor);
CGContextSetShadowWithColor(context, CGSizeMake(0.0f, 1.0f), 0.0f, [UIColor colorWithRed:255.0f/255.0f green:255.0f/255.0f blue:255.0f/255.0f alpha:0.2f].CGColor);
CGContextDrawPath(context, kCGPathStroke);
CGContextRestoreGState(context); //Restore Context State After Drawing "linePath" Shadow

Now, we want to draw a separating line between the buttons and the labels. Creating the line using CGPathCreateMutable draws it using the specified point locations. The line's location within the alert view is based on the buttonOffset and spans its width. Once again, we use a function call containing the word "create," CGPathCreateMutable(). Therefore, we release the CGMutablePathRef one after we're done.

Create an Inner Shadow

CGContextAddPath(context, path);
CGContextSetLineWidth(context, 3.0f);
CGContextSetStrokeColorWithColor(context, [UIColor colorWithRed:210.0f/255.0f green:210.0f/255.0f blue:210.0f/255.0f alpha:1.0f].CGColor);
CGContextSetShadowWithColor(context, CGSizeMake(0.0f, 0.0f), 6.0f, [UIColor colorWithRed:0.0f/255.0f green:0.0f/255.0f blue:0.0f/255.0f alpha:1.0f].CGColor);
CGContextDrawPath(context, kCGPathStroke);

To create the appearance of a raised border around the alert view, we draw the original UIBezierPath with a shadow. The path will continue to be clipped by the first call to clip the context which results in the shadow appearing only on the inside of the stroked line. To change the size of the inner shadow, set the blur parameter inside CGContextSetShadowWidthColor to a different number.

Redraw the Path to Avoid Pixilation

CGContextRestoreGState(context); //Restore First Context State Before Clipping "path"
CGContextAddPath(context, path);
CGContextSetLineWidth(context, 3.0f);
CGContextSetStrokeColorWithColor(context, [UIColor colorWithRed:210.0f/255.0f green:210.0f/255.0f blue:210.0f/255.0f alpha:1.0f].CGColor);
CGContextSetShadowWithColor(context, CGSizeMake(0.0f, 0.0f), 0.0f, [UIColor colorWithRed:0.0f/255.0f green:0.0f/255.0f blue:0.0f/255.0f alpha:0.1f].CGColor);
CGContextDrawPath(context, kCGPathStroke);

Rounded corners have a tendency to appear pixilized once they have been clipped, so our last bit of code redraws the outer line to ensure it is crisp and clean. Before we redraw the outer line, we call CGContextRestoreGState() which releases our first clipping area, so we can draw on top of the original line.


Step 4: Test the UIAlertView

Run the app on the simulator or on your device to view the UIAlertView and its custom graphics.


Conclusion

Creating graphics using Core Graphics is about subtlety and layering of effects. There are plenty of graphical elements that can be adjusted when customizing UIAlertView , and this tutorial only covers a small portion of them. But the point is that these elements can all be adjusted and created programmatically to enhance the user's experience.

Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • Kirby

    Where’s the source code?

    • http://github.com/iosdeveloper iosdeveloper

      The author hasn’t added any source code so I did. You can find a zip archive at http://github.com/iosdeveloper/CustomAlert/zipball/master .

      • Sreenidhi

        how can i add an back ground image to my Alert box by using this code?
        i’m using above code but there is no method for adding Back ground image for an alert box..
        please help me to get this…

  • http://www.habanerostudio.mx Dario Gutierrez

    Cool, looks really nice great feature!! :D

  • http://yasiradnan.com/ Yasir Adnan

    Nice Tutorial.Thanks for share:-D

  • Oren Fridman

    v. good :)

  • Alex

    Very cool tutorial :) Thanks,

    What’s the best way to change the colour of the alert.

    I’ve been amending

    CGFloat components[12] =
    {
    70.0f/255.0f, 70.0f/255.0f, 70.0f/255.0f, 1.0f, //1
    55.0f/255.0f, 55.0f/255.0f, 55.0f/255.0f, 1.0f, //2
    40.0f/255.0f, 40.0f/255.0f, 40.0f/255.0f, 1.0f //3
    };

    but haven’t been getting the desired results.

    • http://www.tapdezign.com Aaron Crabtree
      Author

      Thanks!

      A good way to get started with colors is to go into Adobe Illustrator, find a set of colors for your gradient, and copy their RGB values into the code by putting the red value in the first location, the green value in the second location, the blue value in the third location, and adjusting the alpha percentage in the final location (the only value that changes is the number before the /255.0f at each color location). For example, if you wanted to change the second color stop to green, you could use

      0.0f/255.0f, 255.0f/255.0f, 0.0f/255.0f, 1.0f, //2

      where there is no red, full green, no blue and 1.0f (100%) alpha.

      Here’s an example of a nav bar gradient with 8 color stops and a height of 44pt:

      size_t numLocations = 8;
      CGFloat locations[8] = {0.0f, 0.015f, 0.53f, 0.96f, 0.96f, 0.98f, 0.98f, 1.0f};
      CGFloat components[32] =
      { 255.0f/255.0f, 255.0f/255.0f, 255.0f/255.0f, 1.0f, //1
      255.0f/255.0f, 255.0f/255.0f, 255.0f/255.0f, 1.0f, //2
      240.0f/255.0f, 240.0f/255.0f, 240.0f/255.0f, 1.0f, //3
      230.0f/255.0f, 230.0f/255.0f, 230.0f/255.0f, 1.0f, //4
      240.0f/255.0f, 240.0f/255.0f, 240.0f/255.0f, 1.0f, //5
      240.0f/255.0f, 240.0f/255.0f, 240.0f/255.0f, 1.0f, //6
      170.0f/255.0f, 170.0f/255.0f, 170.0f/255.0f, 1.0f, //7
      170.0f/255.0f, 170.0f/255.0f, 170.0f/255.0f, 1.0f}; //8

  • Jeremy Bone

    Great tutorial! One small problem I found with the code is that the hatched background and separating line don’t resize appropriately if the amount of lines in your popup message are different than in the example. I made a few small changes to accommodate for this.

    Change buttonOffset to the following:

    CGFloat buttonOffset = 68.5f; //Offset buttonOffset by half point for crisp lines”

    hatchFrame to:

    CGRect hatchFrame = CGRectMake(0.0f, activeBounds.size.height – buttonOffset + 1.0f, activeBounds.size.width, buttonOffset);

    and linePathY to:

    CGFloat linePathY = (activeBounds.size.height – buttonOffset – 1.0f);

    • http://www.magnanimoussoftware.com Devang

      Spot On dude! … :) … Thanks a lot!

    • http://www.stevejabs.com Steve Jabs

      I actually accomplished the same thing by just changing one line:

      CGFloat buttonOffset = activeBounds.size.height – 66.5f;

  • Daniel

    Hi,

    I’m trying to implement this into my application, but my problem is, that the text is more than three lines, and it is messing up everything.

    It seems to me, that the alert text has a fixed height, and the gray button background is going up.

    I tried to do the following:
    if ([subview isMemberOfClass:[UILabel class]]) { //Point to UILabels To Change Text
    UILabel *label = (UILabel*)subview; //Cast From UIView to UILabel
    label.textColor = [UIColor colorWithRed:210.0f/255.0f green:210.0f/255.0f blue:210.0f/255.0f alpha:1.0f];
    label.shadowColor = [UIColor blackColor];
    label.shadowOffset = CGSizeMake(0.0f, 1.0f);
    label.lineBreakMode = UILineBreakModeWordWrap;
    label.numberOfLines = 4;
    }

    But it is still not working.

    Does anyone know any solution?

    Thanks!

  • Fadi

    Hi

    Just a question, will this go through Apple’s review process? Will they accept such internal alteration UIAlertView? I remember reading somewhere that iterating subviews of built in views is not allowed. Please correct me if I am wrong.

    Also there is a great tool called Opacity (http://likethought.com/opacity/) which can be very useful in designing and creating the Core Graphics code for you if you do not want to bother with its nitty gritty. Just generate the code and place it in drawRect above.

  • crazy

    It works only in the appdelegate when I call customalert in other viewcontroller, it works in simulator, but not works in the device, any helps…

  • Elvis

    Beautiful.

  • gowtham

    Hi my custom alert view class is like this. It works fine but when alertview is called twice, the background image does not works. Can anyone help ?

    #import “Custom_Alert.h”

    @implementation Custom_Alert

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

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

    - (void)show
    {
    [super show];

    for (UIView *subview in self.subviews)
    { //Fast Enumeration

    if ([subview isKindOfClass:[UIImageView class]])
    { //Find UIImageView Containing Blue Background
    //subview.hidden = YES; //Hide UIImageView Containing Blue Background

    UIImage *bg=[UIImage imageNamed:@"alert_bg.png"];
    ((UIImageView*)subview).image=bg;
    ((UIImageView*)subview).autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;

    }

    if([subview isKindOfClass:[UIButton class]])
    {

    [(UIButton*)subview setBackgroundImage:[UIImage imageNamed:@"bt_grey_alert.png"] forState:UIControlStateNormal];
    [(UIButton*)subview setBackgroundImage:[UIImage imageNamed:@"bt_blue_alert.png"] forState:UIControlStateHighlighted];

    }

    if ([subview isKindOfClass:[UILabel class]])
    {
    UILabel *label = (UILabel*)subview;

    if ([label.text isEqualToString:self.title])
    {

    label.textColor=[UIColor whiteColor];
    }
    else
    {
    label.textColor=[UIColor whiteColor];
    label.shadowColor = [UIColor blackColor];
    label.shadowOffset = CGSizeMake(0.0f, 1.0f);
    }

    }

    }

    }

    @end

  • Victor

    Great tutorial.

  • Esowes

    Hello there,

    Thanks for this great tutorial.

    The CustomAlertView works perfectly on my device…

    Just a question though: I’ve implemented a black/red hashBar, but would like to increase the button’s opacity as, right now it almost seems (from a user’s interface issue point of view) as if the button is not clickable… Where is this accessible ? (If at all…)

    Thanks

  • alex

    Can some tell me how to put a background image in this one . or any image at a particular frame

  • Artmobile

    It’s a very nice article but unfortunately sublassing UIAlerView may lead to rejection by Apple. The below comes from the UIAlertView class reference:

    http://developer.apple.com/library/ios/#documentation/uikit/reference/UIAlertView_Class/UIAlertView/UIAlertView.html

    “The UIAlertView class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified.”

    Other than that, very nice article

  • Sreenidhi

    how can i set back ground image for the alert box by using this code….
    u didn’t post regarding to that..
    anyone can help me please..