Display any view on top of native-stack's modal
Closed this issue ยท 53 comments
Hello, thank you for this awesome library.
Is there any possible solution to display any view on top of visible stack's modal?
I realized, that react-native-modal is not visible over stack's modal, and also there is no way to display any view on top of screen. For example, I need to display in-app-notification on top of all content, or sometime display modal loader spinner - both of them are not appears when stack's modal is visible.
This is related to ios only, android works fine.
I've got the same issue. iOS-Modal issue only.
same issue
This issue is known and also reported by me. You can't even toggle the element inspector on the modal. They managed to get EXPOs Dev Menu finally to overlay the modal, but not sure about any other view. The modal is currently the most top element that nothing can overlap. I also had issues with in-app-notifications and worked around displaying them inside the modal when modal was open - but yeah, there should be another method for it. Maybe we need to wrap a provider for that native-modal and let it mount there - dunno if thats even possible, just thinking.
P:S the modal from import { Modal } from "react-native"
actually can overlap it. My app uses this too
@hirbod how Modal from react-native can overlap? I've tested it for a while, but modal just invisible and dismisses almost right after mounted.
@Strate https://streamable.com/mtsbmh here, from my app
@hirbod wow, awesome! This is looks like Shared Element Transition, but with Modal?
Yes, tooked me a crazy amount of time and is not smooth on android :D
But it works with react-native modal in a react-native-screens modal :D
Hopefully we will have natie-shared-element support for v5 stack at some point :)
@hirbod could you please share some code for Modal from react-native? Which presentation used for display over screen's modal? Because my modal still invisible :(
@Strate its nothing special.
import { Modal } from "react-native";
....
...
...
renderFullscreen = () => {
return (
<Modal
transparent
visible={fullscreen}
onShow={this.handleModalShow}
onRequestClose={this.close}
/>
)};
I think the important thing is that Modal isn't rendered on mount. My modal gets rendered when my Image receives an onPress. I have an if-statement around my Modal in my code.
And my renderFullScreen returns the Modal and unmounts again in my close function. My onPress sets the state fullscreen to true
and renders the modal
return (
<View>
<TouchableWithoutFeedback onPress={....}>
<View>
<ImageCustom />
</View>
</TouchableWithoutFeedback>
{fullscreen && this.renderFullscreen()}
</View>
);
I've the same issue, I'm trying to display a loader but can't draw over the 'formsheet' modal.
<>
<NavigationContainer>
<Stack.Navigator screenOptions={{ stackPresentation: 'formSheet' }} .... >
</NavigationContainer>
<View style={{
...Stylesheet.absoluteFillObject,
zIndex: 1000,
backgroundColor: 'rgba(0,0,0,0.4)'
}}/> /* <<< */
</>
Can you provide a repo with the thing you want to achieve so I could work on it?
Hi @WoLewicki
this repo explains the issue ( at least the one I'm having.)
https://github.com/a-eid/react-native-native-stack-global-loader
you can see that when we have a formSheet
screen the loader is behind the screen unlike the normal screen. please let me know if you need more context.
I wan't to do another example for toast but it basically is the same idea.
thank you.
it's also worth noting that this happens also with other modals.
also a modal screen (using stackPresentation:modal) does not pop up over a react-native-modal's modal component.
@a-eid regarding your example: the loader will not load above the modal this way since all the views displayed under the root view of the application will be displayed also under the modal screens. You can spot the same thing while using Modal
component from react-native
. It is like so because of the native view hierarchy of iOS. Roots of the modals are above the react's root view, so the only way to display something above the modal is to either display it in that modal, or display another modal on the previous modal. In particular, for your example, the flow can be changed somehow like that:
<Stack.Screen
name="modal-screen"
options={{
stackPresentation: 'formSheet',
}}>
{() => (
<GlobalLoader>
<ModalScreen />
</GlobalLoader>
)}
</Stack.Screen>
Then the GlobalLoader
component is under the modal screen in the view hierarchy and is displayed on the modal component.
Another option, maybe even better, is to make the GlobalLoader
a Screen
of the stack and call navigation to it when triggered and goBack
when removing it. In order for it to be displayed on the whole screen, you can use stackPresentation: 'transparentModal'
which will give you an option to make the component semi-transparent. Does it solve your issue?
@Abhishek12345679 yes, because it messes with the modal stack of native-stack
and is not permitted in react-native-screens
since they both use view controllers.
@WoLewicki thank you for the reply.
I'm wondering how would you display toasts on a 'formSheet' screens ?
also can you trigger a modal with Animated.Value
?
About the first question, what is the difference of having formSheet
screens for displaying toasts? I think the approach should be the same, the Toast
should be a child of the Screen
component in order to be visible over the modal screen. You probably cannot use transparentModal
then because it disables touches on the screens under it.
About the second question, I am not sure what you mean. If you mean to control the transition of the modal appearing then no, since it is controlled by a native UINavigationController
.
global toasts are great when you have a background task ( socket connection or even an api request ). and you wanna notify the user when the task is finish ( success | error ). there is no way to find out where the user is on the app.
global toasts & loaders have a single mount point which is really great for those cases and convenient for almost all other cases.
I'm not really that familiar with IOS but do u think there is a way to do that on ios Natively ?
You can read more about how certain interactions with user on iOS should be implemented in Apple's HIG. You can look at e.g. Alert Views and then look for libraries in react-native that implement those things. Also, I think it is just not possible to have the listener for such events in one place only when trying to use native iOS modal views since they simply aren't children of RCTRootView
. Can I help you more with it?
@WoLewicki thank you very much, I'll be reading more about it.
@WoLewicki looks like there is still no solution to display any view on top of modal ^(
@Strate you can do it as long as you are displaying it in the Screen
, in which the modal is located or use another modal screen to display it.
@WoLewicki thats okay for alerts, for example, but not great for toasts :(
This is how toasts looks like when rendered inside Screen which displayed in modal:
@WoLewicki I'm wondering how RN perf monitor
works, could we display a toast
the same way that the perf monitor
is being displayed ?
@a-eid I think it is just a View
that shows on the screen. You can see that enabling it on one screen and then going to a modal screen makes it stay on the background screen.
@WoLewicki yes, however if you enable it on a formsheet modal
and then dismiss it it stays in place as if it's not part of that screen. ( somehow mounted at the top of the tree . ).
Yes, it is directly under the UIWindow
. But the modal is on the same level in the view hierarchy. That's why it is visible after dismissing the modal but goes under after going to another modal since that modal is displayed as the most visible subview of the window. You can use your own component that will add a subview to the UIWindow
and I think it will behave the same. You can check it by displaying View Hierarchy in Xcode.
I'm having the same issue. I can append my view to UIWindow on the native side, and it will appear on top. But the problem is that I have a ViewController with IBActions etc, and afaik you can't append a ViewController to UIWindow (?). Or do you know if there's another way to natively append a ViewController to the UINavigationController
(ie the modal) ?
@WoLewicki thank you for suggestion!
Do you have any idea how to implement this thing: "You can use your own component that will add a subview to the UIWindow"? Sounds very easy, but very hard for non-iOS developer :(
Here is the code which adds the monitor to the UIWindow
: https://github.com/facebook/react-native/blob/dc80b2dcb52fadec6a573a9dd1824393f8c29fdc/React/CoreModules/RCTPerfMonitor.mm#L336. I think you can make your own native module, which will make a native view and add it straight under the UIWindow
. I may add such thing in a free moment and I will post it here then.
@WoLewicki that would be awesome, thank you.
I added a PoC package where you can render a View
under the UIWindow
. You can try and play with it: https://github.com/WoLewicki/react-native-window-view.
@WoLewicki I'm testing it rn, do you think you'll keep it as a separate package or will you be adding it to react-native-screens
?
edit:
it's only draggable, it doesn't allow touches, do u think I can try and fix it ?
been trying to fix it but Objc
syntax is so weird.
observations:
The modal does display on top of RNWindowView
.
if the RNWindowView
is displayed last it always comes on top thou
do you think you'll keep it as a separate package or will you be adding it to react-native-screens ?
I think it will now be a separate package since react-native-screens
is library connected to navigation and this component has nothing to do with it. It may change in the future though.
it's only draggable, it doesn't allow touches, do u think I can try and fix it ?
I fixed it in 0.3.0
. I also added option to make it non-draggable with draggable
prop.
The modal does display on top of
RNWindowView
.
if theRNWindowView
is displayed last it always comes on top thou
Yes, because the modal and RNNWindowView
are both attached to the UIWindow
, so their visibility is determined by their attaching order, just like you saw here: #525 (comment). For now what you can do is detect when the modal is being pushed and do a hide-show sequence when it comes up, something similar to what you posted above. I also added another option to make the view always visible by subscribing to subview updates of the UIWindow
in your AppDelegate.m
(see changes here: WoLewicki/react-native-window-view@1cf924e#diff-572a1fa43eaec8dc052d345a050635916f8bdeb379f1ab00367368dffa947e45R14)
@WoLewicki now Touchables work inside of the windowView
now, great work thank you.
I'm a bit new to this thread, and I'm not exactly sure how the iOS Window works.
@WoLewicki does the package you linked to solve the problem of rendering toasts on top of any modal? I have some UI providers (bottom sheet menu, toast notifications) at the root of my app, and I'd like them to render on top of any content.
@nandorojo the package work similar to Perf Monitor
window, which does display in front on any modal as long as it was displayed last. it goes behind the modal if the modal is displayed after it.
I see, you mean last in the component tree, right? As in, if I render this after my native stack, it should be fine.
Can treat this component as a sort of a Portal for components at the root of the app that need to render on top of everything?
I see, you mean last in the component tree, right? As in, if I render this after my native stack, it should be fine.
yes and no.
you see if you render this component then trigger a modal, the modal will display on top of the component.
Oh, so it's based on the time of rendering? As if it were attached to the window imperatively?
I added an option for the toast to stay on top even after pushing modals after it was rendered (see WoLewicki/react-native-window-view#2 (comment)). This library is kind of a PoC still, so if you see any bugs/inconsistencies, please report them there.
@WoLewicki thank you so much, I will be testing this lib extensively in a couple of apps that we have, will report and if possible contribute.
I added an option for the toast to stay on top even after pushing modals after it was rendered (see WoLewicki/react-native-window-view#2 (comment)). This library is kind of a PoC still, so if you see any bugs/inconsistencies, please report them there.
@WoLewicki thanks! Is this a prop on the JS side? I don't know my way around native code so I'm not sure exactly what I should be doing.
Also, are pointed events configurable? I'd like to set it to box-none, not sure what the default behavior is. Thanks again.
It requires adding code in your app's AppDelegate.m
as shown in the code of example app. As for pointer events, I have not changed anything about it, so I assume it should work in a default RN way. If not, please submit issues in the repo.
@WoLewicki pointerEvents
& style
do no work consistently between android & ios.
positional style do work but not all styles, for example backgroundColor
does not work.
also pointerEvents doesn't seem to be working.
does having RNViewContainer
& RNWindowView
means that we have 2 views ?
is that necessary and could that be the issue why pointerEvents
is not working properly ?
also is there a reason we are using RCTView
& UIView
and not one of a kind ?
I tried adding hitTest:withEvent:
but it didn't work.
@a-eid I think I answered the questions in your issue in the repo, and I think we should keep the discussion there since it is connected to it.
@WoLewicki is it possible that your PoC will make it into the screens package? I find it really necessary to render content arbitrarily on top of a modal from the root of the app.
This includes toasts, menus, portals, alert dialogues, etc.
I'm working on a design system like Radix / HeadlessUI for React Native, and it requires using a portal at the root of the app for those use cases, among others.
It would be great for the API to be like so:
<WindowView>
{children}
</WindowView>
Where:
pointerEvents
defaults tobox-none
so it doesn't interrupt touchesshown
is always true, so there's no need for a propdraggable
is defaultfalse
or removed altogether- most importantly, the content always stays at on top of the app, regardless of the number of modals or when they were added.
Does this seem realistic? I think it would add a lot to screens. I wish I could help implement it, I just don't know any native code.
Thanks again for all the great work here!
I think WindowView
doesn't belong to the react-native-screens
package since it has nothing to do with the platform navigation primitives, and it is the main goal of this package. As for those changes, I think it 4. is already implemented, and the discussion about others should probably be moved to some issue in that repo. I am open to suggestions if you think I am wrong.
After some discussion, we decided to move WindowView
to react-native-screens
package. You can check #1096 and test if it is what you need.
is FullWindowOverlay from "react-native-screens"..
it worked for me..
you can read about it here
FullWindowOverlay