dan1eln1el5en
dan1eln1el5en

Reputation: 53

Binary operator '>' cannot be applied to two 'String?!' operands

So I have a project, that builds fine. but hen I want to Archive it it throws this error on this line of code:

let pred = NSPredicate(format: "%K in %@", "categoryID", selectedCategoryIDs!)
let selectedAlmanacEntries = almanacEntries.filter { pred.evaluate(with: $0) }.sorted(by: { ($0 as AnyObject).title > ($1 as AnyObject).title })

this hasn't been an issue before (Earlier releases). Have tried restarting Xcode and cleaning the project before. Any hints appreciated.

(Xcode 8, Swift 3)

Upvotes: 3

Views: 9021

Answers (2)

Bola Ibrahim
Bola Ibrahim

Reputation: 780

Swift 5

you can simply unwrap optional by this way

let selectedAlmanacEntries = selectedAlmanacEntries.sorted {
    var isSorted = false
    if let first = $0.title, let second = $1.title {
        isSorted = first < second
    }
    return isSorted
}

Upvotes: 8

Charles Srstka
Charles Srstka

Reputation: 17040

?! is a really great construction, isn't it? Perfectly sums up the reaction whenever you see it in an error message.

Anyway, in Swift, you're probably better off casting to the appropriate type that these objects should belong to instead of AnyObject. If you must duck type, then you should be aware that every time Swift calls a method on AnyObject, it gains a level of optionality due to the fact that Swift can't verify that the object actually responds to the message. So you will have to deal with the optionals.

The other problem is that since there exist multiple classes in the frameworks that have properties named title, Swift has no way of knowing which one to use. And some of them have different signatures; for example, NSButton has a title property that is typed as String, NSStatusItem has a title property that is typed as String?, and NSWindowTab has a title property that is typed as String!. Which one the compiler picks is a bit of a luck of the draw, which is why random chance can make it behave differently from compile to compile. So you need to help the compiler out by telling it what type to expect.

So, something like this can work:

let selectedAlmanacEntries = almanacEntries.filter { pred.evaluate(with: $0) }.sorted(by: {
    guard let first: String = ($0 as AnyObject).title else { return false }
    guard let second: String = ($1 as AnyObject).title else { return true }

    return first > second
})

Or, if your heart is set on a long one-liner as in the original:

let selectedAlmanacEntries = almanacEntries.filter { pred.evaluate(with: $0) }.sorted(by: { (($0 as AnyObject).title as String?) ?? "" > (($1 as AnyObject).title as String?) ?? "" })

I'd really recommend casting to the actual type instead, though.

Upvotes: 6

Related Questions