本投稿は TECOTEC Advent Calendar 2022 の10日目の記事です。
おはこんばんちは、証券フロンティア事業部の赤池です。
凍えそうな季節で愛をどーこー云いたい気分になったりしますが、皆様いかがお過ごしでしょうか。
私は自宅のPCの排熱がかなりあるせいで、部屋の中が温かい、というか暑いくらいです。
というわけで今回はiOSのContext Menusについて書いていこうかなと思います。
Context Menusってなんぞやって方のために参考画像を。こんなやつです。
ボタンを押すと、ポップオーバーみたいな感じで出てくるメニューですね。
今回はこのContext Menusについて、簡単な使い方を紹介していこうかなと思います。
動作環境はXcode 14.1(14B47b)、使っているシミュレータはiPhone 14 Pro Max(iOS16.1)です
動機
まず、なんで今さらContext Menusについて書くかというと、実装中のプロジェクトのiOS12サポートが終わって、iOS13以降限定の機能がようやく使えるようになったというのが1点。
プロジェクトの新機能でこんなの使えるんじゃね、ということで試しにやってみようとおもったというのがもう1点。
完全にノリでやってみようと思った次第ですね、はい。
Context Menusの実装方法
では早速、Context Menusの実装について説明しましょう。この方法が一番簡単だと思います、ということでまずはボタンをタップしたらUIContextMenuが表示されるというシンプルなやつです。
とりあえずプロジェクト作って、最初からあるViewControllerの真ん中にUIButtonがあるという設定です。
class ViewController: UIViewController { @IBOutlet weak var button: UIButton! override func viewDidLoad() { super.viewDidLoad() setupButton() } private func setupButton() { let saladMenu = UIAction(title: "シェフの気まぐれサラダ", image: UIImage(systemName: "carrot.fill")) { (action) in print("シェフの気まぐれサラダ") } let pastaMenu = UIAction(title: "イタズラ妖精のきのこパスタ", image: UIImage(systemName: "leaf.fill")) { (action) in print("イタズラ妖精のきのこパスタ") } let menu = UIMenu(title: "本日のメニュー", image: nil, identifier: nil, options: .displayInline, children: [saladMenu, pastaMenu]) button.menu = menu button.showsMenuAsPrimaryAction = true } }
こんな感じで書いてアプリを実行してボタンを押すと、こんな感じになります。
画像のキャプションにも書きましたが、最初に見せた画像と微妙に違いますね。
なぜかというと、これはiOS14以降で使える、UIButton用のUIMenu追加ロジックを使っているからですね。
今から新しいアプリを作る分にはこれで問題ないですが、私が関わっているプロジェクトのようにiOS13も対応していると話が違います。
ではその場合、どうするのか? 答えはUIContextMenuInteractionDelegateを使った実装をします。
class ViewController: UIViewController { @IBOutlet weak var button: UIButton! private var index = 0 override func viewDidLoad() { super.viewDidLoad() setupButton() } private func setupButton() { let interaction = UIContextMenuInteraction(delegate: self) button.addInteraction(interaction) } @IBAction func buttonAction(_ sender: Any) { print("\(index)") index += 1 } } extension ViewController: UIContextMenuInteractionDelegate { func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? { let actionProvider: ([UIMenuElement]) -> UIMenu? = { _ in let saladMenu = UIAction(title: "シェフの気まぐれサラダ", image: UIImage(systemName: "carrot.fill")) { (action) in print("シェフの気まぐれサラダ") } let pastaMenu = UIAction(title: "イタズラ妖精のきのこパスタ", image: UIImage(systemName: "leaf.fill")) { (action) in print("イタズラ妖精のきのこパスタ") } let menu = UIMenu(title: "本日のメニュー", image: nil, identifier: nil, children: [saladMenu, pastaMenu]) return menu } return UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: actionProvider) } }
このコードでアプリを実行して、ボタンを長押しすると最初の画像のような感じになると思います。
短いタップだとIBActionの中が実行されるので、普通のタップはIBAction、ロングタップはContext Menu表示、みたいな場合分けも可能になります。
※ちなみにシステムアイコンの一覧は、↓の公式アプリをインストールすると確認することが出来ます developer.apple.com
ちょっとしたカスタマイズ
Context Menusの中に、更にContext Menusを実装することも出来ます。↑のコードのextension部分をこんな感じに書き換えてみます。
extension ViewController: UIContextMenuInteractionDelegate { func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? { llet actionProvider: ([UIMenuElement]) -> UIMenu? = { _ in let saladMenu = UIAction(title: "シェフの気まぐれサラダ", image: UIImage(systemName: "carrot.fill")) { (action) in print("シェフの気まぐれサラダ") } let mainMenu: UIMenu = { let pastaMenu = UIAction(title: "イタズラ妖精のきのこパスタ", image: UIImage(systemName: "leaf.fill")) { (action) in print("イタズラ妖精のきのこパスタ") } let fishMenu = UIAction(title: "サーモンと帆立貝柱のキャベツ包み蒸し", image: UIImage(systemName: "fish.fill")) { (action) in print("サーモンと帆立貝柱のキャベツ包み蒸し") } let menu = UIMenu(title: "メインメニュー", image: UIImage(systemName: "list.bullet.clipboard.fill"), identifier: nil, children: [pastaMenu, fishMenu]) return menu }() let menu = UIMenu(title: "本日のメニュー", image: nil, identifier: nil, children: [saladMenu, mainMenu]) return menu } return UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: actionProvider) } }
これを実行すると、まずは↓のように表示されます。
更にメインメニューを開いてみると
こんなふうに表示されました。
こういった実装も簡単にできるので、ついつい調子に乗って何個も入れ子にしたくなっちゃいますが、Context MenusのHuman Interface Guideの中に、「サブメニューを使うときは一階層にとどめてね♡(意訳)」とあるので、一階層だけにしておきましょう。
欠点
割と便利なContext Menusですが、欠点ももちろんあります。
それは、CustomViewを使えないこと。
もちろん、普通に使う分にはこれで問題ないわけなので、拡張性を無くすというのも解りますが、もうちょっと手を加えさせてほしいですね。
なので見た目をもっと変えたいという場合はちょっと違いますが、UIPopoverPresentationControllerを使いましょう。
おわりに
ということで今回はiOSのContext Menusについて簡単に紹介してきました。
こういったちょっと前までは作るのが面倒だったものも、最近はApple側が用意してくれるようになったのでだいぶ楽になりましたね。
今後もこんな感じにどんどん、簡単なものを提供してもらいたいと思いつつ、カスタムの余地を残してくれると嬉しいです。
ではまたどこかでお会いしましょうノシ