57

I am using Swift to write an app and I need to show an alert. The app must be iOS 7 and iOS 8 compatible. Since UIAlertView has been replaced with UIAlertController, how can I check if the UIAlertController is available without checking the system version? I have been hearing that Apple recommends that we should not check the system version of the device in order to determine the availability of an API.

This is what I am using for iOS 8 but this crashes on iOS 7 with "dyld: Symbol not found: _OBJC_CLASS_$_UIAlertAction" :

let alert = UIAlertController(title: "Error", message: message, preferredStyle: .Alert)
let cancelAction = UIAlertAction(title: "OK", style: .Cancel, handler: nil)
alert.addAction(cancelAction)
presentViewController(alert, animated: true, completion: nil)

If I use the UIAlertView for iOS 8, I get this warning: Warning: Attempt to dismiss from view controller <_UIAlertShimPresentingViewController: 0x7bf72d60> while a presentation or dismiss is in progress!

Kampai
  • 22,848
  • 21
  • 95
  • 95
Shan
  • 3,057
  • 4
  • 21
  • 33

14 Answers14

77

The detection pattern is identical to the Objective-C style.

You need to detect whether the current active runtime has the ability to instantiate this class

if objc_getClass("UIAlertController") != nil {

     println("UIAlertController can be instantiated")

      //make and use a UIAlertController

 }
 else {

      println("UIAlertController can NOT be instantiated")

      //make and use a UIAlertView
}

Don't try and work out this based on the OS version. You need to detect abilities NOT OS.

EDIT

The original detector for this answer NSClassFromString("UIAlertController") fails under -O optimisation so its been changed to the current version which does work for Release builds

EDIT 2

NSClassFromString is working at all optimisations in Xcode 6.3/Swift 1.2

Warren Burton
  • 17,451
  • 3
  • 53
  • 73
  • 2
    Thanks. This answers my original question because I have read on other posts that you should not check OS version to detect abilities. – Shan Aug 18 '14 at 01:46
  • @bay.phillips a friendly poster in dev forums provided me with a detector which works. Im going to file a radar against the original – Warren Burton Dec 20 '14 at 10:57
  • @WarrenBurton great to hear! And yeah, it's silly that this check works when written in Objective-C and then can be referenced in Swift code. But, that's the way it currently is as it's a known limitation of the language. – Bay Phillips Dec 20 '14 at 16:54
29

For non-swift code, pure objective-C do this

if ([UIAlertController class])
    {
        // use UIAlertController
        UIAlertController *alert= [UIAlertController
                                      alertControllerWithTitle:@"Enter Folder Name"
                                      message:@"Keep it short and sweet"
                                      preferredStyle:UIAlertControllerStyleAlert];

        UIAlertAction* ok = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault
                                                   handler:^(UIAlertAction * action){
                                                       //Do Some action here
                                                       UITextField *textField = alert.textFields[0];
                                                       NSLog(@"text was %@", textField.text);

                                                   }];
        UIAlertAction* cancel = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleDefault
                                                       handler:^(UIAlertAction * action) {

                                                           NSLog(@"cancel btn");

                                                           [alert dismissViewControllerAnimated:YES completion:nil];

                                                       }];

        [alert addAction:ok];
        [alert addAction:cancel];

        [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
            textField.placeholder = @"folder name";
            textField.keyboardType = UIKeyboardTypeDefault;
        }];

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

    }
    else
    {
        // use UIAlertView
        UIAlertView* dialog = [[UIAlertView alloc] initWithTitle:@"Enter Folder Name"
                                                         message:@"Keep it short and sweet"
                                                        delegate:self
                                               cancelButtonTitle:@"Cancel"
                                               otherButtonTitles:@"OK", nil];

        dialog.alertViewStyle = UIAlertViewStylePlainTextInput;
        dialog.tag = 400;
        [dialog show];

    }
Sam B
  • 27,273
  • 15
  • 84
  • 121
  • This code will not work as `UIAlertAction` does not have a `textFields` property. Only the `UIAlertController` has a `textFields` property. – Matt Morey Dec 09 '14 at 17:46
  • Not a good idea because the old and new alerts have different behaviours. E.g. the old alert can be queued up to show multiple error alerts, the new one cannot it just skips showing more alerts while the first is shown. Warning: Attempt to present on which is already presenting (null) – malhal Feb 04 '16 at 18:47
9

I was annoyed that I kept having to write out both situations, so I wrote a compatible UIAlertController that works for iOS 7 as well so I just threw it up on GitHub. I did my best to replicate the (much better) methods of adding buttons and actions of the UIAlertController. Works with both Objective-C and Swift. I'm posting this as I found this question when searching on Google and figured it could be helpful for others.

https://github.com/BayPhillips/compatible-alert-controller

Bay Phillips
  • 2,015
  • 1
  • 13
  • 7
8

You can resolve your issue using this code :-

var device : UIDevice = UIDevice.currentDevice()!;
        var systemVersion = device.systemVersion;
        var iosVerion : Float = systemVersion.bridgeToObjectiveC().floatValue;
        if(iosVerion < 8.0) {
            let alert = UIAlertView()
            alert.title = "Noop"
            alert.message = "Nothing to verify"
            alert.addButtonWithTitle("Click")
            alert.show()
        }else{
            var alert : UIAlertController = UIAlertController(title: "Noop", message: "Nothing to verify", preferredStyle: UIAlertControllerStyle.Alert)
            alert.addAction(UIAlertAction(title: "Click", style:.Default, handler: nil))
            self.presentViewController(alert, animated: true, completion: nil)
        }

and UIKit had to be marked Optional rather than Required.

Courtsey :- Alert that can work on iOS 7 and iOS 8

Community
  • 1
  • 1
Kundan
  • 3,084
  • 2
  • 28
  • 65
  • 5
    It is considered bad practice to check for a specific iOS version - better to check if a class is "known" or if "responds to selector". – Gonen May 09 '15 at 16:17
6

Swift 2.0

 if #available(iOS 8.0, *) {

 } else {

 }
William Hu
  • 15,423
  • 11
  • 100
  • 121
4

If this is shared code, and there is the possibility that the code can be used in an iOS 8 extension (where UIAlertView and UIActionSheet are restricted APIs) as well as iOS 7, where UIAlertController does not exist, have a look at: JVAlertController

It is an API-compatible back-port of UIAlertController to iOS 7, that I undertook to make SDK code safe for use in both iOS 7 and iOS 8 extensions.

jverdi
  • 1,506
  • 13
  • 14
2

You could use a category to solve that (though you'll need to convert it to Swift):

@implementation UIView( AlertCompatibility )

+( void )showSimpleAlertWithTitle:( NSString * )title
                          message:( NSString * )message
                cancelButtonTitle:( NSString * )cancelButtonTitle
{
    if( [[UIDevice currentDevice] isSystemVersionLowerThan: @"8"] )
    {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle: title
                                                        message: message
                                                       delegate: nil
                                              cancelButtonTitle: cancelButtonTitle
                                              otherButtonTitles: nil];
        [alert show];
    }
    else
    {
        // nil titles break alert interface on iOS 8.0, so we'll be using empty strings
        UIAlertController *alert = [UIAlertController alertControllerWithTitle: title == nil ? @"": title
                                                                       message: message
                                                                preferredStyle: UIAlertControllerStyleAlert];

        UIAlertAction *defaultAction = [UIAlertAction actionWithTitle: cancelButtonTitle
                                                                style: UIAlertActionStyleDefault
                                                              handler: nil];

        [alert addAction: defaultAction];

        UIViewController *rootViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
        [rootViewController presentViewController: alert animated: YES completion: nil];
    }
}

@end

@implementation UIDevice( SystemVersion )

-( BOOL )isSystemVersionLowerThan:( NSString * )versionToCompareWith
{
    if( versionToCompareWith.length == 0 )
        return NO;

    NSString *deviceSystemVersion = [self systemVersion];
    NSArray *systemVersionComponents = [deviceSystemVersion componentsSeparatedByString: @"."];

    uint16_t deviceMajor = 0;
    uint16_t deviceMinor = 0;
    uint16_t deviceBugfix = 0;

    NSUInteger nDeviceComponents = systemVersionComponents.count;
    if( nDeviceComponents > 0 )
        deviceMajor = [( NSString * )systemVersionComponents[0] intValue];
    if( nDeviceComponents > 1 )
        deviceMinor = [( NSString * )systemVersionComponents[1] intValue];
    if( nDeviceComponents > 2 )
        deviceBugfix = [( NSString * )systemVersionComponents[2] intValue];


    NSArray *versionToCompareWithComponents = [versionToCompareWith componentsSeparatedByString: @"."];

    uint16_t versionToCompareWithMajor = 0;
    uint16_t versionToCompareWithMinor = 0;
    uint16_t versionToCompareWithBugfix = 0;

    NSUInteger nVersionToCompareWithComponents = versionToCompareWithComponents.count;
    if( nVersionToCompareWithComponents > 0 )
        versionToCompareWithMajor = [( NSString * )versionToCompareWithComponents[0] intValue];
    if( nVersionToCompareWithComponents > 1 )
        versionToCompareWithMinor = [( NSString * )versionToCompareWithComponents[1] intValue];
    if( nVersionToCompareWithComponents > 2 )
        versionToCompareWithBugfix = [( NSString * )versionToCompareWithComponents[2] intValue];

    return ( deviceMajor < versionToCompareWithMajor )
           || (( deviceMajor == versionToCompareWithMajor ) && ( deviceMinor < versionToCompareWithMinor ))
           || (( deviceMajor == versionToCompareWithMajor ) && ( deviceMinor == versionToCompareWithMinor ) && ( deviceBugfix < versionToCompareWithBugfix ));
}

@end

Then just call

[UIView showSimpleAlertWithTitle: @"Error" message: message cancelButtonTitle: @"OK"];

But, if you do not want to check the system version, just use

BOOL lowerThaniOS8 = NSClassFromString( @"UIAlertController" ) == nil;

inside the category UIView( AlertCompatibility )

  • how can i call action in caller class? – iPhone Guy Nov 17 '14 at 13:39
  • You could pass the action target as another parameter to `+showSimpleAlertWithTitle:message:cancelButtonTitle:` – Daniel Alves Nov 19 '14 at 01:33
  • Comparing the system version for tasks like this is discouraged. As the accepted answer states, you should check for capabilities, not versions. – Alex Repty Nov 20 '14 at 12:39
  • Yeah, that's why I also gave that alternate solution: `BOOL lowerThaniOS8 = NSClassFromString( @"UIAlertController" ) == nil;` If you read all the answer, you will find it at the bottom =) – Daniel Alves Nov 24 '14 at 15:37
2

If your using both iOS 7- UIAlertView and iOS 8+ UIAlertController as described above, and you want your UIAlertController blocks to call your UIAlertView's delegate (e.g. MyController) alertView:didDismissWithButtonIndex method to continue processing the results, here is an example of how to do that:

if ([UIAlertController class]) {
    MyController * __weak mySelf = self;

    UIAlertController *alertController = [UIAlertController
        alertControllerWithTitle:alertTitle
        message:alertMessage
        preferredStyle:UIAlertControllerStyleAlert];

    UIAlertAction *cancelAction = [UIAlertAction
        actionWithTitle:alertCancel
        style:UIAlertActionStyleCancel
        handler:^(UIAlertAction *action)
            {
            [mySelf alertView:nil didDismissWithButtonIndex:0];
            }
    ];

...

This uses Apple's recommendation for capturing self in a block: Avoid Strong Reference Cycles when Capturing self

Of course, this method assumes you only have one UIAlertView in the controller and therefore pass nil as its value to the delegate method. Otherwise, you'd need to instantiate (and tag) a "fake" UIAlertView to pass to alertView:didDismissWithButtonIndex.

ScottyB
  • 2,167
  • 1
  • 30
  • 46
1

Here to check two way of UIAlertView and UIAlertContoller.

Check 1 : iOS verstion check UIAlertController Class.

    if #available(iOS 8.0, *) {

        // UIALertController
        let alert = UIAlertController(title: "Alert", message: "Alert after 8.0", preferredStyle: .Alert)
        let cancelAction = UIAlertAction(title: "OK", style: .Cancel, handler: nil)
        alert.addAction(cancelAction)
        presentViewController(alert, animated: true, completion: nil)
    } else {

        // UIALertView
        UIAlertView(title: "Alert", message: "Alert below iOS V 8.0", delegate: nil, cancelButtonTitle: "OK").show()
    }

Check 2 : check UIAlertController nil then iOS version below 8.0.

    if objc_getClass("UIAlertController") != nil {

        // UIALertController
        let alert = UIAlertController(title: "Alert", message: "Alert after 8.0", preferredStyle: .Alert)
        let cancelAction = UIAlertAction(title: "OK", style: .Cancel, handler: nil)
        alert.addAction(cancelAction)
        presentViewController(alert, animated: true, completion: nil)

    }
    else {

        // UIALertView
        UIAlertView(title: "Alert", message: "Alert below iOS V 8.0", delegate: nil, cancelButtonTitle: "OK").show()
    }
Kirit Modi
  • 23,155
  • 15
  • 89
  • 112
0

If you want to be compatible with iOS 7, just don't use UIAlertController. Simple as that.

UIAlertView has not been replaced, it still works perfectly and will continue to work perfectly for the foreseeable future.

Abhi Beckert
  • 32,787
  • 12
  • 83
  • 110
  • 1
    But I get this warning when running on an iOS 8 device: Warning: Attempt to dismiss from view controller <_UIAlertShimPresentingViewController: 0x7bf72d60> while a presentation or dismiss is in progress! – Shan Aug 04 '14 at 03:20
  • So file a bug report? iOS 8 is a beta, it has bugs. – Abhi Beckert Aug 11 '14 at 03:51
  • I'm (ab)using a UIAlertView for a type of password double-entry, two edit boxes that must match. I'm doing this by adding subviews to a larger view, then adding that view as the accessory view of the alert view. Unfortunately the sizing of the view is borked and the second edit box is clipped off completely, as are the buttons. – William T. Mallard Sep 18 '14 at 22:37
  • I filed a radar about subviews not returning for UIActionSheet/UIAlertViews anymore. I was basically told to use the new UIAlertController as UIAlert/Action was being deprecated. Not exactly ideal since you can't target iOS 7 and expect the same results for users running on iOS 8. I think the only way to make it work correct for users is to detect ability of new style and apply changes that way. – Bill Burgess Sep 23 '14 at 17:25
  • `UIAlertView` is deprecated, if you take a look at the source code you'll find the following commet *UIAlertView is deprecated. Use UIAlertController with a preferredStyle of UIAlertControllerStyleAlert instead* Also see: http://nshipster.com/uialertcontroller/ – Eric Oct 06 '14 at 03:45
  • 7
    @Eric it's deprecated but only in the documentation, they deliberately didn't flag it as deprecated in the header files. Usually when Apple does that it means they want people to move away from it but they will continue to maintain `UIAlertView` for another 5 years or so. It definitely is not time yet to switch to `UIAlertController` for the majority of apps. Apple wants developers to maintain backwards compatibility with iOS 7 if possible, and iOS 7 does not have UIAlertController. Not sure why my comment was down-voted when I simply stated a fact. – Abhi Beckert Oct 06 '14 at 03:47
  • 2
    I am experiencing compatibility problems with UIAlertView under iOS8. Sometimes I see only the left 2/3 of my iPad (landscape) dimmed. Alert view appears misplaced, as though the device thinks its rotated. Right part of the screen still accepts touch gestures! – Bradley Thomas Jan 06 '15 at 16:13
0

Here is my drag and drop swift solution:

//Alerts change in iOS8, this method is to cover iOS7 devices
func CozAlert(title: String, message: String, action: String, sender: UIViewController){

    if respondsToSelector("UIAlertController"){
        var alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
        alert.addAction(UIAlertAction(title: action, style: UIAlertActionStyle.Default, handler:nil))
        sender.presentViewController(alert, animated: true, completion: nil)
    }
    else {
        var alert = UIAlertView(title: title, message: message, delegate: sender, cancelButtonTitle:action)
        alert.show()
    }
}

Call like this:

CozAlert("reportTitle", message: "reportText", action: "reportButton", sender: self)

Beware this is only for the most basic alerts, you might need additional code for advanced stuff.

Esqarrouth
  • 38,543
  • 21
  • 161
  • 168
-1

Try below code. It works fine for both iOS 8 and below version.

if (IS_OS_8_OR_LATER) {
    UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert];

    UIAlertAction *cancelAction = [UIAlertAction
                                 actionWithTitle:@"OK"
                                 style:UIAlertActionStyleCancel
                                 handler:^(UIAlertAction *action)
                                 {

                                 }];
    [alertVC addAction:cancelAction];

    [[[[[UIApplication sharedApplication] windows] objectAtIndex:0] rootViewController] presentViewController:alertVC animated:YES completion:^{

    }];
}
else{
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:title message:msg delegate:self cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil];
    [alert show];
}
amitshinik
  • 91
  • 1
  • 10
-2

In iOS8, there is a new class UIAlertController that replaces UIAlertView and UIActionSheet. From iOS8 on, use UIAlertController, and for iOS8 and before use UIAlertView and UIActionSheet. I think that iOS8 added size classes that change UIAlertView display direction. See: https://github.com/wangyangcc/FYAlertManage

Erik Godard
  • 5,930
  • 6
  • 30
  • 33
-2

download alert class from this link and use it easily for ios 6 ,7 and 8

//Old code  
**UIAlertView** *alert=[[**UIAlertView** alloc]initWithTitle:@"FreeWare" message:@"Welcome to Common class" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Ok", nil];

//New code 

**MyAlertView** *alert=[[**MyAlertView** alloc]initWithTitle:@"FreeWare" message:@"Welcome to Common class" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Ok", nil];
CinCout
  • 9,486
  • 12
  • 49
  • 67
GameLoading
  • 6,688
  • 2
  • 33
  • 57