iOSでエラーが起きた場合、UIAlertControllerに表示することは珍しくないですが、その際に発生するErrorを元にしていい感じにUIAlertControllerに表示します。
「いい感じ」の定義
「いい感じ」の定義としては、macOSで使われているAppKitのNSAlert
を参考にします。
NSAlertというのはこんな感じのやつです。
NSAlertの中にはmessageText
,informativeText
が表示され、ボタンが存在します。
大体UIAlertControllerのtitle
,message
と同じ形に見えませんか?
NSAlertにはErrorを渡して生成することによって、これらの要素が自動でつくので、同様にUIAlertControllerでも、Errorを渡すだけで各種パラメータが設定されるようにします。
UIAlertControllerの表示
NSAlertのmessageText
は、NSErrorのlocalizedDescription
、informativeText
にはNSErrorのlocalizedRecoverySuggestion
が使われています。よって、ErrorをNSErrorにキャストしてこれらのパラメータを割り当てればOKです。
let nsError = error as NSError
let alert = UIAlertController(title: nsError.localizedDescription,
message: nsError.localizedRecoverySuggestion,
preferredStyle: .alert)
LocalizedErrorを生成する
実際にNSErrorのパラメータがわかったところで、生成されるエラーがこの仕様に準拠していなければ意味がありませんので、この仕様に準拠したエラーを生成します。
NSErrorを生成し、NSLocalizedDescriptionKey
などをUserInfoに設定するのもいいのですが、Swiftであれば LocalizedError
を使うことで準拠が可能です。
LocalizedErrorに含まれる、errorDescription
は、NSErrorにキャストするとlocalizedDescription
に、recoverySuggestion
はlocalizedRecoverySuggestion
になります。
enum MyError: LocalizedError {
case error1
var errorDescription: String? { return "errorDescription" }
var recoverySuggestion: String? { return "recoverySuggestion" }
}
RecoverableErrorでリカバリー対応する
NSAlertにRecoverableError
に適合したエラーを渡して生成すると、リカバリーオプションボタンのついたNSAlertを生成できます。
これらのボタンを押下すると、RecoverableError内で実装したリカバリー処理が動くので、こちらもUIAlertControllerに再現してみましょう。
先ほどのLocalizedErrorも含めて、macOSのpresentError
のように、UIViewControllerのクラスでエラーを表示できるextensionを作成してみました。
delegate
や didRecoverSelector
を設定することでリカバリー時のreturnを取得することができます。
extension UIViewController {
func presentError(_ error: Error,
delegate:Any? = nil,
didRecoverSelector:Selector? = nil,
contextInfo: UnsafeMutableRawPointer? = nil) {
let nsError = error as NSError
let alert = UIAlertController(title: nsError.localizedDescription,
message: nsError.localizedRecoverySuggestion,
preferredStyle: .alert)
if let localizedRecoveryOptions = nsError.localizedRecoveryOptions,
!localizedRecoveryOptions.isEmpty,
let attempter: AnyObject = nsError.recoveryAttempter as AnyObject? {
for (i, value) in localizedRecoveryOptions.enumerated() {
let alertAction = UIAlertAction(title: value, style: .default) { _ in
attempter.attemptRecovery(fromError: error,
optionIndex: i,
delegate: delegate,
didRecoverSelector: didRecoverSelector,
contextInfo: contextInfo)
}
alert.addAction(alertAction)
}
} else {
alert.addAction(UIAlertAction(title: NSLocalizedString("ok", value: "OK", comment: "エラーアラートのデフォルトボタン"), style: .default, handler: nil))
}
present(alert, animated: true, completion: nil)
}
}
追記:presentErrorの使い方
class ViewController: UIViewController {
@IBAction func showError(_ sender: Any) {
presentError(MyLocalizedError.error1)
}
@IBAction func showRecoverableError(_ sender: Any) {
// リカバリー付きエラーを表示
presentError(MyRecoverableError.error1, delegate: self, didRecoverSelector: #selector(didPresentErrorWithRecovery(_:contextInfo:)), contextInfo: nil)
}
/// リカバリーの結果が返ってくる
/// - Parameters:
/// - didRecover: attemptRecoveryの成功有無
/// - contextInfo: エラー回復の試行に関連する任意のデータ
@objc func didPresentErrorWithRecovery(_ didRecover:Bool, contextInfo:UnsafeMutableRawPointer?) {
}
}
見比べてみる
macOS | iOS | |
---|---|---|
Error | ||
LocalizedError | ||
LocalizedError & RecoverableError |
まとめ
つかおう!LocalizedError!