The UIActionSheet is a great tool to easily let users select between multiple options. I use it all the time when developing apps. Just like all great and easy to use components, designers like to customize them and make developer’s lives harder than they have to be. In this tutorial I will show you how to make an action sheet that is easy to customize and allow you to control every aspect of its look and feel. If you would like to follow along the project is on git hub. Also before we get started, if you would like to follow me on twitter I am always looking for iOS developer friends!

Follow @BarrettBreshear



Download this sample project on git hub

Quick! To the documentation

If you head over to Apple’s documentation we will quickly see that the UIActionSheet isn’t supposed to be subclassed.

UIActionSheet is not designed to be subclassed, nor should you add views to its hierarchy. If you need to present a sheet with more customization than provided by the UIActionSheet API, you can create your own and present it modally with presentViewController:animated:completion:.

So what will we do? We will create a UIView that looks and acts like an UIActionSheet. To get started lets create the class in Xcode. Select new objective-c class and name it CustomActionSheet and set it’s subclass to UIView. Next, lets also create a custom button that the action sheet will use. Select new objective-c class and name it ActionButton and make it a subclass of UIButton.

Action Button

Before we get started on the action sheet view lets finish up the button that the view will use. You don’t have to do this and could just insert a UIButton, but I like to subclass things like buttons because they will most likely be used all over the app. Here is what the ActionButton class looks like.

ActionButton.h

// // ActionButton.h // CustomUIActionSheet // // Created by Barrett Breshears on 8/8/14. // Copyright (c) 2014 Barrett Breshears. All rights reserved. // #import @interface ActionButton : UIButton // class method that will be used when allocating button + (ActionButton *)buttonWithText:(NSString *)text cancel:(BOOL)cancel; // instance method to set the button label and let the button know if it is a cancel button - (id)initWithText:(NSString *)text cancel:(BOOL)cancel; @property (nonatomic, retain) UILabel *label; @end

ActionButton.m

// // ActionButton.m // CustomUIActionSheet // // Created by Barrett Breshears on 8/8/14. // Copyright (c) 2014 Barrett Breshears. All rights reserved. // #import "ActionButton.h" @implementation ActionButton + (ActionButton *)buttonWithText:(NSString *)text cancel:(BOOL)cancel { // return the initialized button return [[self alloc] initWithText:text cancel:(BOOL)cancel]; } - (id)initWithText:(NSString *)text cancel:(BOOL)cancel { // initialize self = [super init]; if (self != nil) { // we are only using a single image for the demo, but this project is set up for // an image with a highlighted state UIImage *mainImage; UIImage *tappedImage; // check if the image is a cancel button, if it is we will use a special image if (cancel) { mainImage = [UIImage imageNamed:@"red_button.png"]; tappedImage = [UIImage imageNamed:@"red_button.png"]; } else { // otherwise the default button image will be a green button mainImage = [UIImage imageNamed:@"green_button.png"]; tappedImage = [UIImage imageNamed:@"green_button.png"]; } // create the buttons frame based on the image size CGRect frame; frame.size = mainImage.size; self.frame = frame; // set button images [self setImage:mainImage forState:UIControlStateNormal]; [self setImage:tappedImage forState:UIControlStateHighlighted]; // set up button label _label = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)]; _label.backgroundColor = [UIColor clearColor]; _label.textColor = [UIColor whiteColor]; _label.textAlignment = NSTextAlignmentCenter; _label.text = text; [self addSubview:_label]; } return self; } @end

As you can see, this is a simple button that will give us the look of both UIActionSheet buttons. This type of subclassing has always worked great for me and if I needed more variety I would change the cancel param to a button type with a string parameter with a switch or if statement to decide what image to use for the button.

CustomActionSheet Class

Now lets move on to the custom action sheet. We will create our view to behave just like an action sheet, initializing it with a title, button titles, and a cancel button title. It will also fire a custom delegate method to let the implementing object know the action sheet has been dismissed and a button was selected.

CustomActionSheet.h

The header file is nothing special, but has one little snippet of code that I always get excited to use which allows you to pass in as many strings as you need.

otherButtonTitles:(NSString *)otherButtonTitles, ... NS_REQUIRES_NIL_TERMINATION

I always get excited when I get to use this. Anyway here is the CustomAction.h file.

// // CustomActionSheet.h // CustomUIActionSheet // // Created by Barrett Breshears on 8/7/14. // Copyright (c) 2014 Barrett Breshears. All rights reserved. // #import @class CustomActionSheet; // define the custom actionview delegate @protocol CustomActionViewDelegate @optional // declare the option delegate method which passed in the alert and which button was selected - (void)modalAlertPressed:(CustomActionSheet *)alert withButtonIndex:(NSInteger)buttonIndex; @end @interface CustomActionSheet : UIView @property (assign) id delegate; @property (nonatomic, strong) UIView *backgroundView; @property (nonatomic, assign) float yPosition; @property (nonatomic, assign) int index; - (void)animateOn; - (void)animateOff; - (id)initWithTitle:(NSString *)title delegate:(id)delegate cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ... NS_REQUIRES_NIL_TERMINATION; - (void)buttonPressedWithIndex:(id)sender; - (void)showAlert; @end

CustomActionSheet.m

Here is a little overview of what we will be doing here:

–The view will need to be initialized and create the background, and all the buttons, adding and adjusting the views’ size depending on the number of buttons needed.

–Create an action method the buttons will use when they are clicked.

–We will need to create a showAlert method that adds the view to the window.

–Create methods to animate the view on and off the window.

Just a quick note I decided to call the delegate method [_delegate modalAlertPressed:self withButtonIndex:index] inside of the animateOff method instead of the – (void)buttonPressedWithIndex:(id)sender since there is a delay with animation. If you need to change this to fire immediately after the button is pressed you can stick it in the – (void)buttonPressedWithIndex:(id)sender method instead.

// // CustomActionSheet.m // CustomUIActionSheet // // Created by Barrett Breshears on 8/7/14. // Copyright (c) 2014 Barrett Breshears. All rights reserved. // #import "CustomActionSheet.h" #import "ActionButton.h" @interface CustomActionSheet () @end @implementation CustomActionSheet @synthesize backgroundView; @synthesize yPosition; @synthesize index; - (id)initWithTitle:(NSString *)title delegate:(id)delegate cancelButtonTitle:(NSString *)cancelButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ... { self = [super init]; if (self) { // set the delegate _delegate = delegate; // create a frame this will take up the full screen size self.frame = CGRectMake(0.0, 0.0, [[UIScreen mainScreen] bounds].size.width, [[UIScreen mainScreen] bounds].size.height); self.backgroundColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.7]; // set up the background view backgroundView = [[UIView alloc] init]; backgroundView.backgroundColor = [UIColor colorWithRed:7.0/255.0f green:45.0/255.0f blue:58.0/255.0 alpha:1]; backgroundView.userInteractionEnabled = YES; CGRect frame = backgroundView.frame; frame.size = CGSizeMake(320, 380); frame.origin = CGPointMake(0, self.frame.size.height); backgroundView.frame = frame; // get our button array from the otherButtonTittles parameter NSMutableArray *buttonArray = [[NSMutableArray alloc]init]; va_list args; va_start(args, otherButtonTitles); for (NSString *arg = otherButtonTitles; arg != nil; arg = va_arg(args, NSString*)) { [buttonArray addObject:arg]; } va_end(args); // this will track the current position of where the elements will be placed yPosition = 15; // check if there is a title if (title != nil) { // create a the title and position it in the view UILabel *titleField = [[UILabel alloc] init]; titleField.textColor = [UIColor whiteColor]; titleField.shadowColor = [UIColor blackColor]; [titleField setTextAlignment:NSTextAlignmentCenter]; titleField.text = title; frame = titleField.frame; frame.size.width = self.frame.size.width ; frame.size.height = 19; frame.origin.x = self.frame.size.width/2 - frame.size.width/2; frame.origin.y = yPosition; titleField.frame = frame; [backgroundView addSubview:titleField]; yPosition += titleField.frame.size.height + 10; } // i will be used to set the button's tag and is returned to delegate method int i; // loop through the buttons and create an ActionButton for each for (i = 0; i < buttonArray.count; i++) { ActionButton *alertButton = [ActionButton buttonWithText:[buttonArray objectAtIndex:i] cancel:NO]; frame = alertButton.frame; frame.origin.x = backgroundView.frame.size.width/2 - frame.size.width/2; frame.origin.y = yPosition; alertButton.frame = frame; alertButton.tag = i; [alertButton addTarget:self action:@selector(buttonPressedWithIndex:) forControlEvents:UIControlEventTouchUpInside]; [backgroundView addSubview:alertButton]; yPosition += alertButton.frame.size.height + 10; } // increase the tag index for the cancel button i++; // create the cancel button ActionButton * cancel = [ActionButton buttonWithText:cancelButtonTitle cancel:YES]; frame = cancel.frame; frame.origin.x = backgroundView.frame.size.width/2 - frame.size.width/2; frame.origin.y = yPosition; cancel.frame = frame; cancel.tag = i; [cancel addTarget:self action:@selector(buttonPressedWithIndex:) forControlEvents:UIControlEventTouchUpInside]; [backgroundView addSubview:cancel]; yPosition += cancel.frame.size.height + 15; frame = backgroundView.frame; frame.size.width = self.frame.size.width; frame.size.height = yPosition; backgroundView.frame = frame; // add the background view and animate the view on screen [self addSubview:backgroundView]; [self animateOn]; } return self; } // method that is fired when one of the ActionButtons is pressed - (void)buttonPressedWithIndex:(id)sender { // get the button that was pressed ActionButton *button = (ActionButton *)sender; index = (int)button.tag; [self animateOff]; } // shows the action sheet by adding it to the key window - (void)showAlert { [[[UIApplication sharedApplication]keyWindow]addSubview:self]; } // animate the view on to the screen - (void)animateOn { [UIView animateWithDuration:.23 animations:^{ CGRect frame = backgroundView.frame; frame.origin.y -= yPosition; backgroundView.frame = frame; }]; } // remove the view with animation once removed the delegate method is fired off notifying the // object that implemented the CustomActionSheet - (void)animateOff { [UIView animateWithDuration:.23 animations:^{ CGRect frame = backgroundView.frame; frame.origin.y += yPosition; backgroundView.frame = frame; } completion:^(BOOL complete){ [_delegate modalAlertPressed:self withButtonIndex:index]; [self removeFromSuperview]; }]; } @end

This is a really basic example and hopefully will be a great starting point if you ever needed a custom designed UIActionSheet. Let me know if you have any questions or comments in the comment section below, I would love to hear from you! Also if you ever want to talk about iOS or any kind of development send me a message on twitter. Thanks for reading!