75

New in Xcode 8 beta 4, NSError is bridged to the Swift Error protocol type. This affects StoreKit when dealing with failed SKPaymentTransactions. You ought to check to be sure the error didn't occur because the transaction was cancelled to know whether or not to show an error message to the user. You do this by examining the error's code. But with Error instead of NSError, there is no code defined. I haven't been able to figure out how to properly get the error code from Error.

This worked in the previous version of Swift 3:

func failedTransaction(_ transaction: SKPaymentTransaction) {
    if let transactionError = transaction.error {
        if transactionError.code != SKErrorCode.paymentCancelled.rawValue {
            //show error to user
        }
     }
     ...
}

Now that error is an Error not NSError, code is not a member.

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
Jordan H
  • 52,571
  • 37
  • 201
  • 351

6 Answers6

141

Another option to access code and domain properties in Swift 3 Error types is extending it as follow:

extension Error {
    var code: Int { return (self as NSError).code }
    var domain: String { return (self as NSError).domain }
}
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • 5
    Check the answer of @murray-sagal, it is better to use the newly provided SKError, instead of switching back to Objective-C objects. – o15a3d4l11s2 Nov 03 '16 at 09:01
57

Now in Xcode 8 and swift 3 the conditional cast is always succeeds, so you need do following:

let code = (error as NSError).code

and check the code for your needs. Hope this helps

Andrey M.
  • 3,021
  • 29
  • 42
42

Casting to SKError seems to be working for me in xCode 8 and Swift 3...

    guard let error = transaction.error as? SKError else {return}
    switch error.code {  // https://developer.apple.com/reference/storekit/skerror.code
    case .unknown: break
    case .paymentCancelled: break
    case .clientInvalid: break
    case .paymentInvalid: break
    case .paymentNotAllowed: break
    case .cloudServiceNetworkConnectionFailed: break
    case .cloudServicePermissionDenied: break
    case .storeProductNotAvailable: break
    }

No need for rawValue.

Murray Sagal
  • 8,454
  • 4
  • 47
  • 48
  • Updating the correct answer to this one as it is the most correct. Much preferred to do this instead of casting to `NSError`. Confirmed it works. – Jordan H Nov 25 '16 at 06:37
  • Ain't that great? I have the very same problem but in ALAssets. But there does not seem to be an SKError equivalent in there – Anton Tropashko Apr 29 '17 at 12:20
  • @Joey Apple had removed SKError from the iOS 11 public API on Xcode 9 betas 1 and 2. This was a bug that has been fixed on Xcode 9 beta 3. Now will have to use "SwiftyStoreKit"! – Anirudha Mahale Mar 23 '18 at 10:31
11

This is correct (Apple's own tests use this approach):

if error._code == SKError.code.paymentCancelled.rawValue { ... }

On the other hand, casting to NSError will probably be deprecated soon:

let code = (error as NSError).code // CODE SMELL!!
if code == SKError.code.paymentCancelled.rawValue { ... }
Rob
  • 5,534
  • 1
  • 22
  • 22
  • Is there a link to the approach used by Apples own tests? – Andrew Paul Simmons Dec 06 '16 at 19:40
  • Well, @AndrewPaulSimmons there wasn't a nice clean link but if you check out Apple's Git repo for Swift and then search for "_code", you can see all their own tests where they deal with the Error object in that way. Check it out: https://github.com/apple/swift/search?utf8=%E2%9C%93&q=_code – Rob Dec 07 '16 at 13:31
  • 9
    You consider casting to the correct class a code smell, but externally accessing underscored members is fine? Granted you should be accessing `.code` in an `if let` or a `guard let`, not just blindly assuming the cast worked. – devios1 Dec 17 '16 at 20:36
  • You're right, @devios1. "Code smell" was a dumb way to put it. – Rob Dec 19 '16 at 14:04
  • 1. The `Error` protocol doesn't have a `_code` member, thus this code would fail to compile; 2. Casting to `NSError` is still permitted, 6 years later. – Cristik Sep 21 '22 at 04:59
0

Use

error._code == NSURLErrorCancelled

to match the error code.

Gurjit Singh
  • 1,723
  • 21
  • 27
  • where did you find this way...? this is working ! but I wanna know the place which you got to know. – YodagamaHeshan Apr 21 '20 at 04:57
  • I just explore the Error protocol in Xcode where did found that one. – Gurjit Singh Apr 25 '20 at 17:05
  • I didn't get. Could you please explain a bit. In Error protocol definition i didn't find even one word for Error._code. – YodagamaHeshan Apr 26 '20 at 00:58
  • The `Error` protocol has no requirements, thus the compiler should not allow you to call anything on a reference to just `Error`. Your code must've been different if it allowed you to use `_error`. – Cristik Sep 21 '22 at 04:55
0

A lot is changing. Here's update for Xcode 11.

if let skError = transaction.error as? SKError, skError.code == .paymentCancelled { print("Cancelled") }

Codetard
  • 2,441
  • 28
  • 34
  • This is repeating the solution given [3 years before](https://stackoverflow.com/a/40273317), of casting to `SKError` and accessing the code afterwards. – Cristik Sep 21 '22 at 04:56