UIAlertView/UIAlertController iOS 7和iOS 8兼容性

57

我正在使用Swift编写应用程序,并需要显示一个警告。该应用程序必须兼容iOS 7和iOS 8。由于UIAlertView已经被UIAlertController代替,那么如何在不检查系统版本的情况下检查UIAlertController是否可用?我听说过Apple建议我们不要检查设备的系统版本来确定API的可用性。

这是我在iOS 8上使用的代码,但在iOS 7上会崩溃并显示“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)

如果我在iOS 8上使用UIAlertView,会收到此警告:警告:尝试从视图控制器<_UIAlertShimPresentingViewController: 0x7bf72d60>中解除显示,而此时正在进行显示或解除显示!

14个回答

77

检测模式与Objective-C风格相同。

您需要检测当前活动的运行时是否具有实例化此类的能力。

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
}

不要根据操作系统版本来尝试解决这个问题,你需要检测能力,而不是操作系统。

编辑

此答案的原始探测器NSClassFromString("UIAlertController")-O优化下失败了,因此已更改为当前版本,可以在发布版本中正常工作。

编辑2

NSClassFromString在Xcode 6.3 / Swift 1.2的所有优化中都有效。


2
谢谢。这回答了我的原始问题,因为我在其他帖子上读到过,不应该检查操作系统版本来检测功能。 - Shan
@bay.phillips是开发论坛中友好的帖子发布者,为我提供了一个有效的检测器。我将对原始文件提交一份报告。 - Warren Burton
@WarrenBurton 很高兴听到这个消息!是的,这个检查在 Objective-C 中编写时有效,然后可以在 Swift 代码中引用,这很荒谬。但是,由于这是语言的已知限制,目前它就是这样。 - Bay Phillips

29
对于非 Swift 代码,纯 Objective-C 做法为:
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];

    }

这段代码无法工作,因为 UIAlertAction 没有 textFields 属性。只有 UIAlertController 才有 textFields 属性。 - Matt Morey
不是一个好主意,因为旧的和新的警报有不同的行为。例如,旧的警报可以排队显示多个错误警报,而新的警报则不能,它只会在第一个警报显示时跳过显示更多的警报。警告:试图在已经显示(null)的<MasterViewController: 0x7fe172734310>上呈现<UIAlertController: 0x7fe17268afd0>。 - malhal

9
我很烦恼一直要写两种情况,所以我编写了一个兼容iOS 7的UIAlertController,并将其上传到了GitHub上。我尽力复制了UIAlertController添加按钮和动作的(更好的)方法。它适用于Objective-C和Swift。我发表这篇文章是因为我在谷歌上搜索时找到了这个问题,我认为它对其他人可能有帮助。 https://github.com/BayPhillips/compatible-alert-controller

您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Paludis
很高兴听到@Paludis。我很乐意听取任何反馈,以及任何有助于使其更加完善的PR! - Bay Phillips

8
您可以使用以下代码解决您的问题:
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)
        }

必须将UIKit标记为可选而不是必需的。

致谢:- 可以在iOS 7和iOS 8上工作的警报


5
检查特定的iOS版本被认为是不好的做法 - 更好的方法是检查一个类是否“已知”或是否“响应选择器”。 - Gonen

6

Swift 2.0

 if #available(iOS 8.0, *) {

 } else {

 }

4
如果这是共享代码,并且有可能在iOS 8扩展中使用该代码(其中UIAlertView和UIActionSheet是受限API),以及iOS 7,其中UIAlertController不存在,请查看:JVAlertController 这是一个向后兼容的API,将UIAlertController回溯到iOS 7,我进行了改进以使SDK代码可安全用于iOS 7和iOS 8扩展。

这看起来是一个优雅的解决方案!谢谢! - Zaxter

2

如果您同时使用iOS 7的UIAlertView和iOS 8及以上的UIAlertController,且希望您的UIAlertController块调用您的UIAlertView的代理(例如MyController)alertView:didDismissWithButtonIndex方法以继续处理结果,则以下是如何实现的示例:

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];
            }
    ];

...

这里使用了苹果公司在块中捕获self的建议方式:避免捕获self时出现强引用环

当然,这种方法假设你的控制器中只有一个UIAlertView,并且将nil作为其值传递给委托方法。否则,您需要实例化(并标记)一个“假”的UIAlertView来传递给alertView:didDismissWithButtonIndex。


2
你可以使用分类来解决这个问题(尽管你需要将其转换为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

然后只需要调用:
[UIView showSimpleAlertWithTitle: @"Error" message: message cancelButtonTitle: @"OK"];

但是,如果您不想检查系统版本,只需使用

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

在UIView(AlertCompatibility)类别中


我怎样才能在调用者类中调用动作? - iPhone Guy
你可以将操作目标作为另一个参数传递给 +showSimpleAlertWithTitle:message:cancelButtonTitle: - Daniel Alves
不建议像这样比较系统版本以执行此类任务。正如所接受的答案所述,您应该检查功能,而不是版本。 - Alex Repty
是的,这就是为什么我还提供了另一种解决方案: BOOL lowerThaniOS8 = NSClassFromString(@“UIAlertController”)== nil; 如果你阅读所有答案,你会在底部找到它=) - Daniel Alves

1

我将检查两种使用UIAlertViewUIAlertContoller的方法。

检查1: iOS版本检查UIAlertController类。

    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()
    }

检查2: 检查 UIAlertController 是否为 nil,如果是,则 iOS 版本低于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()
    }

0
这是我的拖放 Swift 解决方案:
//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()
    }
}

调用方式如下:

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

注意,这仅适用于最基本的警报,您可能需要额外的代码来处理高级功能。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接