Closed ancyrweb closed 4 years ago
Also experiencing this.
Same here.
Having this problem as well.
same problem on iOS 13 I tried using the module "react-native-swipe-gestures", but it was a terrible experience. Now at all there is no way to swipe to set the activity flag at the modal window, it is always true.
any update on this? I was just about to use it when noticed the swipe down problem.
Also experiencing. So frustrating.
I had to change the presentationStyle to 'overFullScreen' to prevent swiping down. It's certainly no replacement for pageSheet, but if you need an onDismiss function to trigger it can be a temporary solution until this is fixed.
I am using Modal
from react-native
, presentationStyle ="pageSheet"
which means I can slide to dismiss.
However when I do that, no function is fired.
onDismiss only fires when I close it from a button.
The modal won't open again if I do like this because it doesn't change it's state.
I've been running into this issue. I wrote a library (basically just copied over React Native's Modal code) to see if I could fix this issue. I hook into the viewDidDisappear
function in the ModalHostViewController which does get called when the Native iOS gesture for dismissing the modal happens. I then manually call the onDismiss function. Here's the library: https://github.com/HarvestProfit/react-native-modal-patch
I'm not that familiar with Objective-C so I'm not sure if this is a great solution, but hopefully it helps someone. If someone more familiar thinks this is a valid solution, then I'll create a PR.
I am also experiencing this. Please release a fix for this.
Same. Does anyone knows how to prevent the swipe? Not a solution but a possible workaround for now
Same issue here. Because of that it's Modal
component is useless.
Same problem with dismissing modal in presentation style. When I swipe it down to close, then state is not changed.
Here is a (not-ideal/hacky!) workaround while awaiting a proper fix if anyone is also desperate for the pull-down modal behaviour. It does not solve the problem that there's no way to fire a callback when user dismisses the modal, but enables reopening the modal after being pulled-down.
The idea behind the logic is to check if an imperative modal-open is being attempted on an "already open" modal, and forcing a couple of re-renders to reset the value on the modal.
import { useState, useEffect } from 'react';
export const useModalState = initialState => {
const [modalVisible, setModalVisible] = useState(initialState);
const [forceModalVisible, setForceModalVisible] = useState(false);
const setModal = modalState => {
// tyring to open "already open" modal
if (modalState && modalVisible) {
setForceModalVisible(true);
}
setModalVisible(modalState);
};
useEffect(() => {
if (forceModalVisible && modalVisible) {
setModalVisible(false);
}
if (forceModalVisible && !modalVisible) {
setForceModalVisible(false);
setModalVisible(true);
}
}, [forceModalVisible, modalVisible]);
return [modalVisible, setModal];
};
// use it the same way as before (as docs recommend)
const [modalVisible, setModalVisible] = useModalState(false)
Hope it may help some of you!
I wasn't able to get the workaround @lrholmes posted above to work. For whatever reason, the TouchableOpacity used to open the modal (and interestingly any adjacent TouchOpacity components) would no longer fire their onPress after the modal was swiped down. Desperate to get this working, I ended up putting a ScrollView around the entire modal content and used onDragEnd to flip the modal state variable. Works if your modal contents don't scroll. Not by any means ideal but was a reasonable trade-off in my situation.
@r4mdat
Just try use Modal component inside TouchableWithoutFeedback. Then onPress will does not blocked. And use function like @lrholmes
<TouchableWithoutFeedback>
<Modal
visible={enable}
presentationStyle={'formSheet'}
</Modal>
</TouchableWithoutFeedback>
<TouchableOpacity
onPress={() => {
setModalSelectPhotos(false);
setTimeout(() => {
setModalSelectPhotos(true);
}, 50);
}}>
.......
</TouchableOpacity>
It was help me
Patch workaround for React Native 0.61.2
Here's a patch that triggers onDismiss
callback when a modal is dismissed by swiping down, on iOS 13. However, there is a caveat: Once you patch the code, Xcode seems to change the default modal behavior for the entire app (at least for me), causing all modals to appear in the new style, on iOS 13. Depending on how your app is, this may be unwanted so please consider that before using the patch in production. Edit: There is no caveat anymore. Updated patch in link works as intended.
Download patch https://gist.github.com/scarlac/ec162221e11927c52cfc9c94e7252824
Installation You can apply it using either:
patch < react-native+0.61.2.patch
in your project root folder or...Same issue - even with the patched versions.
@martsie You need to recompile your app. Set a breakpoints in the newly added lines to verify
Sorry, @martsie there was a line missing from my diff. You'll need to say that you're implementing a delegate as well (UIAdaptivePresentationControllerDelegate
). I've updated the link. Not sure why it worked for me - perhaps I had a local modification that I forgot to include in the patch
I wasn't able to get the workaround @lrholmes posted above to work. For whatever reason, the TouchableOpacity used to open the modal (and interestingly any adjacent TouchOpacity components) would no longer fire their onPress after the modal was swiped down. Desperate to get this working, I ended up putting a ScrollView around the entire modal content and used onDragEnd to flip the modal state variable. Works if your modal contents don't scroll. Not by any means ideal but was a reasonable trade-off in my situation.
@r4mdat just put your Modal in a View with height: 0
<View style={{height:0}}><Modal>....</Modal></View>
@r4mdat
Just try use Modal component inside TouchableWithoutFeedback. Then onPress will does not blocked. And use function like @lrholmes
<TouchableWithoutFeedback> <Modal visible={enable} presentationStyle={'formSheet'} </Modal> </TouchableWithoutFeedback> <TouchableOpacity onPress={() => { setModalSelectPhotos(false); setTimeout(() => { setModalSelectPhotos(true); }, 50); }}> ....... </TouchableOpacity>
It was help me
This worked perfectly fine for me. I would suggest this over any other options as it takes the least amount of code and is the most understandable. Also, once the issue is fixed, it will be the simplest to revert.
Hey everyone,
Im having the same issue. onDismiss property doesn't clear the Modal component (swipe down iOS). Can someone help me?
Thanks
@aca-hakan-pinar: @scarlac solution (https://github.com/facebook/react-native/issues/26892#issuecomment-581198937) works fine for me.
Has this patch been merged? I'm on 0.61.5 and still experiencing the issue.
This still persists on 0.61.5. This bug is the entire reason why I am resorting to using a third party library for Modal.
until the problem is fixed:
import React, { useState } from 'react';
import { SafeAreaView, Modal, TouchableWithoutFeedback, Keyboard, Dimensions } from 'react-native';
import { Button } from 'react-native-elements';
const MyComponent = () => {
const [modalView, setModalView] = useState<null|SafeAreaView>(null);
const [isOpen, setIsOpen] = useState<boolean>(false);
return (
<>
<Modal
presentationStyle="formSheet"
animationType="slide"
transparent={false}
visible={isOpen}
>
<TouchableWithoutFeedback
onPressOut={() => {
if(!modalView)
return;
modalView.measure((fx, fy, width, height, px, py) => {
if(py === Dimensions.get('window').height)
setIsOpen(false);
});
}}>
<SafeAreaView ref={(ref) => setModalView(ref)}>
</SafeAreaView>
</TouchableWithoutFeedback>
</Modal>
<Button title={'demo'} onPress={() => setIsOpen(true)} />
</>
);
};
and if you use focus (textInput...):
import React, { useEffect, useRef, useState } from 'react';
import { SafeAreaView, Modal, TouchableWithoutFeedback, Dimensions } from 'react-native';
import { Button } from 'react-native-elements';
const MyComponent = () => {
const [modalView, setModalView] = useState<null|SafeAreaView>(null);
const [isOpen, setIsOpen] = useState<boolean>(false);
const [keyboardOpen, setKeyboardOpen] = useState<boolean>(false);
const intervalRef = useRef<null|number>(null);
useEffect(() => {
if (keyboardOpen)
{intervalRef.current = setInterval(() => {
tryClose();
}, 500);}
else if (intervalRef.current != null) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
return () => {
if (intervalRef.current)
{clearInterval(intervalRef.current);}
};
},[keyboardOpen]);
const tryClose = () => {
if (!modalView)
{return;}
modalView.measure((fx, fy, width, height, px, py) => {
if (py === Dimensions.get('window').height){
setIsOpen(false);
setKeyboardOpen(false);
}
});
};
return (
<>
<Modal
presentationStyle="formSheet"
animationType="slide"
transparent={false}
visible={isOpen}
>
<TouchableWithoutFeedback
onBlur={() => setKeyboardOpen(false)}
onFocus={() => setKeyboardOpen(true)}
onPressOut={tryClose}>
<SafeAreaView ref={(ref) => setModalView(ref)}>
</SafeAreaView>
</TouchableWithoutFeedback>
</Modal>
<Button title={'demo'} onPress={() => setIsOpen(true)} />
</>
);
};
or:
import React, { useEffect, useRef, useState } from 'react';
import { SafeAreaView, Modal, Dimensions } from 'react-native';
import { Button } from 'react-native-elements';
const MyComponent = () => {
const [modalView, setModalView] = useState<null|SafeAreaView>(null);
const [isOpen, setIsOpen] = useState<boolean>(false);
const intervalRef = useRef<null|number>(null);
useEffect(() => {
const clearLoop = () => {
if(intervalRef.current)
clearInterval(intervalRef.current);
intervalRef.current = null;
};
if(isOpen && modalView)
intervalRef.current = setInterval(() => tryClose(), 500);
else if (intervalRef.current != null)
clearLoop();
return () => clearLoop();
},[isOpen, modalView]);
const tryClose = () => {
if(!modalView)
return;
modalView.measure((fx, fy, width, height, px, py) => {
if (py === Dimensions.get('window').height)
setIsOpen(false);
});
};
return (
<>
<Modal
presentationStyle="formSheet"
animationType="slide"
transparent={false}
visible={isOpen}>
<SafeAreaView ref={(ref) => setModalView(ref)}>
</SafeAreaView>
</Modal>
<Button title={'demo'} onPress={() => setIsOpen(true)} />
</>
);
};
@CodingByJerez
Thanks a lot for posting this but when it comes to the use of SafeAreaView
- that sure isn't working for me on iOS 13.2.3. It has to be a regular View
. (Possibly related to this?)
Plus - if the user swipes down immediately before tapping on the modal, it seems that onPressOut
is not triggered. Which apparently can be resolved by using TouchableOpacity
.
And so overall, I ended up with this working for me (since it's possible to have the ref
on TouchableOpacity
, I figured why not. One just has to watch out that it spans the whole modal, like with flex: 1
. And the View
apparently becomes unnecessary in this case.):
const [modalView, setModalView] = useState<null | TouchableOpacity>(null);
return (
<Modal
animationType="slide"
presentationStyle="pageSheet"
visible={isVisible}
onRequestClose={onClose}
>
<TouchableOpacity
activeOpacity={1}
onPressOut={() => {
if (!modalView) return;
modalView.measure((fx, fy, width, height, px, py) => {
if (py === Dimensions.get('window').height) onClose();
});
}}
ref={(ref) => setModalView(ref)}
style={{
flex: 1,
}}
>
<Text>Hello World!</Text>
<Button onPress={onClose} title="Hide Modal" />
</TouchableOpacity>
</Modal>
);
@s-h-a-d-o-w I run it on iOS 13.3, and I don't have a problem. Have you tried the loop? (This is the safest way) (Check 'PY' It may vary depending on your use in 0 and Dimensions.get('window').height)
import React, { FunctionComponent, useEffect, useRef, useState } from 'react';
import { SafeAreaView, Modal } from 'react-native';
interface IProps {
visible:boolean,
onVisible(bool:boolean):void,
}
const MyComponent:FunctionComponent<IProps> = ({visible, onVisible, children}) => {
const [modalView, setModalView] = useState<null|SafeAreaView>(null);
const [isOpen, setIsOpen] = useState<boolean>(false);
const intervalRef = useRef<null|number>(null);
useEffect(() => setIsOpen(visible),[visible]);
useEffect(() => {
if(visible !== isOpen)
onVisible(isOpen);
const clearLoop = () => {
if(intervalRef.current){
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};
if(isOpen && modalView)
intervalRef.current = setInterval(() => tryClose(), 500);
else if (intervalRef.current != null)
clearLoop();
return () => clearLoop();
},[isOpen, modalView]);
const tryClose = () => {
if(!modalView)
return;
modalView.measure((fx, fy, width, height, px, py) => {
if (py === 0)
setIsOpen(false);
});
};
return (
<Modal
presentationStyle={'formSheet'}
animationType={'slide'}
transparent={false}
visible={isOpen}
onRequestClose={() => setIsOpen(false)}>
<SafeAreaView ref={(ref) => setModalView(ref)}>
{children}
</SafeAreaView>
</Modal>
);
};
I'm on react native 0.62.0 and its still not working for me
Yes, this still continues to be a bug. I am thinking that RN core is planning to get rid of Modal
from core altogether and move it to react-native-community and thus there has been no interest in fixing this?? still not sure...
@scarlac Patch workaround doesn't work anymore in RN 0.62.0… RCTModalManager.h file not found in RCTModalHostView.m
.
@jdanthinne you could try this gist https://gist.github.com/r0b0t3d/3c9f77434e6fbcfa78698dcf57614fad
<Modal
visible={visible}
animationType="slide"
presentationStyle="pageSheet"
onRequestClose={onClose}
>
</Modal>
@r0b0t3d Working fine. Thanks!
Anyone knows why this issue is still marked as closed despite the issue still happening? @r0b0t3d do you know if there's a pull request for the gist you posted?
@noambonnie i think that's because they are planning to remove Modal
out of core and move it to react-native-community
; there's already a lib out there react-native-community/react-native-modal.
I am not a 100% sure if this is true though.
@annjawn I see no indication that they'll move it out. That library relies on the modal component, does not provide the same default look, and has no native integrations like page sheets. Is there a discussion where they are considering it?
@scarlac as I said, I am not 100% certain that's the plan but seeing how many of the things are with core, this could very well be a possibility. Yes, the community modal doesn't closely support the navigations and native integrations but I would venture a guess that it won't be a huge deal to support native view controller integrations there for both iOS and Android. But again , all of this is a big speculation on my side.
@jdanthinne Does @r0b0t3d ’s solution working ? Would you be able to close the modal programmatically ?
@amilaDulanjana Yes, working fine for me.
@annjawn that package re-uses the existing Modal from react-native, and adds styling on top. It's purely JS and has no native code.
@jdanthinne Does @r0b0t3d ’s solution working ? Would you be able to close the modal programmatically ?
not working for me..any other ideas so far?
I have it working with combination of two solutions mentioned in this thread..
Wrap <Modal />
with <View style={{ height: 0}} />
This allows the button triggering the modal to be clicked again when the PageSheet modal is swiped down
Ensure that you always set isVisible to false first, then set to true to "refresh" the modal state.
I guess this will leave the modal instance hanging around when swiped down, but setting setModalOpen to false on the unmount of component containing the <PageSheetModal />
should help?
const [isModalOpen, setModalOpen] = React.useState<boolean>(false);
const showModal = (): void => {
setModalOpen(false);
requestAnimationFrame(() => setModalOpen(true));
};
// React.useEffect() to setModalOpen to false on component unmount
Modal example
import * as React from 'react';
import { Modal, View } from 'react-native';
import { IPageSheetModalProps } from './PageSheetModal.types';
export function PageSheetModal(
props: React.PropsWithChildren<IPageSheetModalProps>
): React.ReactElement {
const { isVisible, children } = props;
return (
<View style={{ height: 0 }}>
<Modal presentationStyle="pageSheet" visible={isVisible} animationType="slide">
{children}
</Modal>
</View>
);
}
I've also tried using <TouchableWithoutFeedback />
, but it caused issues if I had <ScrollView />
inside. The above example also works with <ScrollView />
inside the modal.
Patch workaround for React Native 0.61.2 Here's a patch that triggers
onDismiss
callback when a modal is dismissed by swiping down, on iOS 13. ~However, there is a caveat: Once you patch the code, Xcode seems to change the default modal behavior for the entire app (at least for me), causing all modals to appear in the new style, on iOS 13. Depending on how your app is, this may be unwanted so please consider that before using the patch in production.~ Edit: There is no caveat anymore. Updated patch in link works as intended.Download patch https://gist.github.com/scarlac/ec162221e11927c52cfc9c94e7252824
Installation You can apply it using either:
* Manually (which is temporary - yarn may remove it) using: `patch < react-native+0.61.2.patch` in your project root folder or... * Automatically using [patch-package](https://www.npmjs.com/package/patch-package) (recommended)
This was the solution for me I'm using react-native 0.61.5
before opening modal, set visible state to false, then set visible state to true again, because if modal already open, closed and open again
showModal = () => { this.setState({ modalVisible:false},function(){ this.setState({ modalVisible: true }) }) }
I've updated to RN 0.62.2 and the bug persist.
still not working in RN 0.62.2 onDismiss is never called
I used react-native-modal
as a small workaround for this issue https://github.com/react-native-community/react-native-modal. It works as expected.
Any solution for Expo users?
Will somebody create a PR with @scarlac’s patch?
When the user dismisses the modal by a swipe gesture using a custom presentation style, the event isn't caught by onDismiss.
React Native version: 0.61.0
Sample code :
I'm no expert in iOS, but this article might give a hint.
I can fill in a PR with some help.