5
\$\begingroup\$

To address backwards compatibility for UIAlertView, I have come up with an interface that would seem to work for both iOS versions 8 and lower.

This is not something I would like to roll out in production hence I would appreciate few extra pair of eyes taking a look at it.

Here is a link to the GitHub gist.

//UIAlertMode.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

typedef void (^AlertViewSelectionBlock) (NSInteger);
typedef void (^AlertControllerDefaultAction ) (UIAlertAction *);
typedef void (^AlertControllerCancelAction) (UIAlertAction *);
typedef void (^GenericBlock) (void *);

@interface UIAlertMode : NSObject

@property (assign, nonatomic) AlertViewSelectionBlock alertViewBlock;
@property (assign, nonatomic) AlertControllerCancelAction cancelAlertAction;
@property (assign, nonatomic) AlertControllerDefaultAction defaultAlertAction;

- (id) initAlertWith:(NSString *) title
                    :(NSString *) message
                    :(NSString *) cancelButtonTitle
                    :(NSString *) otherButtonTitle;


- (BOOL) supportsiOS8;
- (void) showAlertFor:(AlertViewSelectionBlock) alertViewBlock;
- (void) showAlertControllerFor:(AlertControllerDefaultAction) defaultAction :(AlertControllerCancelAction) cancelAction;
- (void) showAlertWith:(GenericBlock) block;
@end


// UIAlertMode.m

#import "UIAlertMode.h"
#import <objc/objc-runtime.h>
#import "AppDelegate.h"

@interface UIAlertMode () <UIAlertViewDelegate> {
    UIAlertView       *_alertView;
    UIAlertController *_alertController;
}

@end



@implementation UIAlertMode

@synthesize alertViewBlock;
@synthesize defaultAlertAction;
@synthesize cancelAlertAction;

- (id) init {
    self = [super init];
    return self;
}

- (id) initAlertWith:(NSString *) title
                    :(NSString *) message
                    :(NSString *) cancelButtonTitle
                    :(NSString *) otherButtonTitle {
    self = [super init];
    if ([self supportsiOS8]) {
        _alertController = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];

        UIAlertAction *defaultAction = [UIAlertAction actionWithTitle:otherButtonTitle style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
            if (self.defaultAlertAction) {
                self.defaultAlertAction(action);
            }
        }];

        UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:cancelButtonTitle style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
            if (self.cancelAlertAction) {
                self.cancelAlertAction(action);
            }
        }];

        [_alertController addAction:defaultAction];
        [_alertController addAction:cancelAction];

    } else {
        _alertView = [[UIAlertView alloc] initWithTitle:title message:message delegate:self cancelButtonTitle:cancelButtonTitle otherButtonTitles:otherButtonTitle, nil];
    }
    return self;
}
#pragma mark - Public API

- (BOOL) supportsiOS8 {
    Class kAlertViewClass = NSClassFromString(@"UIAlertController");
    return !kAlertViewClass ? NO : YES;
}

- (void) showAlertFor:(AlertViewSelectionBlock) alertBlock {
    self.alertViewBlock = alertBlock;
    [_alertView show];
}
- (void) showAlertControllerFor:(AlertControllerDefaultAction) defaultAction :(AlertControllerCancelAction) cancelAction {
    self.cancelAlertAction = cancelAction;
    self.defaultAlertAction = defaultAction;
    dispatch_async(dispatch_get_main_queue(), ^{
        AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
        [delegate.window.rootViewController presentViewController:_alertController animated:YES completion:nil];
    });
}

#pragma mark - UIAlertView delegate method

- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    if (self.alertViewBlock) {
        self.alertViewBlock(buttonIndex);
    }
}

I would like to get feedback on design, scalability, and memory management.

\$\endgroup\$
1
  • \$\begingroup\$ I will look at this over the weekend \$\endgroup\$
    – nhgrif
    Oct 24, 2014 at 13:28

1 Answer 1

3
\$\begingroup\$

As a start, this is confusing:

- (BOOL) supportsiOS8 {
    Class kAlertViewClass = NSClassFromString(@"UIAlertController");
    return !kAlertViewClass ? NO : YES;
}

There was a time when I was uncomfortable with ternary operators. They're admittedly kind of weird. I've gotten used to them, and I do enjoy them, but they should be used appropriately.

The logic here is that NSClassFromString returns nil if it can't figure out what class you've specified.

For starters, I'd change this to look more like this:

- (BOOL)supportsiOS8 {
    return NSClassFromString(@"UIAlertController") != nil;
}

Now it's slightly more readable and more clear what we're doing. We don't actually care what's returned beyond whether or not it is nil.

However, there's still problems...

Your method is called supportsiOS8, but all your method actually checks is whether or not this class can see a class called UIAlertController. And there's a difference.

I can use a class called UIAlertController on iOS 7 and lower. It's simply a matter of me implementing that class myself. If I've imported that class's file into this file, your supportsiOS8 will return YES. And my implementation of UIAlertController may or may not match Apple's implementation. So with that said, simply renaming the method as supportsAlertController wouldn't really work.

Moreover, it's likely that UIAlertController will continue to exist in iOS 9 and onward. Apps that use this class may or may not actually support iOS 8 (because supporting iOS 8 isn't actually necessary).

Instead, what we actually need to do is check the version of iOS that the user is actually using and compare it to the minimum iOS version for Apple's UIAlertController (iOS 8).

For starters, we can use the following method to determine the device's version:

[[UIDevice currentDevice] systemVersion];

This will return the device's version as an NSString. It might look something like @"8.1".

Now, we compare the return of this to the version we want.

- (BOOL)hasMinimumRequiredOS {
    NSString *deviceVersion = [[UIDevice currentDevice] systemVersion];
    return ([deviceVersion compare:@"8" options:NSNumericSearch] != NSOrderedAscending);
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.