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

问题描述 投票:0回答:3

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

到目前为止,我已经尝试过类似于苹果文档的内容: https://developer.apple.com/documentation/swiftui/longpressgesture

但似乎手势一旦达到手势中定义的最小持续时间就会完成。我希望只要用户按住该手势就可以持续,并在用户放开后立即结束。

此外,当涉及到拖动和选择其他按钮时,我陷入了困境。到目前为止,这是我的方法:

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()
    }

}
ios swift swiftui menu pinterest
3个回答
0
投票

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

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

上下文菜单 - Apple 开发者文档


0
投票

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

然而它是用 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
    }
    
}

0
投票

导入UIKit

DialView 类:UIView {

var selectedAction: Int? = nil

// Define actions with names and icons
private let actions: [(name: String, icon: UIImage)] = [
    ("Hide", UIImage(systemName: "eye.slash.fill")!),
    ("Pin", UIImage(systemName: "pin.fill")!),
    ("Share", UIImage(systemName: "square.and.arrow.up.fill")!)
]
private var actionButtons: [UIButton] = []

// Initializer for programmatic UI
override init(frame: CGRect) {
    super.init(frame: frame)
    setupDial()
}

// Initializer for storyboard/XIB UI
required init?(coder: NSCoder) {
    super.init(coder: coder)
    setupDial()
}

// Setup dial buttons
private func setupDial() {
    self.backgroundColor = UIColor.clear
    
    // Create buttons for each action
    for i in 0..<actions.count {
        let button = UIButton(type: .system)
        button.setImage(actions[i].icon, for: .normal)
        button.tintColor = .white
        button.frame.size = CGSize(width: 44, height: 44)
        button.layer.cornerRadius = 22
        button.backgroundColor = UIColor.black.withAlphaComponent(0.6)
        button.addTarget(self, action: #selector(actionButtonTapped(_:)), for: .touchUpInside)
        self.addSubview(button)
        actionButtons.append(button)
    }
}

// Adjust button positions when view layout changes
override func layoutSubviews() {
    super.layoutSubviews()
    updateButtonPositions()
}

// Position buttons along the top-left arc
private func updateButtonPositions() {
    let totalAngle: CGFloat = 115  // Arc angle
    let startAngle: CGFloat = -180 // Start from top-left
    let radius: CGFloat = 60       // Distance from center
    let angleIncrement = totalAngle / CGFloat(actions.count - 1)
    
    // Calculate and set button positions
    for i in 0..<actionButtons.count {
        let angle = startAngle + CGFloat(i) * angleIncrement
        let angleRadians = angle * .pi / 180
        let button = actionButtons[i]
        button.center = CGPoint(x: self.bounds.midX + cos(angleRadians) * radius,
                                y: self.bounds.midY + sin(angleRadians) * radius)
    }
}

// Update selected action based on touch point
func updateSelectedAction(at point: CGPoint) {
    let dx = point.x - self.bounds.midX
    let dy = point.y - self.bounds.midY
    let angle = atan2(dy, dx) * 180 / .pi + 360
    let normalizedAngle = Int(angle.truncatingRemainder(dividingBy: 360))
    let totalAngle: CGFloat = 115
    let startAngle: CGFloat = -180
    let angleIncrement = totalAngle / CGFloat(actions.count - 1)
    
    // Determine which button is selected based on angle
    for i in 0..<actions.count {
        let buttonAngle = startAngle + CGFloat(i) * angleIncrement
        let buttonAngleMin = buttonAngle - angleIncrement / 2
        let buttonAngleMax = buttonAngle + angleIncrement / 2
        
        if CGFloat(normalizedAngle) >= buttonAngleMin + 360 && CGFloat(normalizedAngle) < buttonAngleMax + 360 {
            selectedAction = i
            break
        }
    }
    updateButtonColors()
}

// Handle button tap action
@objc private func actionButtonTapped(_ sender: UIButton) {
    if let index = actionButtons.firstIndex(of: sender) {
        selectedAction = index
        performAction(index)
    }
}

// Update button colors based on selection
private func updateButtonColors() {
    for (index, button) in actionButtons.enumerated() {
        button.tintColor = (index == selectedAction) ? .red : .white
    }
}

// Perform action based on selected button
private func performAction(_ action: Int) {
    switch action {
    case 0:
        print("Hide action selected")
    case 1:
        print("Pin action selected")
    case 2:
        print("Share action selected")
    default:
        break
    }
}

}

© www.soinside.com 2019 - 2024. All rights reserved.