Search code examples
swiftswift3error-handlingnslocalizedstring

How to provide a localized description with an Error type in Swift?


I am defining a custom error type with Swift 3 syntax and I want to provide a user-friendly description of the error which is returned by the localizedDescription property of the Error object. How can I do it?

public enum MyError: Error {
  case customError

  var localizedDescription: String {
    switch self {
    case .customError:
      return NSLocalizedString("A user-friendly description of the error.", comment: "My error")
    }
  }
}

let error: Error = MyError.customError
error.localizedDescription
// "The operation couldn’t be completed. (MyError error 0.)"

Is there a way for the localizedDescription to return my custom error description ("A user-friendly description of the error.")? Note that the error object here is of type Error and not MyError. I can, of course, cast the object to MyError

(error as? MyError)?.localizedDescription

but is there a way to make it work without casting to my error type?


Solution

  • As described in the Xcode 8 beta 6 release notes,

    Swift-defined error types can provide localized error descriptions by adopting the new LocalizedError protocol.

    In your case:

    public enum MyError: Error {
        case customError
    }
    
    extension MyError: LocalizedError {
        public var errorDescription: String? {
            switch self {
            case .customError:
                return NSLocalizedString("A user-friendly description of the error.", comment: "My error")
            }
        }
    }
    
    let error: Error = MyError.customError
    print(error.localizedDescription) // A user-friendly description of the error.
    

    You can provide even more information if the error is converted to NSError (which is always possible):

    extension MyError : LocalizedError {
        public var errorDescription: String? {
            switch self {
            case .customError:
                return NSLocalizedString("I failed.", comment: "")
            }
        }
        public var failureReason: String? {
            switch self {
            case .customError:
                return NSLocalizedString("I don't know why.", comment: "")
            }
        }
        public var recoverySuggestion: String? {
            switch self {
            case .customError:
                return NSLocalizedString("Switch it off and on again.", comment: "")
            }
        }
    }
    
    let error = MyError.customError as NSError
    print(error.localizedDescription)        // I failed.
    print(error.localizedFailureReason)      // Optional("I don\'t know why.")
    print(error.localizedRecoverySuggestion) // Optional("Switch it off and on again.")
    

    By adopting the CustomNSError protocol the error can provide a userInfo dictionary (and also a domain and code). Example:

    extension MyError: CustomNSError {
    
        public static var errorDomain: String {
            return "myDomain"
        }
    
        public var errorCode: Int {
            switch self {
            case .customError:
                return 999
            }
        }
    
        public var errorUserInfo: [String : Any] {
            switch self {
            case .customError:
                return [ "line": 13]
            }
        }
    }
    
    let error = MyError.customError as NSError
    
    if let line = error.userInfo["line"] as? Int {
        print("Error in line", line) // Error in line 13
    }
    
    print(error.code) // 999
    print(error.domain) // myDomain