【Swift】iPadでactionSheetを使用するとクラッシュしたので対応した

2021.12.12

iPadUIAlertControllerpreferredStyle: .actionSheetを指定して表示しようとするとクラッシュしたので対処方法を調べました。

開発環境

  • Xcode 13
  • Swift 5.5

エラーメッセージ

クラッシュしてこのようなエラーメッセージが吐かれました。

Thread 1: "Your application has presented a UIAlertController () of style UIAlertControllerStyleActionSheet from UINavigationController (). The modalPresentationStyle of a UIAlertController with this style is UIModalPresentationPopover. You must provide location information for this popover through the alert controller's popoverPresentationController. You must provide either a sourceView and sourceRect or a barButtonItem. If this information is not known when you present the alert controller, you may provide it in the UIPopoverPresentationControllerDelegate method -prepareForPopoverPresentation."

翻訳

  • あなたのアプリケーションはUIAlertControllerのスタイルであるActionSheetUINavigationControllerから提示しました。
  • このスタイルのUIAlertControllermodalPresentationStyleUIModalPresentationPopoverです。
  • あなたはこのポップオーバーの為にAlertControllerpopoverPresentationControllerを通してロケーション情報を提供しなければなりません。
  • あなたはsourceViewsourceRectもしくはbarButtonItemのいずれかを提供しなければなりません。
  • AlertControllerを提示した時にこの情報が分からない場合は、あなたはUIPopoverPresentationControllerDelegateprepareForPopoverPresentationでその情報を提供するかもしれません。

要するに

ActionSheetをポップオーバーする為には位置情報が必要なので提供してくれないと表示できないよ。ということです。

その位置情報は、下記のいずれかを使用して提供しましょう。

  • sourceViewsourceRect
  • barButtonItem

エラー対応

今回はUIBarButtonItemでのクラッシュ対応だった為、barButtoItemを使用して対応します。

barButtonItem

エラーで吐かれた指示通り、popoverPresentationController.barButtonItemsender(UIBarButtonItem)の値を渡してあげました。

actionSheetController.popoverPresentationController?.barButtonItem = sender

このbarButtonItemは、ポップオーバーのアンカーをどのbarButtonItemにするかを決めるもので、渡すUIBarButtonItemをアンカーに表示することが出来ます。

@IBAction private func showActionSheetButtonTapped(_ sender: UIBarButtonItem) {

    let actionSheetController = UIAlertController(title: "クイズ",
                                            message: "これはアクションシートですか?",
                                            preferredStyle: .actionSheet)
    actionSheetController.popoverPresentationController?.barButtonItem = sender

    let yesAction = UIAlertAction(title: "はい", style: .default)
    let noAction = UIAlertAction(title: "いいえ", style: .default)
    actionSheetController.addAction(yesAction)
    actionSheetController.addAction(noAction)

    present(actionSheetController, animated: true)
}

クラッシュせずに表示されました。

scoureViewとscoureRect

上記はBarButtonItemでしたが、その他の場合の対応としてUIButtonのケースを例にして対応してみます。

このような実装を行いました。

@IBAction func showActionSheetButtonTapped(_ sender: UIButton) {
    let actionSheetController = UIAlertController(title: "クイズ",
                                                  message: "これはアクションシートですか?",
                                                  preferredStyle: .actionSheet)
    actionSheetController.popoverPresentationController?.sourceView = sender.superview
    actionSheetController.popoverPresentationController?.sourceRect = sender.frame

    let yesAction = UIAlertAction(title: "はい", style: .default)
    let noAction = UIAlertAction(title: "いいえ", style: .default)
    actionSheetController.addAction(yesAction)
    actionSheetController.addAction(noAction)

    present(actionSheetController, animated: true)
}

sourceView

こちらはポップオーバーの為のアンカーを持っているViewになります。 ここでどのViewをアンカーにするか指定することが出来ます。

今回はsender.superviewをアンカーにしています。

actionSheetController.popoverPresentationController?.sourceView = sender.superview

sourceRect

こちらはsourceViewをアンカーに表示するCGRectになります。 今回はsenderの箇所にポップオーバーで表示したいので、sender.frameを渡しています。

actionSheetController.popoverPresentationController?.sourceRect = sender.frame

こちらもクラッシュせずに表示されました。

おわりに

こちらのクラッシュはiPadでは発生しますが、iPhone端末では発生しない現象でした。 iPhoneでしか検証していない場合は発見することが出来なかったので、改めて複数の端末、iPhoneやiPadでの検証は大事だと感じました。 また、今回はエラーメッセージにしっかりと答えが書かれていたので出されたエラーメッセージをしっかり読むことの大事さを改めて感じました。

この記事が誰かの救いになればと思います🎤

参考