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
  • 216,610
  • 56
  • 458
  • 536
Jordan H
  • 48,988
  • 36
  • 182
  • 321

7 Answers7

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
  • 216,610
  • 56
  • 458
  • 536
  • 4
    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
56

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.
  • 2,685
  • 24
  • 40
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,202
  • 4
  • 44
  • 45
  • 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
12

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,016
  • 1
  • 21
  • 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
  • 8
    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

Use

error._code == NSURLErrorCancelled

to match the error code.

Gurjit Singh
  • 1,567
  • 17
  • 23
1

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

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

Codetard
  • 2,274
  • 26
  • 33
0
enum CheckValidAge : Error{
    case overrage
    case underage
}

func checkValidAgeForGovernmentJob(age:Int)throws -> Bool{
    if age < 18{
        throw CheckValidAge.underage
    }else  if age > 25{
        throw  CheckValidAge.overrage
    }else{
        return true
    }
}

do {
    try checkValidAgeForGovernmentJob(age: 17)
    print("You are valid for government job ")
}catch CheckValidAge.underage{
    print("You are underage for government job ")
}catch CheckValidAge.overrage{
    print("You are overrage for government job ")
}

try checkValidAgeForGovernmentJob(age: 17) OutPut : You are underage for government job

try checkValidAgeForGovernmentJob(age: 26) OutPut : You are overrage for government job

try checkValidAgeForGovernmentJob(age: 18) OutPut : You are valid for government job