Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[0.61][iOS 13] pageSheet/formSheet dismissal from swipe not propagated #26892

Closed
ancyrweb opened this issue Oct 17, 2019 · 90 comments
Closed

[0.61][iOS 13] pageSheet/formSheet dismissal from swipe not propagated #26892

ancyrweb opened this issue Oct 17, 2019 · 90 comments
Labels
Bug Platform: iOS iOS applications. Resolution: Locked This issue was locked by the bot.

Comments

@ancyrweb
Copy link

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 :

import React, { Component } from 'react';
import {
  StyleSheet,
  Text,
  Button,
  View,
  Modal as RNModal,
} from 'react-native';

export default class Example extends Component {
  state = {
    visible: false,
  };
  
  render() {
    return (
      <View style={styles.container}>
        <Button
          onPress={() => this.setState({ visible: true })}
          title="Default"
        />
        <RNModal
          visible={this.state.visible}
          onDismiss={() => console.log("on dismiss")}
          onRequestClose={() => console.log("on dismiss")}
          presentationStyle={"pageSheet"}>
          <View style={styles.content}>
            <Text style={styles.contentTitle}>Open</Text>
            <Button
              onPress={() => this.setState({ visible: false })}
              title="Close"
            />
          </View>
        </RNModal>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: 'white',
  },
  content: {
    backgroundColor: 'white',
    padding: 22,
    justifyContent: 'center',
    alignItems: 'center',
    borderRadius: 4,
    borderColor: 'rgba(0, 0, 0, 0.1)',
  },
  contentTitle: {
    fontSize: 20,
    marginBottom: 12,
  },
});

I'm no expert in iOS, but this article might give a hint.

I can fill in a PR with some help.

@dan-fein
Copy link

Also experiencing this.

@tomeberle
Copy link

Same here.

@janpe
Copy link

janpe commented Oct 30, 2019

Having this problem as well.

@epicbytes
Copy link

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.

@akondo06
Copy link

any update on this? I was just about to use it when noticed the swipe down problem.

@AppKidd
Copy link

AppKidd commented Nov 19, 2019

Also experiencing. So frustrating.

@jimcamut
Copy link

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.

@kangfenmao
Copy link

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.

@humphreyja
Copy link

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.

@amidulanjana
Copy link

I am also experiencing this. Please release a fix for this.

@jpamarohorta
Copy link

Same. Does anyone knows how to prevent the swipe? Not a solution but a possible workaround for now

@beniaminrychter
Copy link

Same issue here. Because of that it's Modal component is useless.

r0b0t3d referenced this issue in r0b0t3d/react-native Dec 27, 2019
Summary:
# Disclaimer:
I might be missing something as the solution I implemented here seems like something that was considered by original author. If this solution isn't good, I have a plan B.

# Problem:
`onDismiss` prop isn't being called once the modal is dismissed, this diff fixes it.

Also I've noticed that `onDismiss` is meant to only work on iOS, why is that? By landing this diff, it'll be called on Android as well so we need to change the docs (https://facebook.github.io/react-native/docs/modal.html#ondismiss).

## Video that shows the problem
Following code is in playground.js P70222409 which just increments number everytime onDismiss is called

{F166303269}

Reviewed By: shergin

Differential Revision: D16109536

fbshipit-source-id: 3fba56f5671912387b217f03b613dffd89614c9d
@patrikmasiar
Copy link

Same problem with dismissing modal in presentation style. When I swipe it down to close, then state is not changed.

@lrholmes
Copy link

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.

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!

@r4mdat
Copy link

r4mdat commented Jan 26, 2020

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.

@deniscreamer
Copy link

deniscreamer commented Jan 30, 2020

@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

@scarlac
Copy link
Contributor

scarlac commented Feb 3, 2020

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 (recommended)

@martsie
Copy link

martsie commented Feb 3, 2020

Same issue - even with the patched versions.

@scarlac
Copy link
Contributor

scarlac commented Feb 3, 2020

@martsie You need to recompile your app. Set a breakpoints in the newly added lines to verify

@scarlac
Copy link
Contributor

scarlac commented Feb 3, 2020

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

@thomasttvo
Copy link

thomasttvo commented Feb 10, 2020

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>

@bmkopp10
Copy link

@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.

alloy pushed a commit that referenced this issue Feb 13, 2020
Summary:
Starting on iOS 13, a View Controller presented modally will have a "bottom sheet" style unless it's explicitly presented full screen.

Before this, modals on iOS were only being dismissed programatically by setting `visible={false}`. However, now that the dismissal can happen on the OS side, we need a callback to be able to update the state.

This PR reuses the `onRequestClose` prop already available for tvOS and Android, and makes it work on iOS for this use case.

Should fix #26892

## Changelog

[iOS] [Added] - Add support for onRequestClose prop to Modal on iOS 13+
Pull Request resolved: #27618

Test Plan:
I tested this using the RNTester app with the Modal example:

1. Select any presentation style other than the full screen ones
2. Tap Present and the modal is presented
3. Swipe down on the presented modal until dismissed
4. Tap Present again and a second modal should be presented

![Screen Recording 2019-12-26 at 14 05 33](https://user-images.githubusercontent.com/8739/71477208-0ac88c80-27e9-11ea-9342-8631426a9b80.gif)

Differential Revision: D19235758

Pulled By: shergin

fbshipit-source-id: c0f1d946c77ce8d1baab209eaef7eb64697851df
@aca-hakan-pinar
Copy link

aca-hakan-pinar commented Mar 2, 2020

Hey everyone,

Im having the same issue. onDismiss property doesn't clear the Modal component (swipe down iOS). Can someone help me?

Thanks

@ps73
Copy link

ps73 commented Oct 23, 2020

Hey,
I have built a patch file for react-native 0.63.3 that fixes this issue: https://gist.github.com/ps73/012b8b97bb7866db4b2b4636a8396d98

If you embed this to your project you just have to set the onRequestClose property and the swipe down works as expected for formSheet and pageSheet modals on iOS. If you don't set onRequestClose the swipe down is disabled.

@alloy
Copy link
Contributor

alloy commented Oct 23, 2020

@ps73 It’s a little hard for me to judge if this is generic enough that it would be a solution for all. If you do think or want to let others weigh in, mind creating a PR for it?

@lsiqueira-jc
Copy link

Eu encontrei uma solução alternativa muito mais simples, parece em vez de:

onPress = { ( )  =>  setModalVisible ( true )

Você pode tentar isso

onPressIn = { ( )  =>  setModalVisible ( false ) } 
onPressOut = { ( )  =>  setModalVisible ( true ) }

Claro, isso significa que o componente do botão deve fornecer esses eventos (TouchableOpacity fornece)

Show man!
very good.

@jamesmcn1
Copy link

@vasiliicuhar solution worked for me with RN 0.62.2:

      <Modal
        visible={visible}
        animationType={animation || 'slide'}
        onRequestClose={onClose}
        presentationStyle={presentationStyle}
        onDismiss={onDismiss}
        statusBarTranslucent={statusBarTranslucent}
      >
        <TouchableWithoutFeedback
          // for bug which prevents dismiss from firing on swipe close
          // https://github.com/facebook/react-native/issues/26892
          onPressOut={onDismiss}
        >
          <SafeAreaView
            testID={generateDismissibleModalTestId(testIDBase)}
            style={styles.SafeAreaView}
            forceInset={{ bottom: 'always' }}
          >
            {this.content()}
          </SafeAreaView>
        </TouchableWithoutFeedback>
      </Modal>

@arvidnilber
Copy link

@alloy update on this bug? Not working yet in 0.63.4.

@alloy
Copy link
Contributor

alloy commented Jan 21, 2021

I alas have no updates on this, my last question was if somebody wanted to solve it.

@oarsoy
Copy link

oarsoy commented Jan 21, 2021

That's embarrassing. Nobody could fix this Modal problem in React Native for long time.

@edi
Copy link

edi commented Jan 28, 2021

I've tried quite a few of the above, nothing works for me ... others are quite ugly to implement.

Any advice on how to work around this issue ... seems quite weird.

@ps73
Copy link

ps73 commented Jan 29, 2021

Hey,
I have built a patch file for react-native 0.63.3 that fixes this issue: https://gist.github.com/ps73/012b8b97bb7866db4b2b4636a8396d98

If you embed this to your project you just have to set the onRequestClose property and the swipe down works as expected for formSheet and pageSheet modals on iOS. If you don't set onRequestClose the swipe down is disabled.

@edi I've updated my patch-file for 0.63.4.

Just follow my instructions in the first comment. After that you have to reinstall your app on your simulator or real device.

After all these steps you can use onRequestClose property to allow the swipe down gesture (equivalent to android back button behavior). You can also disable swipe-gesture by unsetting onRequestClose.

@arvidnilber
Copy link

So there is a fix for this issue? Why not include this in a new release?

@oarsoy
Copy link

oarsoy commented Feb 10, 2021

Airbnb was right. React Native really s*cks. After every version change, even so basic functionalities stop working for no reason and it's really tiring to track these things. And it seems Facebook or repository admins do NOT want to fix/merge the most of these issues.

I'm switching to Flutter.

@jacobp100
Copy link
Contributor

Have people tried in 0.64?

@Yerlan
Copy link

Yerlan commented Mar 27, 2021

Have people tried in 0.64?

Not working in v0.64 as well

@oarsoy
Copy link

oarsoy commented Mar 27, 2021

Have people tried in 0.64?

Not working in v0.64 as well

:) No surprise.

@Yerlan
Copy link

Yerlan commented Mar 28, 2021

:) No surprise.

Yeah, there must be a good reason (I assume it's performance) why the patch was included and then removed from the recent version.

As a temporary workaround, you can use React Native Modalize, I think they pretty much have everything you want to work with page/form sheet modals (inc. swipe down to close).

80501705-458d2d80-895f-11ea-9667-d193c135cabf

@enestatli
Copy link

enestatli commented Apr 3, 2021

My solution works only who uses react-navigation v5 in her/his project.

Create a modal stack in your navigator and nest a regular stack inside it. Then simply call your modal screen anywhere in your project, just like your other screens. navigation.navigate('MyModalScreen')

Now, you can have ModalPresentationIOS transition in both platform.

When you cannot pass the navigation prop into the component directly, you can use useNavigation()hook which gives access to navigation object.

 <RootStack.Navigator mode="modal">
      <RootStack.Screen
        name="Main"
        component={MainStackScreen}
        options={{ headerShown: false }}
      />
      <RootStack.Screen name="MyModalScreen" component={MyModalScreen} />
</RootStack.Navigator>

@rockwotj
Copy link

@rewieer can this be reopened? This is still an issue.

@fallaciousreasoning
Copy link

fallaciousreasoning commented Apr 19, 2021

I just encountered this too. I was pretty surprised @thomasttvo's suggestion fixed it. Would you mind explaining how the fix works?

@r4mdat just put your Modal in a View with height: 0

<View style={{ height: 0 }}>
    <Modal ...>
        <YourModalContent />
    </Modal>
<View>

@rockwotj
Copy link

@fallaciousreasoning the height=0 trick doesn't seem to work for me :/

@thomasttvo
Copy link

I just encountered this too. I was pretty surprised @thomasttvo's suggestion fixed it. Would you mind explaining how the fix works?

@r4mdat just put your Modal in a View with height: 0

<View style={{ height: 0 }}>
    <Modal ...>
        <YourModalContent />
    </Modal>
<View>

That was a long time ago, but I vaguely remember it has something to do with event propagation and TouchableWithoutFeedback, not entirely sure tho.

@oarsoy
Copy link

oarsoy commented May 1, 2021

:) No surprise.

Yeah, there must be a good reason (I assume it's performance) why the patch was included and then removed from the recent version.

As a temporary workaround, you can use React Native Modalize, I think they pretty much have everything you want to work with page/form sheet modals (inc. swipe down to close).

80501705-458d2d80-895f-11ea-9667-d193c135cabf

Modal is very base functionality. And it should stay as native. It doesn't make sense to use a third party dependency or library to be able to solve this basic problem or to be able to use modal.

@anthlasserre
Copy link

@vasiliicuhar solution worked for me with RN 0.62.2:

      <Modal
        visible={visible}
        animationType={animation || 'slide'}
        onRequestClose={onClose}
        presentationStyle={presentationStyle}
        onDismiss={onDismiss}
        statusBarTranslucent={statusBarTranslucent}
      >
        <TouchableWithoutFeedback
          // for bug which prevents dismiss from firing on swipe close
          // https://github.com/facebook/react-native/issues/26892
          onPressOut={onDismiss}
        >
          <SafeAreaView
            testID={generateDismissibleModalTestId(testIDBase)}
            style={styles.SafeAreaView}
            forceInset={{ bottom: 'always' }}
          >
            {this.content()}
          </SafeAreaView>
        </TouchableWithoutFeedback>
      </Modal>

Thanks @vasiliicuhar
I Confirm! Actually the only trick to close this type of modal is to do like this

<Modal
  visible={showModal}
  animationType="slide"
  presentationStyle="formSheet"
>
  <TouchableWithoutFeedback
    // for bug which prevents dismiss from firing on swipe close
    // https://github.com/facebook/react-native/issues/26892
    onPressOut={() => console.log('Close modal')}
  >
    <View style={{ flex: 1 }}>
      {/* YOUR CONTENT */}
    </View>
  </TouchableWithoutFeedback>
</Modal>

@oarsoy
Copy link

oarsoy commented May 5, 2021

<Modal
  visible={showModal}
  animationType="slide"
  presentationStyle="formSheet"
>
  <TouchableWithoutFeedback
    // for bug which prevents dismiss from firing on swipe close
    // https://github.com/facebook/react-native/issues/26892
    onPressOut={() => console.log('Close modal')}
  >
    <View style={{ flex: 1 }}>
      {/* YOUR CONTENT */}
    </View>
  </TouchableWithoutFeedback>
</Modal>

I can confirm too, this example finally works with 0.63 & 0.64. I hope they will not break again this base functionality.

@whalemare
Copy link

whalemare commented May 20, 2021

This solution close modal on any touch outside, not on swipe.

We are waiting for merge this pr: #31500

@oarsoy
Copy link

oarsoy commented May 20, 2021

This solution close modal on any touch outside, not on swipe.

We are waiting for merge this pr: #31500

I agree it's not ideal but only working solution so far. I hope they merge this because I think FB hates iOS and they love to keep it broken.

@sgup
Copy link

sgup commented Jun 16, 2021

Still having this issue

@Jonatthu
Copy link

Jonatthu commented Aug 1, 2021

I am still having issues with this. Waiting on #31500

@facebook facebook locked as resolved and limited conversation to collaborators Oct 3, 2021
@react-native-bot react-native-bot added the Resolution: Locked This issue was locked by the bot. label Oct 3, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Bug Platform: iOS iOS applications. Resolution: Locked This issue was locked by the bot.
Projects
None yet
Development

Successfully merging a pull request may close this issue.