在从 Obj-C 到 Swift 的转换中实现“如果对象不存在则执行此操作”

Implement 'Do this if object doesn't exist' in conversion from Obj-C to Swift

我正在将一些代码转换为 Swift,我遇到了一个似乎与解包可选相关的问题,因此我可以实现该模式:

如果对象存在,则使用它。如果不创建它。

我也想知道我对 NSPredicate 的使用。

这是有效的 Obj-C 代码:

UILabel *label = [[[cell.contentView subviews] filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(UIView *subview, NSDictionary *bindings) {
    return ([subview isKindOfClass:[UILabel class]]);
}]] firstObject];

if (!label) {
    label = [[UILabel alloc] initWithFrame:cell.bounds];
    label.font = [UIFont systemFontOfSize:10.f];
    label.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    label.textAlignment = NSTextAlignmentCenter;
    label.backgroundColor = [UIColor clearColor];
    [cell.contentView addSubview:label];
}

这是我在 Swift 中修改后的代码:

    let subViews = cell.contentView.subviews
    let labelPredicate = NSPredicate { (UIView subview, _) -> Bool in
        return subview.isKindOfClass(UILabel) }

    let labels = (subViews as NSArray).filteredArrayUsingPredicate(labelPredicate) as! [UILabel]
    let label = labels[0]

    if (!label) {
        label = UILabel(frame: view.bounds)
        label.font = UIFont.systemFontOfSize( 10)
        label.autoresizingMask = UIViewAutoresizing.FlexibleHeight | UIViewAutoresizing.FlexibleWidth
        label.textAlignment = NSTextAlignment.Center
        label.backgroundColor = UIColor.clearColor()
        cell.contentView.addSubview(label)

    }

问题出现在 if(!label) 行,xcode 错误 "Could not find an overload for '!' that accepts the supplied arguments."

如何正确测试标签是否存在?

谢谢。

几个想法:

  1. 问题的标题是如何在Swift中使用谓词。坦率地说,我会考虑使用 filter 方法,它完全消除了 NSPredicate

    let labels = subViews.filter { [=10=] is UILabel }
    
  2. 即使您想使用 NSPredicate,我也可能会建议简化语法:

    let labelPredicate = NSPredicate { (subview, _) -> Bool in subview is UILabel }
    
  3. 关于 if (!label) 语法,等效的 Swift 语法是

    if label == nil { ... }
    

    您不能在 Swift 中对任何随机类型使用 ! 运算符。显式测试可选值是否为 nil

  4. 顺便说一下,构建标签数组以查看是否有标签的过程效率有点低。您可以在不使用 reduce:

    构建数组的情况下测试标签是否存在
    let hasLabel = subViews.reduce(false) { alreadyFoundLabel, subview in alreadyFoundLabel || subview is UILabel }
    if !hasLabel { ... }
    

    或者,您可以在程序上循环遍历子视图:

    var hasLabel = false
    for control in subViews {
        println(control)
        if control is UILabel {
            hasLabel = true
            break;
        }
    }
    if !hasLabel { ... }
    

    显然,如果您出于其他目的需要标签数组,那么一定要构建它。但是仅仅为了检查某物是否存在而构建一个数组是低效的。

正如您在原始问题中所写,您在 Swift 中犯了一个语法错误。条件从句不放在括号中。

正如我们的评论中所写,Swift 正在抱怨,因为它无法使用 != 运算符将 UILabel 与 nil 进行比较。

当您在过滤数组后使用 as! [UILabel] 时,您保证 Swift labels 数组将包含 UILabel。然后在下一行中,您将 labels[0] 分配给 label。通过询问索引 0 处的成员,您保证 Swift 索引 0 处确实会有一个成员。所以最后,当您尝试检查 label 是否为 nil 时,Swift是说 "it cannot be, because you told me so." 它知道 label 不能为 nil,并且拒绝比较。

有很多方法可以改善这一点,但最简单的方法是更改​​:

let labels = // ...
let label = labels[0]
if label == nil {
    // ...
}

let labels = // ...
let label: UILabel                // Declare label, but do not assign in yet

if let lbl = labels.first {
    label = lbl                   // Assign the UILabel in index 0
} else {
    label = UILabel(frame: view.bounds)    // Create a new UILabel
    // ...
}

// Do whatever with label

在此示例中,我们声明 label 将是一个 UILabel,但尚未为其分配任何内容。我们将其声明在 if let / else 的范围之外,以便之后可用。 Swift 在我们分配给它之前不会让我们做任何事情,但没关系。

接下来我们检查labels索引0中是否有标签。如果有,则将其分配给label。如果没有,我们将创建一个新标签并进行设置。

在我们将 UILabel 分配给标签后,您可以按计划继续。


我强烈建议继续阅读 Optionals and Optional Chaining,因为它们是 Swift 的核心,并且对您构建代码的方式有很大影响。