UIAlertController

From iPhone Development Wiki
Jump to: navigation, search

UIAlertController is a new UIKit.framework class introduced in iOS 8. Even though UIAlertView and UIActionSheet have not been marked as deprecated, you should use UIAlertController to display Alerts and Action Sheets and if you are working in SpringBoard you should use SBAlertItem from the SpringBoardUI.framework framework.

What you can do using Public API

Since UIAlertView and UIActionSheet are UI Elements and UIElements should only be used within UIViewControllers and subclasses, Apple has made UIAlertController a UIViewController subclass.
Both UIAlertView and UIActionSheet are UIView subclasses which allowed you to easily subclass it and modify its content. Subclassing UIAlertController is also possible, but you have less freedom to design what you need. You'd need to make your own custom Alert then.
However if you are happy with the default look of an Alert, you can create one using the public API as follows:

UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"MyAlert Title" message:@"MyAlert Awesome Message" 
                                                                                    preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleDefault handler:^{
                       NSLog(@"Cancel Button was pressed");
}];

[self presentViewController:alertController animated:YES completion:nil];

/*
self must be a UIViewController object or subclass object in order to call presentViewController:animated:completion: on this object.
*/

That is basically all what you can do with public API. As you can see there is no delegate method anymore on which button was clicked but there is a convenient handler block for each action.

What you can do using Private API

Now the fun part begins, the part that actually made me write this article because the public API is accessible for everyone. Private API can only be used on jailbroken devices because Apple does not allow developers to use them hence they are private.

There is one property of UIAlertController which makes the difference and which I will pay most attention to in this article:

@property (nonatomic,retain) UIViewController * contentViewController;

Yes, each UIAlertController has a contentViewController property which again has an UIView that we can customise. This allows us to create 90% custom UIAlertControllers and 10% go to the default look of it. We can add any UIKit Element to our Alert View and we have full control over it.
To achieve this we will do three steps:

  • Subclass UIViewController
  • Create a UIView for our ViewController
  • Assign our ViewController as the contentViewController of the UIAlertController

Subclass UIViewController

Our subclass will not contain much code, it will only load our UIView and set a preferred content size:

//MyContentViewController.h
#import "MyContentView.h"
@interface MyContentViewController : UIViewController
@end

//MyContentViewController.m
#import "MyContentViewController.h"

@implementation MyContentViewController

-(void)loadView {
	self.view = [[MyContentView alloc] init];
}

-(CGSize)preferredContentSize {
	CGSize contentSize = [super preferredContentSize];
        //gets the preferredContentHeight from the View. will be set depending on how much content we have
	return CGSizeMake(contentSize.width,((MyContentView *)self.view).preferredContentHeight); 
}

@end

Now we need to create our custom UIView which is initialised by our MyContentViewController class. In this example I will create a title label and a message label to re-create the default look of an alert. You are free to add anything you want but a title and a message are important.

Create a UIView for our ViewController

//MyContentView.h
@interface MyContentView : UIView
@property (nonatomic,strong,readonly) UILabel *titleLabel;
@property (nonatomic,strong,readonly) UILabel *messageLabel;
@property (nonatomic,readonly) CGFloat preferredContentHeight;
@end

//MyContentView.m
#import "MyContentView.h"

@implementation MyContentView
@synthesize titleLabel = _titleLabel,messageLabel = _messageLabel; //create getters and setters

//overwrite the init method to set up our UI.
-(id)init {
	self = [super initWithFrame:CGRectZero];

	if (self) {
		[self setupUI];
	}

	return self;
}

-(void)setupUI {
    //create title label
    _titleLabel = [[UILabel alloc] initWithFrame:CGRectZero];
    [self.titleLabel setNumberOfLines:1]; //one line title, can be changed
    [self.titleLabel setFont:[UIFont boldSystemFontOfSize:18]]; //bold text
    [self.titleLabel setText:@"My Title"];
    [self.titleLabel setBackgroundColor:[UIColor clearColor]];
    [self.titleLabel setTextColor:[UIColor blackColor]];
    [self.titleLabel setTextAlignment:NSTextAlignmentCenter];
    [self addSubview:self.titleLabel]; //add our label
    //now it is time to position the label.
    [self.titleLabel setTranslatesAutoresizingMaskIntoConstraints:NO]; //so it only tries to fill our layout constraints
    //position it centered on the X axis
    [self addConstraint:[NSLayoutConstraint constraintWithItem:self.titleLabel attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual 
                                   toItem:self attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
   //position it on top of the view + 10px lower 
    [self addConstraint:[NSLayoutConstraint constraintWithItem:self.titleLabel attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual 
                                   toItem:self attribute:NSLayoutAttributeTop multiplier:1 constant:10]];

    //create message label
    _messageLabel = [[UILabel alloc] initWithFrame:CGRectZero];
    [self.messageLabel setNumberOfLines:2]; //two lines for example
    [self.messageLabel setFont:[UIFont systemFontOfSize:12]]; //non bold, smaller font
    [self.messageLabel setText:@"My awesome Message"];
    [self.messageLabel setBackgroundColor:[UIColor clearColor]];
    [self.messageLabel setTextColor:[UIColor blackColor]];
    [self.messageLabel setTextAlignment:NSTextAlignmentCenter];
    [self addSubview:self.messageLabel]; //adding label to view
    //need to position it as well
    [self.messageLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
    //center it on the view on the X axis
    [self addConstraint:[NSLayoutConstraint constraintWithItem:self.messageLabel attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual 
                                    toItem:self attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
    //set the width to be 0.8 * view.width so it has some padding on the left and right side
    [self addConstraint:[NSLayoutConstraint constraintWithItem:self.messageLabel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual 
                                    toItem:self attribute:NSLayoutAttributeWidth multiplier:0.8 constant:0]];
    //position its top to be 10px lower than the bottom of the titleLabel
    [self addConstraint:[NSLayoutConstraint constraintWithItem:self.messageLabel attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual 
                                    toItem:self.titleLabel attribute:NSLayoutAttributeBottom multiplier:1 constant:10]];
}

-(CGFloat)preferredContentHeight {
	return 150.0f; //by testing you can see what height your view requires to display all content.
}

Using NSLayoutConstraints is a great way to make a clean looking interface without needing to take care of orientation changes and different device types.
This seems a lot of code compared to simply showing an alert view, but while iOS manages the Alert you are free to design its content which is a great opportunity.
And this shows that with one private property you are able to do so much more.

Assign our ViewController as the contentViewController of the UIAlertController

Last but not least we assign our view controller to be the content view controller of the AlertController

//we add this category to be able to compile fine and so we do not need to import header files
@interface UIAlertController (ContentViewController)
@property (nonatomic,retain) UIViewController * contentViewController;
@end

UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"" message:@"" 
                                                                                    preferredStyle:UIAlertControllerStyleAlert];
[alertController setContentViewController:[[MyContentViewController alloc] init]];
[alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleDefault handler:^{
                       NSLog(@"Cancel Button was pressed");
}];

[self presentViewController:alertController animated:YES completion:nil];

What about Message and Title?

There is an easy trick to keep the message and title string and use them in your custom content view.
All you need to do is adding a weak property to MyContentViewController of type UIAlertController and assign the alertController to it.

//MyContentViewController.h
@property (nonatomic,weak) UIAlertController *myAlertController;

And in the loadView method where you create the custom view you pass the message and title property of UIAlertController to your UIView subclass which then can use it when creating the labels.
This way you can pass the title and message all the way to your content view.

External links