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

# はじめに
[iOS Advent Calendar 2020](https://qiita.com/advent-calendar/2020/ios) の18日目になります！

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

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

>**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のメリット
![スクリーンショット 2020-12-16 2.09.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416607/a0587df3-1e83-e65a-6bed-b6a65d57cf86.png)

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

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

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

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

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

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

## 3-1. 完成画面
![uimenu.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416607/bbbab160-b4dc-0c79-f52a-55741b97924c.gif)

## 3-1-1. 下準備

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

```swift

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を設定

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

```swift

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`内で呼び出す様にします。

```swift

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`を使用することで実装することができます。

![uimenu_load.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/416607/ba44818c-da4b-757e-bfd1-350de35fc1ed.gif)

```swift

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用に処理を書き忘れてしまうと、クラッシュする原因にもなるのでこの辺り考えなくて良くなるのは嬉しい部分ではありました。

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