[SwiftUI]長押しでメニュー表示

目次

概要

LINEのユーザー一覧のセルを長押しするとメニューを表示することができる。
加えて、UIのタップ処理とは別に機能を持たせたい時、ロングタップは良く使われるが、
そのロングタップの動作でも複数の機能を持たせたい時がある。

そんな時は長押しした時にメニューを表示させて、複数の操作ができるようにしよう。
メニューを表示した方がユーザーからもわかりやすくなるはず。

ソースコード

アプリの最初の実行部分

TestApp.swift
import SwiftUI

@main
struct TestApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    var body: some Scene {
        WindowGroup {
            ListMenuView()
        }
    }
}
Swift

View表示部分

ListMenuView.swift
import SwiftUI

struct ListMenuView: View {
    
    @ObservedObject private var viewModel: ListMenuViewModel = ListMenuViewModel()
    
    var body: some View {
        NavigationView {
            VStack {
                listView()
            }
            .navigationTitle("リストの備忘録動作確認")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    listOrderMenu()
                }
            }
        }
    }
    
    private func listView() -> some View {
        return List {
            ForEach(Array(viewModel.dataList.enumerated()), id: \.offset) { index, data in
                Button {
                    print("\(data)をタップしました。")
                } label: {
                    Text(data)
                }
                .contextMenu(menuItems: {
                    Button {
                        print("編集する?")
                    } label: {
                        Text("編集")
                    }
                    Button {
                        viewModel.deleteData(index: index)
                    } label: {
                        Text("削除")
                            .foregroundColor(.red)
                    }
                })
            }
        }
    }
    
    private func listOrderMenu() -> some View {
        return Menu {
            ForEach(DataOrder.allCases, id:\.self) { dataOrder in
                Button {
                    viewModel.sortDataOrder(selectedOrder: dataOrder)
                } label: {
                    HStack {
                        if dataOrder == viewModel.selectedOrder {
                            Image(systemName: "checkmark")
                        }
                        Text(dataOrder.rawValue)
                    }
                }
            }
        } label: {
            Image(systemName: "arrow.up.and.down.text.horizontal")
        }
    }
}

Swift

処理部分

ListMenuViewModel.swift
import Foundation

enum DataOrder: String, CaseIterable, Identifiable {
    case titleAscendingOrder = "項目名の昇順"
    case titleDescendingOrder = "項目名の降順"
    
    var id: String { rawValue }
}

class ListMenuViewModel: ObservableObject {
    @Published private(set) var dataList: [String]
    @Published private(set) var selectedOrder: DataOrder = .titleAscendingOrder
    
    init() {
        dataList = []
        for index in 0 ..< 20 {
            dataList.append("データ\(index)")
        }
    }
    
    func sortDataOrder(selectedOrder: DataOrder) {
        self.selectedOrder = selectedOrder
        switch selectedOrder {
        case .titleAscendingOrder:
            dataList = dataList.sorted(by: {$0 < $1})
        case .titleDescendingOrder:
            dataList = dataList.sorted(by: {$1 < $0})
        }
    }
    
    func deleteData(index: Int) {
        self.dataList.remove(at: index)
    }
}
Swift

スクリーンショット

詳細

長押しした時に表示されるメニュー

こちらの実装部分は以下のような処理で行える

.contextMenu(menuItems: {
    // ここにメニューとして表示させたいUIを記載していく
})

ここに今回の例のようにButtonを羅列していくと動画のようなメニューを表示することができる。
ただし、この時タップした項目はNavigationLinkによる画面遷移ができない様子。(要調査)
代わりに、フラグやデータの編集はできるため、以下のことは実装可能

  • ダイアログを表示する
  • 格納しているデータを編集して表を更新する(削除なども可能)

アイコンをタップした時に表示されるメニュー

こちらの実装はMenuを使用する。Menuの書き方の一つとして以下のように書く。

Menu {
    // 表示したいUIを羅列する
} label: {
    // View上の表示を設定する
}

今回の例では、
「表示したいUI」の部分にメニューの内容と各々をタップした時の処理を、
「View上の表示」の部分にボタンのアイコンを書いている。

また、Pickerでメニューを表示することもできるが、View上の表示が選択したメニューの内容になる。これはXcode上のバグと言われているが、これを解決する手段としてMenuで実装するというのが挙げられる。
また、Menuの場合はそのままではどれを選択しているかわからないので、以下のようにチェックマークを入れることで現在の設定がわかるようにしている。

HStack {
    if dataOrder == viewModel.selectedOrder {
        Image(systemName: "checkmark")
    }
    Text(dataOrder.rawValue)
}

参考ページ:
Qiita「SwiftUIのドロップダウン、ホイール、メニュー、Picker」
Qiita「【SwiftUI】長押しでメニューを表示する」
カピ通信「【SwiftUI】Menuの使い方」