Creating a universal UIAlertController for various versions of iOS
One of the most sought after classes in UIKit before iOS version 8 was UIAlertView and UIActionSheet. Probably every developer of applications for the mobile platform from Apple sooner or later came across them. Displaying messages or an action selection menu is an integral part of almost any user application. To work with these classes, or rather, to handle button clicks, the programmer needed to implement in his class the methods of the corresponding delegate - UIAlertViewDelegate or UIActionSheetDelegate (if something was not required above, then it was enough to implement the clickedButtonAtIndex method). In my opinion, this is very inconvenient: if several dialog boxes with different sets of actions were created inside the object, then they were processed all the same in the same method with a bunch of conditions inside. With the release of iOS version 8, the UIAlertController class appeared in UIKit, which replaced UIAlertView and UIActionSheet. And one of its main distinguishing features is that instead of delegates, it uses a block approach:
This approach allows you to write more structured and logical code. From now on, the programmer no longer needs to separate the creation of the dialog box and the processing of events - UIAlertController eliminates this misunderstanding, but at the same time brings historical injustice due to the inability to use it in iOS 7 and earlier versions. There are several ways to solve this problem:
The latter option is the most logical, and most developers, I’m sure, would choose it, but this method has a significant drawback - the condition for checking the version of the operating system will have to be written every time you need to display a dialog box. Faced with this in practice, I created a special wrapper class UIAlertDialog, which allows you to forget about this problem.
The idea is that UIAlertController’s convenient block syntax can be used in its projects without being limited to the latest versions of iOS.
Defining a Dialog Style
and type of handler block
you can go to the class structure:
transferred parameters are stored in
and an array is initialized (items), which will store the actions of the buttons.
Adding a new button:
where UIAlertDialogItem is
which stores the text of the button and the action associated with it.
And finally, the showInViewController method , which encapsulates the creation of a dialog box depending on the version of the operating system:
I emphasize that each corresponding method is not immediately executed, but is added to the main execution queue. This is due to the fact that if another dialog box is created in the button handler, it will be displayed only after the entire animation of the previous dialog has been completed.
Let's consider in detail the methods of creating dialog boxes:
UIAlertController
In this listing, I would like to mark the line
or rather, its position in the code. Due to the property of the block to store the context in which it was created, it becomes possible to transfer the index of the pressed button to the block handler. This method is necessary: UIAlertAction does not contain the desired parameter.
UIAlertView and UIActionSheet
As described by UIAlertDialog, now creating a dialog box looks like this:
and due to the fact that this class is a delegate of UIAlertView and UIActionSheet
one point needs to be clarified.
As you know, delegates in the class are described as properties with the weak modifier . This means that if strong references to the delegate object no longer exist, an EXC_BAD_ACCESS exception will be thrown when trying to call delegate methods.
In our case, this is exactly what will happen - ARC will remove alertDialog , since there are no external links to it. The problem can be solved if you create the UIAlertView and UIActionSheet descendant classes by adding a link to the dialog object in them:
and
Thanks to the manipulations, the code for creating dialog boxes will take the following form:
and the final touch - processing the actions of buttons, occurs in the method of the corresponding delegate:
Conclusion
The result is a simple and compact solution that will significantly reduce the time spent working with dialog boxes ( source code ).
Thanks for attention!
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Hello" message:@"Habr!" preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:@"Action" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
// код обработчика кнопки
}]];
This approach allows you to write more structured and logical code. From now on, the programmer no longer needs to separate the creation of the dialog box and the processing of events - UIAlertController eliminates this misunderstanding, but at the same time brings historical injustice due to the inability to use it in iOS 7 and earlier versions. There are several ways to solve this problem:
- Disregard UIAlertController and continue to use obsolete UIAlertView and UIActionSheet.
- Use custom dialogs. The programmer either writes his own implementation, which leads to an increase in time costs, or connects third-party components (for example, SIAlertView ), the use of which has several disadvantages:
- software modules with good support can be counted on the fingers (often their creators quickly throw this thankless task);
- if the project uses several components from different developers, then their interaction may cause problems (rarely, but it is possible).
- Check iOS version and create either UIAlertController, or UIAlertView or UIActionSheet.
The latter option is the most logical, and most developers, I’m sure, would choose it, but this method has a significant drawback - the condition for checking the version of the operating system will have to be written every time you need to display a dialog box. Faced with this in practice, I created a special wrapper class UIAlertDialog, which allows you to forget about this problem.
The idea is that UIAlertController’s convenient block syntax can be used in its projects without being limited to the latest versions of iOS.
Defining a Dialog Style
typedef NS_ENUM(NSInteger, UIAlertDialogStyle) {
UIAlertDialogStyleAlert = 0,
UIAlertDialogStyleActionSheet
};
and type of handler block
typedef void(^UIAlertDialogHandler)(NSInteger buttonIndex);
you can go to the class structure:
@interface UIAlertDialog : NSObject
- (instancetype)initWithStyle:(UIAlertDialogStyle)style title:(NSString *)title andMessage:(NSString *)message;
- (void)addButtonWithTitle:(NSString *)title andHandler:(UIAlertDialogHandler)handler;
- (void)showInViewController:(UIViewController *)viewContoller;
@end
Inside the constructor
- (instancetype)initWithStyle:(UIAlertDialogStyle)style title:(NSString *)title andMessage:(NSString *)message {
if (self = [super init]) {
self.style = style;
self.title = title;
self.message = message;
self.items = [NSMutableArray new];
}
return self;
}
transferred parameters are stored in
internal variables
@interface UIAlertDialog ()
@property (nonatomic) UIAlertDialogStyle style;
@property (copy, nonatomic) NSString *title;
@property (copy, nonatomic) NSString *message;
@property (strong, nonatomic) NSMutableArray *items;
@end
and an array is initialized (items), which will store the actions of the buttons.
Adding a new button:
- (void)addButtonWithTitle:(NSString *)title andHandler:(UIAlertDialogHandler)handler {
UIAlertDialogItem *item = [UIAlertDialogItem new];
item.title = title;
item.handler = handler;
[self.items addObject:item];
}
where UIAlertDialogItem is
special inner class (analog of UIAlertAction)
@interface UIAlertDialogItem : NSObject
@property (copy, nonatomic) NSString *title;
@property (copy, nonatomic) UIAlertDialogHandler handler;
@end
which stores the text of the button and the action associated with it.
And finally, the showInViewController method , which encapsulates the creation of a dialog box depending on the version of the operating system:
- (void)showInViewController:(UIViewController *)viewContoller {
if ([[UIDevice currentDevice].systemVersion intValue] > 7) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self showAlertControllerInViewController:viewContoller];
}];
return;
}
if (self.style == UIAlertDialogStyleActionSheet) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self showActionSheetInView:viewContoller.view];
}];
}
else {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self showAlert];
}];
}
}
I emphasize that each corresponding method is not immediately executed, but is added to the main execution queue. This is due to the fact that if another dialog box is created in the button handler, it will be displayed only after the entire animation of the previous dialog has been completed.
Let's consider in detail the methods of creating dialog boxes:
UIAlertController
- (void)showAlertControllerInViewController:(UIViewController *)viewController {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:self.title message:self.message preferredStyle:self.style == UIAlertDialogStyleActionSheet ? UIAlertControllerStyleActionSheet : UIAlertControllerStyleAlert];
NSInteger i = 0;
for (UIAlertDialogItem *item in self.items) {
UIAlertAction *alertAction = [UIAlertAction actionWithTitle:item.title style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
NSInteger buttonIndex = i;
if (item.handler) {
item.handler(buttonIndex);
}
}];
[alertController addAction:alertAction];
i++;
}
UIAlertAction *closeAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"close", nil) style:UIAlertActionStyleCancel handler:nil];
[alertController addAction:closeAction];
[viewController presentViewController:alertController animated:YES completion:nil];
}
In this listing, I would like to mark the line
NSInteger buttonIndex = i;
or rather, its position in the code. Due to the property of the block to store the context in which it was created, it becomes possible to transfer the index of the pressed button to the block handler. This method is necessary: UIAlertAction does not contain the desired parameter.
UIAlertView and UIActionSheet
As described by UIAlertDialog, now creating a dialog box looks like this:
- (void)showMessage:(NSString *)message
{
UIAlertDialog *alertDialog = [[UIAlertDialog alloc] initWithStyle:UIAlertDialogStyleAlert title:message andMessage:nil];
[alertDialog showInViewController:self];
}
and due to the fact that this class is a delegate of UIAlertView and UIActionSheet
@interface UIAlertDialog : NSObject
one point needs to be clarified.
As you know, delegates in the class are described as properties with the weak modifier . This means that if strong references to the delegate object no longer exist, an EXC_BAD_ACCESS exception will be thrown when trying to call delegate methods.
In our case, this is exactly what will happen - ARC will remove alertDialog , since there are no external links to it. The problem can be solved if you create the UIAlertView and UIActionSheet descendant classes by adding a link to the dialog object in them:
@interface UIAlertViewDialog : UIAlertView
@property (strong, nonatomic) UIAlertDialog *alertDialog;
@end
and
@interface UIActionSheetDialog : UIActionSheet
@property (strong, nonatomic) UIAlertDialog *alertDialog;
@end
Thanks to the manipulations, the code for creating dialog boxes will take the following form:
- (void)showActionSheetInView:(UIView *)view {
UIActionSheetDialog *actionSheet = [[UIActionSheetDialog alloc] initWithTitle:self.title delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles:nil];
actionSheet.alertDialog = self;
for (UIAlertDialogItem *item in self.items) {
[actionSheet addButtonWithTitle:item.title];
}
[actionSheet addButtonWithTitle:NSLocalizedString(@"close", nil)];
actionSheet.cancelButtonIndex = actionSheet.numberOfButtons - 1;
[actionSheet showInView:view.window];
}
similar for UIAlertView
- (void)showAlert {
UIAlertViewDialog *alertView = [[UIAlertViewDialog alloc] initWithTitle:self.title message:self.message delegate:self cancelButtonTitle:nil otherButtonTitles:nil];
alertView.alertDialog = self;
for (UIAlertDialogItem *item in self.items) {
[alertView addButtonWithTitle:item.title];
}
[alertView addButtonWithTitle:NSLocalizedString(@"close", nil)];
alertView.cancelButtonIndex = alertView.numberOfButtons - 1;
[alertView show];
}
and the final touch - processing the actions of buttons, occurs in the method of the corresponding delegate:
- (void)actionSheet:(UIActionSheetDialog *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == actionSheet.numberOfButtons - 1) {
return;
}
UIAlertDialogItem *item = self.items[buttonIndex];
if (item.handler) {
item.handler(buttonIndex);
}
}
UIAlertViewDelegate
- (void)alertView:(UIAlertViewDialog *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == alertView.numberOfButtons - 1) {
return;
}
UIAlertDialogItem *item = self.items[buttonIndex];
if (item.handler) {
item.handler(buttonIndex);
}
}
Conclusion
The result is a simple and compact solution that will significantly reduce the time spent working with dialog boxes ( source code ).
Thanks for attention!