我正在尝试在其 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()
}
}
更新:我找到了一个可行的解决方案,但它使用 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
}
}
导入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
}
}
}