react-native-ios-context-menu
react-native-ios-context-menu copied to clipboard
Customize menu action item
Thank you for creating this amazing package 🚀
I'm currently trying to add custom buttons to the context-menu, because not every emoji reaction should be a separate action menu item. That's why I've made a custom preview render, which renders a small bar with pressables.
![](https://user-images.githubusercontent.com/34555296/147880386-d96aa12e-98eb-43aa-94de-68c168d161b6.png)
However when I try to click it the onPress handles don't get called.
I've tried to use onPressMenuPreview instead, however the page x & y position of the pressed item don't get passed to the event handler, only the generic RNIContextMenuView
tag is passed.
Is there any way to either customize an action menu item or make the buttons in the preview pressable?
Hey, unfortunately this is a limitation of the UIMenu
API 😔
This is actually something I want too, but TLDR the preview doesn't receive any touch events once it's being previewed.
The one in the iMessage app (Reactions), and Safari app (webpage previews), uses private API's.
As soon as apple exposes the public API, I'll add it in haha (maybe if we all file a radar, they'll finally add it)
Note: the last time I tried making the menu preview interactive was in 2019 though, maybe something has changed internally to allow this
Here is the summary:
- The previews are handled via
UIContextMenuContentPreviewProvider
— it returns a view controller (it contains the view you want to show in the preview) - But once the context menu interaction begins, the view + view controller no longer receive touch events (they are no longer interactive)
- I even tried setting
view. userInteractionEnabled = true
when the context menu interaction begins but no luck haha
Edit: I'm currently sick, but I'll try tinkering with the API's again later — I'll close this issue if I can't get it to work
Ah I wish Apple would just open this awesome API...thanks for looking into it @dominicstop
Ok not much we can do about, but thank you for your effort. Get well soon @dominicstop
@nandorojo @Flam3rboy hey sorry for the late response haha here are results of my testing ✨
Debug Layout:
(lldb) po self.previewController?.next
▿ Optional<UIResponder>
- some : <UIViewController: 0x7f7a6f90e100>
(lldb) po self.previewController?.next?.next
▿ Optional<UIResponder>
- some : <UITransitionView: 0x7f7a73016ab0; frame = (0 0; 375 667); alpha = 0; autoresize = W+H; userInteractionEnabled = NO; layer = <CALayer: 0x600002f3dea0>>
-
UITransitionView
is internal, not published in the SDK
(lldb) po self.previewController?.next?.next?.next
▿ Optional<UIResponder>
- some : <UIWindow: 0x7f7a6f90d1e0; frame = (0 0; 375 667); autoresize = W+H; gestureRecognizers = <NSArray: 0x60000265f2d0>; layer = <UIWindowLayer: 0x6000026539c0>>
- Climb responder chain...
-
UIViewController
(Preview Content) ->UITransitionView
(Internal) ->UIWindow
(Main Window)
(lldb) po self.previewController?.presentationController
▿ Optional<UIPresentationController>
- some : <_UIContextMenuPresentationController: 0x7f7a72365200>
(lldb) po self.previewController?.presentingViewController
▿ Optional<UIViewController>
- some : <UIViewController: 0x7f7a6f90e100>
(lldb) po self.previewController?.presentationController
▿ Optional<UIPresentationController>
- some : <_UIContextMenuPresentationController: 0x7f7a72365200>
(lldb) po self.previewController?.presentingViewController
▿ Optional<UIViewController>
- some : <UIViewController: 0x7f7a6f90e100>
- This is how you get the parent view controller of the preview
(lldb) po self.previewController?.presentingViewController?.view
▿ Optional<UIView>
- some : <RCTRootView: 0x7f7a6ef3ee90; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x6000028043a0>>
(lldb) po self.window?.rootViewController?.view
▿ Optional<UIView>
- some : <RCTRootView: 0x7f7a6ef3ee90; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x6000028043a0>>
(lldb) po self.previewController?.presentingViewController?.view === self.window?.rootViewController?.view
true
- the preview is being presented using the current window's root view controller
- Thus, the container view that's holding the preview content should be one of it's subviews...
(lldb) po (self.previewController?.next?.next as? UIView)?.gestureRecognizers
nil
(lldb) po (self.previewController?.next?.next as? UIView)
▿ Optional<UIView>
- some : <UITransitionView: 0x7f7a73016ab0; frame = (0 0; 375 667); alpha = 0; autoresize = W+H; userInteractionEnabled = NO; layer = <CALayer: 0x600002f3dea0>>
-
userInteractionEnabled
is disabled, enabling it does nothing... - TLDR: What needs to be done is to become responsible for handling the gestures for the preview controller, but it is not exposed...
- The moment I try to use the private API's, app store connect might reject it (I can do some swizzling, or drop down to Obj-C to use them)
- So lets try a different approach — I mentioned earlier that the container view that's holding the preview content should be one of it's subviews...
(lldb) po self.window?.subviews
▿ Optional<Array<UIView>>
▿ some : 4 elements
- 0 : <_UITouchFallbackView: 0x7fe89f62f8b0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x600002c69fe0>>
- 1 : <UITransitionView: 0x7fe89d00a320; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x600002855620>>
- 2 : <UITransitionView: 0x7fe89d0837b0; frame = (0 0; 375 667); alpha = 0; autoresize = W+H; userInteractionEnabled = NO; layer = <CALayer: 0x600002c50520>>
- 3 : <_UIContextMenuContainerView: 0x7fe89d07e6d0; frame = (0 0; 375 667); autoresize = W+H; gestureRecognizers = <NSArray: 0x60000248d7d0>; layer = <CALayer: 0x600002c505a0>>
-
_UIContextMenuContainerView
is the view that's holding our preview content (along with the context menu buttons)
(lldb) po self.window?.subviews.first { ($0.gestureRecognizers?.count ?? 0) > 0 }
▿ Optional<UIView>
- some : <_UIContextMenuContainerView: 0x7fe89d07e6d0; frame = (0 0; 375 667); autoresize = W+H; gestureRecognizers = <NSArray: 0x60000248d7d0>; layer = <CALayer: 0x600002c505a0>>
-
_UIContextMenuContainerView
is internal, so we can't cast to it... so we search for the view that has a bunch of gesture recoginizer- We could filter it via it's internal class name using reflection (e.g.
NSStringFromClass
), but referencing internal classes will get out app flag by app review haha
- We could filter it via it's internal class name using reflection (e.g.
(lldb) po self.window?.subviews.first { ($0.gestureRecognizers?.count ?? 0) > 0 }?.gestureRecognizers
▿ Optional<Array<UIGestureRecognizer>>
▿ some : 3 elements
- 0 : <UIPanGestureRecognizer: 0x7fc6021ae860 (com.apple.UIKit.PreviewPlatterPan); state = Possible; enabled = NO; cancelsTouchesInView = NO; view = <_UIContextMenuContainerView 0x7fc60225ec90>; target= <(action=_handlePanGesture:, target=<_UIContextMenuPanController 0x6000034407e0>)>>
- 1 : <UITapGestureRecognizer: 0x7fc6021aea00 (com.apple.UIKit.ContextMenuDismissalTap); state = Possible; enabled = NO; view = <_UIContextMenuContainerView 0x7fc60225ec90>; target= <(action=_handleDismissalTapGesture:, target=<_UIContextMenuPresentationController 0x7fc6022068b0>)>>
- 2 : <_UIContextMenuActionScrubbingHandoffGestureRecognizer: 0x7fc6021aeb20 (com.apple.UIKit.ContextMenuActionPanHandoff); state = Possible; enabled = NO; cancelsTouchesInView = NO; view = <_UIContextMenuContainerView 0x7fc60225ec90>; target= <(action=_handleActionHandoffGesture:, target=<_UIContextMenuPresentationController 0x7fc6022068b0>)>>
po self.window?.subviews.first { ($0.gestureRecognizers?.count ?? 0) > 0 }?.gestureRecognizers?.forEach { $0.isEnabled = false }
- Even after disabling the gesture recognizers, the preview content still dont receive any touch events...
Summary: interactive context menu's are still not possible in iOS 15 😔 (if you have any more ideas I can try, please let me know haha, but for now, I'll be closing the issue — if i find another way, I'll update this)
Note: I could not find any resources on the internet for making context menu previews interactive, so the following are just based on my tinkering haha
its very rough, but here's a proof of concept haha
basically this lets you attach an auxiliary view on top of the edge of the context menu preview
https://user-images.githubusercontent.com/18517029/148428709-cfa2f805-95a2-4ebe-984c-487d68f8c17b.mov
This is awesome, let me know if you add it to the library 🚀
@Flam3rboy i posted a preview on twitter
i'm still designing an API around it, but i'm not sure if the app store would allow this though haha
@Flam3rboy i posted a preview on twitter
i'm still designing an API around it, but i'm not sure if the app store would allow this though haha
Hi, did u add it's into library?
Yeah, see renderAuxiliaryPreview
ok tks worked
@dominicstop Hi! Thanks for your library! Can you advice how to render Auxiliary Preview using native code - Swift, maybe alhorithm.