I'm building an iOS based app that has a RealityKit view filling the screen.
I would like to present a context menu at the location of my tap, so that I can present context sensitive controls for the piece of geometry I'm tapping on.
I managed to setup my context menu similar to this:
class MyARView: ARView, UIContextMenuInteractionDelegate {
required init(frame frameRect: CGRect) {
super.init(frame: frameRect)
setupContextMenu()
}
required dynamic init?(coder decoder: NSCoder) {
super.init(coder: decoder)
setupContextMenu()
}
override init(frame frameRect: CGRect, cameraMode: ARView.CameraMode, automaticallyConfigureSession: Bool) {
super.init(frame: frameRect, cameraMode: cameraMode, automaticallyConfigureSession: automaticallyConfigureSession)
setupContextMenu()
}
func isValidHitTest(at location: CGPoint) -> Bool {
// TODO: Implement...
}
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
guard isValidHitTest(at: location) else {
return nil
}
UIContextMenuConfiguration(
identifier: nil,
previewProvider: nil,
actionProvider: { _ in
let action = UIAction(title: "Menu Option") { _ in
print("Do the thing")
}
return UIMenu(title: "", children: [action])
}
)
}
func setupContextMenu() {
addInteraction(UIContextMenuInteraction(delegate: self))
}
}
This works technically, but it doesn't provide the behaviour I'd expect. If I tap on the view (anywhere), the context menu appears at the top right of the view, but never at the location of the tap. If I use a mouse with the iPad, and right click, it always appears under the mouse.
Is there any way I can configure a menu that will appear under the tap?
I don't mind if it's using UIContextMenuInteraction
, and would actually prefer the visual style of UIEditMenuInteraction
on iPad. But generally, as long as I can get a list of options to appear in a menu under the mouse, that's fine.
I managed to get the edit menu working with the following code.
class MyARView: ARView, UIEditMenuInteractionDelegate {
private var editMenuInteraction: UIEditMenuInteraction? = nil
required init(frame frameRect: CGRect) {
super.init(frame: frameRect)
setupEditMenu()
}
required dynamic init?(coder decoder: NSCoder) {
super.init(coder: decoder)
setupEditMenu()
}
override init(frame frameRect: CGRect, cameraMode: ARView.CameraMode, automaticallyConfigureSession: Bool) {
super.init(frame: frameRect, cameraMode: cameraMode, automaticallyConfigureSession: automaticallyConfigureSession)
setupEditMenu()
}
func isValidHitTest(at location: CGPoint) -> Bool {
// TODO: Implement...
//
return true
}
func setupEditMenu() {
print("Hello, world!")
editMenuInteraction = UIEditMenuInteraction(delegate: self)
if let editMenuInteraction {
self.addInteraction(editMenuInteraction)
}
let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:)))
self.addGestureRecognizer(longPressGestureRecognizer)
}
func editMenuInteraction(_ interaction: UIEditMenuInteraction, menuFor configuration: UIEditMenuConfiguration, suggestedActions: [UIMenuElement]) -> UIMenu? {
UIMenu(
title: "",
options: .displayInline,
children: [
UIAction(title: "Menu Option") { _ in
print("Do the thing")
}
]
)
}
@objc
func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) {
guard gestureRecognizer.state == .began else {
return
}
let location = gestureRecognizer.location(in: self)
guard isValidHitTest(at: location) else {
return
}
let configuration = UIEditMenuConfiguration(identifier: "MyARView", sourcePoint: location)
editMenuInteraction?.presentEditMenu(with: configuration)
}
}