iOS14から使用できるUIMenuの実装について


はじめに

iOS Advent Calendar 2020 の18日目になります!

今回は、WWDC20にて紹介されていましたUIMenuの新機能について焦点を当ててみようと思います。

UIMenu自体はiOS13から使用できるのですが、
iOS14からはUIButtonUIBarButtonItemにも使用可能となった内容がありましたので、おさらい的な紹介ができればと思います🙇‍♂️
とはいっても、すでにとても分かりやすくまとめられている記事がありましたので、細かい説明は割愛させていただきます。
本記事では、実際にコードで書いてみて使い心地を感じられればと思います。

UIMenu
https://developer.apple.com/documentation/uikit/uimenu
WWDC20
https://developer.apple.com/videos/play/wwdc2020/10205

1. 新機能について

すでに分かりやすくまとめられている記事が多々ありますのでリンクを貼らせていただきました🙇‍♂️
(これ以上上手にまとめられる気がしませんでした。。)
https://medium.com/better-programming/whats-new-in-ios-14s-uimenu-and-contextmenu-433cd2037c37

2. UIMenuのメリット

UIMenuと比較する機能としてUIAlertController(ActionSheet)が挙げられると思います。
WWDC20でもこちらを比較して紹介されていましたので以下にまとめてみました。

UIMenuのメリットとして以下が挙げられていました。

  • iPad表示するためにポップオーバー表示するための実装をする必要がない
  • 表示の際に背景を暗くする処理がなくなったため、その分軽量感のある遷移になる
  • 閉じるための「キャンセル」ボタンが不要(Menu外をタップすると閉じる)
  • タップした箇所からMenuが表示されるので操作しやすい(操作性) etc...

3. 実際に書いてみました

「百聞は一見にしかず」と言うことで、実際にコードを書いてみました。

  • [3-1] HIGH, MID, LOWを切り替える
  • [3-1] UIMenuを開いた際には、選択した項目にチェックマークがついている
  • [3-2] UIMenuを非同期で構築する場合

と言う内容を実装してみたいと思います。

3-1. 完成画面

3-1-1. 下準備

UIMenuを設定する前に、設定するためのUIButtonなどを下準備します。


class ViewController: UIViewController {
    // メニュー表示項目
    enum MenuType: String {
        case high = "HIGH"
        case mid = "MID"
        case low = "LOW"
    }
    // メニュー選択ボタン
    @IBOutlet weak var menuButton: UIButton!

    // 選択されたMenuType
    var selectedMenuType = MenuType.high

    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

3-1-2. UIButtonにUIMenuを設定

UIMenuUIButtonに設定するために、以下のメソッドを作成しました。


private func configureMenuButton() {
    var actions = [UIMenuElement]()
    // HIGH
    actions.append(UIAction(title: MenuType.high.rawValue, image: nil, state: self.selectedMenuType == MenuType.high ? .on : .off,
                            handler: { (_) in
                                self.selectedMenuType = .high
                                // UIActionのstate(チェックマーク)を更新するためにUIMenuを再設定する
                                self.configureMenuButton()
                            }))
    // MID
    actions.append(UIAction(title: MenuType.mid.rawValue, image: nil, state: self.selectedMenuType == MenuType.mid ? .on : .off,
                            handler: { (_) in
                                self.selectedMenuType = .mid
                                // UIActionのstate(チェックマーク)を更新するためにUIMenuを再設定する
                                self.configureMenuButton()
                            }))
    // LOW
    actions.append(UIAction(title: MenuType.low.rawValue, image: nil, state: self.selectedMenuType == MenuType.low ? .on : .off,
                            handler: { (_) in
                                self.selectedMenuType = .low
                                // UIActionのstate(チェックマーク)を更新するためにUIMenuを再設定する
                                self.configureMenuButton()
                            }))

    // UIButtonにUIMenuを設定
    menuButton.menu = UIMenu(title: "", options: .displayInline, children: actions)
    // こちらを書かないと表示できない場合があるので注意
    menuButton.showsMenuAsPrimaryAction = true
    // ボタンの表示を変更
    menuButton.setTitle(self.selectedType.rawValue, for: .normal)
}

3-1-3. 完成

あとは、作成したメソッドをviewDidLoad内で呼び出す様にします。


class ViewController: UIViewController {
    // メニュー表示項目
    enum MenuType: String {
        case high = "HIGH"
        case mid = "MID"
        case low = "LOW"
    }

    @IBOutlet weak var selectButton: UIButton!

    // 選択されたMenuType
    var selectedType = MenuType.high

    override func viewDidLoad() {
        super.viewDidLoad()

        // UIButtonにUIMenuを設定する
        self.configureMenuButton()
    }
}

3-2. 非同期でUIMenuを構築する場合

非同期でUIMenuの構築が必要な場合は、UIDeferredMenuElementを使用することで実装することができます。


var actions = [UIMenuElement]()

let deferredMenuElement = UIDeferredMenuElement({ completion in
    // 時間がかかる処理
   ....
    completion(actions)
})
menuButton.menu = UIMenu(title: "UIDeferredMenuElement", options: .displayInline, children: [deferredMenuElement])
menuButton.showsMenuAsPrimaryAction = true 

UIDeferredMenuElement
https://developer.apple.com/documentation/uikit/uideferredmenuelement

4. まとめ

これらの実装は、iOS14から対応なので実際にはiOS13以下の場合の分岐処理を書かなければならず、面倒ではあります。
ただ、操作感が良くや使い所が多岐に渡りそうな気がするのでとても魅力的な機能でした。
個人的にはUIAlertController(ActionSheet)実装時にiPad用に処理を書き忘れてしまうと、クラッシュする原因にもなるのでこの辺り考えなくて良くなるのは嬉しい部分ではありました。

実装してみましたが、説明やコードで誤りなどございましたらご指摘いただけると幸いです。
ご協力のほどよろしくお願いいたします🙇‍♂️