web-dev-qa-db-ja.com

iPadOS:ポインターを使用していないときにUIContextMenuInteractionがトリガーされないようにする

UIMenuUIContextMenuInteractionUIPointerInteraction

ファイルアプリやページアプリと同じ方法でUIContextMenuInteractionを設定しようとしています。

  • (長)空白部分をタップすると、黒い水平線UIMenuが表示されます
  • 空白スペースの任意の場所でポインターを使用してセカンダリ(右/コントロール)クリックすると、コンテキストメニューが表示されます

以下の添付GIFのデモを参照してください。

UIContextMenuInteractionを設定し、そのUIContextMenuInteractionDelegateに、表示するアイテムを含むUIContextMenuConfigurationを返すことができます。

小さな黒いUIMenuについても同じですが、UILongPressGestureRecognizerを使用して、UIMenuController.shared.showMenu

ただし、ビューを長押しすると、UIContextMenuInteractionがトリガーされてUITargetedPreviewが表示されるのを防ぐことができず、異なるUITouchTypesをUIContextMenuInteractionDelegateに提供された情報。

また、UIContextMenuInteractionがないと、プログラムでコンテキストメニューを表示する方法も見つかりませんでした。それを行う方法はありますか?

質問

これはFiles.appにどのように実装されますか?

Files.app ui menu and context menu interaction

4
Tom Kraina

プログラムでコンテキストメニューをトリガーする方法はありませんが、簡単な簿記を使用すると、不要な場合(レスポンダーでタッチがアクティブな場合など)に表示されないようにすることができます。

プレビューを非表示にするには、UIContextMenuConfigurationの初期化子でpreviewProviderからnilを返します。

以下は、ターゲットとしてのビューコントローラのビューを使用した完全な実装です。

import UIKit

class ViewController: UIViewController {

    var touchesInSession = false

    override func viewDidLoad() {
        super.viewDidLoad()

        let interaction = UIContextMenuInteraction(delegate: self)
        view.addInteraction(interaction)

        let recognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressHandler))
        view.addGestureRecognizer(recognizer)
    }

    @objc func longPressHandler(recognizer: UILongPressGestureRecognizer) {
        guard recognizer.state == .began else { return }
        presentMenu(from: recognizer.location(in: view))
    }

    func presentMenu(from location: CGPoint) {
        view.becomeFirstResponder()
        let saveMenuItem = UIMenuItem(title: "New Folder", action: #selector(createFolder))
        let deleteMenuItem = UIMenuItem(title: "Get Info", action: #selector(getInfo))
        UIMenuController.shared.menuItems = [saveMenuItem, deleteMenuItem]
        UIMenuController.shared.showMenu(from: view, rect: .init(Origin: location, size: .zero))
    }

    @objc func createFolder() {
        print("createFolder")
    }

    @objc func getInfo() {
        print("getInfo")
    }

    // MARK: UIResponder
    override var canBecomeFirstResponder: Bool { true }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        touchesInSession = true
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesEnded(touches, with: event)
        touchesInSession = false
    }

    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesCancelled(touches, with: event)
        touchesInSession = false
    }
}

extension ViewController: UIContextMenuInteractionDelegate {
    func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
        guard !touchesInSession else { return nil }
        let configuration = UIContextMenuConfiguration(identifier: "PointOnlyContextMenu" as NSCopying, previewProvider: { nil }, actionProvider: { suggestedActions in
            let newFolder = UIAction(title: "New Folder", image: UIImage(systemName: "folder.badge.plus")) { [weak self] _ in
                self?.createFolder()
            }
            let info = UIAction(title: "Get Info", image: UIImage(systemName: "info.circle")) { [weak self] _ in
                self?.getInfo()
            }
            return UIMenu(title: "", children: [newFolder, info])
        })
        return configuration
    }
}

1
FrontendFool