33
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Errorをいい感じにUIAlertControllerで表示する

Last updated at Posted at 2020-05-07

iOSでエラーが起きた場合、UIAlertControllerに表示することは珍しくないですが、その際に発生するErrorを元にしていい感じにUIAlertControllerに表示します。

「いい感じ」の定義

「いい感じ」の定義としては、macOSで使われているAppKitのNSAlertを参考にします。
NSAlertというのはこんな感じのやつです。
messageText.png

NSAlertの中にはmessageText,informativeTextが表示され、ボタンが存在します。
大体UIAlertControllerのtitle,messageと同じ形に見えませんか?

NSAlertにはErrorを渡して生成することによって、これらの要素が自動でつくので、同様にUIAlertControllerでも、Errorを渡すだけで各種パラメータが設定されるようにします。

UIAlertControllerの表示

NSAlertのmessageTextは、NSErrorのlocalizedDescriptioninformativeTextには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に、recoverySuggestionlocalizedRecoverySuggestionになります。

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を作成してみました。
delegatedidRecoverSelector を設定することでリカバリー時の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 スクリーンショット 2020-05-07 9.06.36.png Simulator Screen Shot - iPhone 11 Pro Max - 2020-05-07 at 09.05.34.png
LocalizedError スクリーンショット 2020-05-07 9.08.13.png Simulator Screen Shot - iPhone 11 Pro Max - 2020-05-07 at 09.08.19.png
LocalizedError & RecoverableError スクリーンショット 2020-05-07 9.10.57.png Simulator Screen Shot - iPhone 11 Pro Max - 2020-05-07 at 09.10.23.png

まとめ

つかおう!LocalizedError!

33
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?