SwiftUI:使用 LongPressGesture 显示类似 Pinterest 的上下文菜单

SwiftUI: Using LongPressGesture to display a Pinterest like context menu

我正在尝试在他们的 iOS 应用程序中创建一个类似于 Pinterest 上下文菜单的上下文菜单。长按 post 显示一个四按钮视图,当用户继续长按时,可以拖动和 select 其他按钮。放开长按会 select 您当前正在 select 的任何按钮,或者如果您没有任何 select 则完全关闭菜单。请参阅下面的示例:

到目前为止,我已经在此处尝试了类似于 Apple 文档的内容: https://developer.apple.com/documentation/swiftui/longpressgesture

但似乎手势一旦达到手势中定义的 minimumDuration 就完成了。我希望手势在用户按住时一直持续,并在他们松开时立即结束。

此外,我在拖动和 selecting 其他按钮时遇到了麻烦。到目前为止,这是我的方法:

struct Example: View {

@GestureState var isDetectingLongPress = false
@State var completedLongPress = false

var longPress: some Gesture {
    LongPressGesture(minimumDuration: 3)
        .updating($isDetectingLongPress) { currentState, gestureState,
                transaction in
            gestureState = currentState
            transaction.animation = Animation.easeIn(duration: 2.0)
        }
        .onEnded { finished in
            self.completedLongPress = finished
        }
}

var body: some View {
    
    HStack {
        
        Spacer()
        ZStack {
            // Three button array to fan out when main button is being held
            Button(action: {
                // ToDo
            }) {
                Image(systemName: "circle.fill")
                    .frame(width: 70, height: 70)
                    .foregroundColor(.red)
            }
            .offset(x: self.isDetectingLongPress ? -90 : 0, y: self.isDetectingLongPress ? -90 : 0)
            Button(action: {
                // ToDo
            }) {
                Image(systemName: "circle.fill")
                    .frame(width: 70, height: 70)
                    .foregroundColor(.green)
            }
            .offset(x: 0, y: self.isDetectingLongPress ? -120 : 0)
            Button(action: {
                // ToDo
            }) {
                Image(systemName: "circle.fill")
                    .frame(width: 70, height: 70)
                    .foregroundColor(.blue)
            }
            .offset(x: self.isDetectingLongPress ? 90 : 0, y: self.isDetectingLongPress ? -90 : 0)
            
            // Main button
            Image(systemName: "largecircle.fill.circle")
                .gesture(longPress)
            
        }
        Spacer()
    }

}

我能找到的最接近的等效项是上下文菜单 Src: AppleDeveloper

如果你按住,你会得到类似的效果。

Context Menu - Apple Devloper Documentation

更新:我找到了一个可行的解决方案,但它使用的是 UIKit,因为我不相信 SwiftUI 提供了一种本机执行此操作的方法。我按照此处找到的类似问题的答案中的指导进行操作:

然而它是用 ObjC 编写的,所以我粗略地翻译成 Swift。对于那些好奇的人,这是该过程的简化版本:

class UIControlsView: UIView {

let createButton = CreateButtonView()
let secondButton = ButtonView(color: .red)
var currentDraggedButton: ButtonView!

required init?(coder: NSCoder) {
    fatalError("-")
}

init() {
    super.init(frame: .zero)
    self.backgroundColor = .green
    
    let longPress = UILongPressGestureRecognizer(target: self, action: #selector(self.longPress))
    createButton.addGestureRecognizer(longPress)
    createButton.frame = CGRect(x: 0, y: 0, width: 80, height: 80)
    createButton.center = CGPoint(x: UIScreen.main.bounds.size.width / 2, y: 40)
    
    secondButton.frame = CGRect(x: 0, y: 0, width: 80, height: 80)
    secondButton.center = CGPoint(x: UIScreen.main.bounds.size.width / 2, y: 40)

    self.addSubview(secondButton)
    self.addSubview(createButton)
}

@objc func longPress(sender: UILongPressGestureRecognizer) {
    if sender.state == .began {
        print("Started Long Press")
        secondButton.center.x = secondButton.center.x + 90
    }
    
    if sender.state == .changed {
        let location = sender.location(in: self)
        guard let superViewLocation = self.superview?.convert(location, from: self) else {
            return
        }

        guard let view = self.superview?.hitTest(superViewLocation, with: nil) else {
            return
        }

        if view.isKind(of: ButtonView.self) {
            let touchedButton = view as! ButtonView
            
            if self.currentDraggedButton != touchedButton {
                if self.currentDraggedButton != nil {
                    self.currentDraggedButton.untouchedUp()
                }
            }
            
            self.currentDraggedButton = touchedButton
            touchedButton.isTouchedUp()
        } else {
            if self.currentDraggedButton != nil {
                print("Unsetting currentDraggedButton")
                self.currentDraggedButton.untouchedUp()
            }
        }
    }
    
    if sender.state == .ended {
        print("Long Press Ended")
        let location = sender.location(in: self)
        guard let superViewLocation = self.superview?.convert(location, from: self) else {
            return
        }

        guard let view = self.superview?.hitTest(superViewLocation, with: nil) else {
            return
        }
        
        if view.isKind(of: ButtonView.self) {
            let touchedButton = view as! ButtonView
            
            touchedButton.untouchedUp()
            touchedButton.tap()
            self.currentDraggedButton = nil
        }

        secondButton.center.x = secondButton.center.x - 90
    }
    
}