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

Implement onRequestClose for iOS 13+ modals #27618

Closed
wants to merge 1 commit into from

Conversation

koke
Copy link
Contributor

@koke koke commented Dec 26, 2019

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+

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

@koke koke requested a review from shergin as a code owner December 26, 2019 13:07
@facebook-github-bot facebook-github-bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Dec 26, 2019
@react-native-bot react-native-bot added Component: Modal Platform: iOS iOS applications. Type: Enhancement A new feature or enhancement of an existing feature. labels Dec 26, 2019
@janicduplessis
Copy link
Contributor

Cc @hramos

Copy link
Contributor

@cpojer cpojer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's ship it 👍

Copy link
Contributor

@facebook-github-bot facebook-github-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cpojer is landing this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

@cpojer
Copy link
Contributor

cpojer commented Dec 27, 2019

Getting this failure at FB:

xplat/js/react-native-github/React/Fabric/Mounting/ComponentViews/Modal/RCTModalHostViewComponentView.mm:114:55: error: assigning to 'id<UIAdaptivePresentationControllerDelegate> _Nullable' from incompatible type 'RCTModalHostViewComponentView *__strong'
    _viewController.presentationController.delegate = self;
                                                      ^~~~

r0b0t3d referenced this pull request 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
@koke
Copy link
Contributor Author

koke commented Dec 27, 2019

Oh, I forgot to mention, https://github.com/facebook/react-native/blob/master/React/Fabric/Mounting/ComponentViews/Modal/RCTModalHostViewComponentView.mm probably needs updating as well, but I tried to enable fabric in the RNTester Podfile and was stuck with errors:

koke:RNTester/ (fix/26892-ios13-modal-dismiss*) $ git --no-pager diff  -- Podfile
diff --git a/RNTester/Podfile b/RNTester/Podfile
index d7fa6188ad..8e364a5879 100644
--- a/RNTester/Podfile
+++ b/RNTester/Podfile
@@ -11,7 +11,7 @@ def pods()
   project 'RNTesterPods.xcodeproj'
 
   # Enable TurboModule
-  use_react_native!(path: "..")
+  use_react_native!(path: "..", fabric_enabled: true)
   pod 'ReactCommon/turbomodule/samples', :path => '../ReactCommon'
 
   # Additional Pods which aren't included in the default Podfile
koke:RNTester/ (fix/26892-ios13-modal-dismiss*) $ pod install                    
📦 Using pod from bundler
📦 Installing dependencies with bundler...
Using CFPropertyList 3.0.2
Using concurrent-ruby 1.1.5
Using i18n 0.9.5
Using minitest 5.13.0
Using thread_safe 0.3.6
Using tzinfo 1.2.6
Using activesupport 4.2.11.1
Using httpclient 2.8.3
Using json 2.3.0
Using algoliasearch 1.27.1
Using atomos 0.1.3
Using bundler 2.1.2
Using claide 1.0.3
Using fuzzy_match 2.0.4
Using nap 1.1.0
Using cocoapods-core 1.8.4
Using cocoapods-deintegrate 1.0.4
Using cocoapods-downloader 1.3.0
Using cocoapods-plugins 1.0.0
Using cocoapods-search 1.0.0
Using cocoapods-stats 1.1.0
Using netrc 0.11.0
Using cocoapods-trunk 1.4.1
Using cocoapods-try 1.1.0
Using colored2 3.1.2
Using escape 0.0.4
Using fourflusher 2.3.1
Using gh_inspector 1.1.3
Using molinillo 0.6.6
Using ruby-macho 1.4.0
Using nanaimo 0.2.6
Using xcodeproj 1.14.0
Using cocoapods 1.8.4
Bundle complete! 1 Gemfile dependency, 33 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
Analyzing dependencies
Fetching podspec for `Folly` from `../third-party-podspecs/Folly.podspec`
Downloading dependencies
Installing Folly 2018.10.22.00
Installing React-Fabric (1000.0.0)
[!] /bin/bash -c 
set -e
#!/bin/bash
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

# This script collects the "core" component schemas used by fabric
# then uses react-native-codegen to generate the component headers
# to a location that the podspecs expect.

# shellcheck disable=SC2038

find "$PWD/../Libraries" -name "*NativeComponent.js" -print | xargs yarn flow-node packages/react-native-codegen/src/cli/combine/combine-js-to-schema-cli.js schema-rncore.json
yarn flow-node packages/react-native-codegen/buck_tests/generate-tests.js schema-rncore.json rncore ReactCommon/fabric/components/rncore rncore

yarn run v1.21.1
$ /Users/koke/src/react-native/node_modules/.bin/flow-node packages/react-native-codegen/src/cli/combine/combine-js-to-schema-cli.js schema-rncore.json /Users/koke/src/react-native/ReactCommon/../Libraries/Image/ImageViewNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/Image/TextInlineImageNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/Utilities/codegenNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/Components/SafeAreaView/RCTSafeAreaViewNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/Components/ScrollView/AndroidHorizontalScrollViewNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/Components/ScrollView/AndroidHorizontalScrollContentViewNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/Components/ScrollView/ScrollViewNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/Components/ScrollView/ScrollContentViewNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/Components/Picker/AndroidDialogPickerNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/Components/Picker/AndroidDropdownPickerNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/Components/Picker/RCTPickerNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/Components/ProgressViewIOS/RCTProgressViewNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/Components/ProgressBarAndroid/ProgressBarAndroidNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/Components/RefreshControl/AndroidSwipeRefreshLayoutNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/Components/RefreshControl/PullToRefreshViewNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/Components/TextInput/AndroidTextInputNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/Components/TextInput/RCTInputAccessoryViewNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/Components/DatePicker/RCTDatePickerNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/Components/CheckBox/AndroidCheckBoxNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/Components/Slider/SliderNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/Components/DrawerAndroid/AndroidDrawerLayoutNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/Components/SegmentedControlIOS/RCTSegmentedControlNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/Components/MaskedView/RCTMaskedViewNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/Components/ActivityIndicator/ActivityIndicatorViewNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/Components/UnimplementedViews/UnimplementedNativeViewNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/Components/View/ViewNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/Components/Switch/SwitchNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/Components/Switch/AndroidSwitchNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/Modal/RCTModalHostViewNativeComponent.js /Users/koke/src/react-native/ReactCommon/../Libraries/ReactNative/requireNativeComponent.js
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
/Users/koke/src/react-native/node_modules/babylon/lib/index.js:4457
  throw err;
  ^

SyntaxError: Unexpected token (215:5)
    at Parser.pp$5.raise (/Users/koke/src/react-native/node_modules/babylon/lib/index.js:4454:13)
    at Parser.pp.unexpected (/Users/koke/src/react-native/node_modules/babylon/lib/index.js:1761:8)
    at Parser.pp$8.flowParsePrimaryType (/Users/koke/src/react-native/node_modules/babylon/lib/index.js:5772:8)
    at Parser.pp$8.flowParsePostfixType (/Users/koke/src/react-native/node_modules/babylon/lib/index.js:5778:19)
    at Parser.pp$8.flowParsePrefixType (/Users/koke/src/react-native/node_modules/babylon/lib/index.js:5795:17)
    at Parser.pp$8.flowParseAnonFunctionWithoutParens (/Users/koke/src/react-native/node_modules/babylon/lib/index.js:5800:20)
    at Parser.pp$8.flowParseIntersectionType (/Users/koke/src/react-native/node_modules/babylon/lib/index.js:5815:19)
    at Parser.pp$8.flowParseUnionType (/Users/koke/src/react-native/node_modules/babylon/lib/index.js:5826:19)
    at Parser.pp$8.flowParseType (/Users/koke/src/react-native/node_modules/babylon/lib/index.js:5837:19)
    at Parser.pp$8.flowParseObjectType (/Users/koke/src/react-native/node_modules/babylon/lib/index.js:5476:30)
error Command failed with exit code 1.

@janicduplessis
Copy link
Contributor

Yea I guess we can remove the change from the fabric implementation and file a task to fix it later when it can compile properly in OSS. @cpojer what do you think?

@mattvagni
Copy link

Is there anything I can do to help land this (I doubt it since it sounds like internal FB failures but thought I'd ask) ?

@jpamarohorta
Copy link

It fixes the dismiss not being propagates issue in pageSheet modals but it seems to make all other modals be pageSheet regardless or the presentationStyle prop used. Anyone can reproduce the same issue with this PR?

@react-native-bot
Copy link
Collaborator

This pull request was successfully merged by @koke in 8e5fac8.

When will my fix make it into a release? | Upcoming Releases

@react-native-bot react-native-bot added the Merged This PR has been merged. label Feb 6, 2020
@scarlac
Copy link
Contributor

scarlac commented Feb 6, 2020

@jpamarohorta I believe it's not the PR itself that does it but the fact that iOS detects that the new API is being used, thus it auto-upgrades the entire app to the new iOS 13 default. I think it's by design.

@HarishMurru
Copy link

How can I get this fix into my project.? please help me, someone.

@AdrianMrn
Copy link

@HarishMurru You can use the steps @scarlac detailed in this comment: #26892 (comment)

Otherwise, you'll probably have to wait until the next release.

@jpamarohorta
Copy link

@scarlac so with this PR will we be able to use a sheet modal and a fullscreen one in the same app?

@shergin
Copy link
Contributor

shergin commented Feb 6, 2020

@koke Seems, it's breaks presentationStyle prop, so I have to unland this. Could you please investigate that?

@scarlac
Copy link
Contributor

scarlac commented Feb 7, 2020

I looked into it. The root cause is that we are changing the delegate which implies that some methods are called. If they are unimplemented, default presentation style is used instead.
Place this delegate method on the delegate object (the one assigned with _viewController.presentationController.delegate = self):

// Method must be implemented, otherwise iOS defaults to 'automatic' (pageSheet on >= iOS 13.0)
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller traitCollection:(UITraitCollection *)traitCollection
{
  return self.presentationStyle;
}

You won't normally find this via StackOverflow answers because most examples don't change the delegate.

I will also modify my gist to include this change.

Copy link
Contributor

@scarlac scarlac left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are the required changes to support changing presentationStyle after setting the .delegate.

@@ -257,4 +264,11 @@ - (UIInterfaceOrientationMask)supportedOrientationsMask
}
#endif

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll need to add this to support changing the presentation style after setting the delegate

// Method must be implemented, otherwise iOS defaults to 'automatic' (pageSheet on >= iOS 13.0)
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller traitCollection:(UITraitCollection *)traitCollection
{
  return self.presentationStyle;
}

@@ -111,6 +111,7 @@ - (instancetype)initWithFrame:(CGRect)frame
_viewController = [RCTFabricModalHostViewController new];
_viewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
_viewController.delegate = self;
_viewController.presentationController.delegate = self;
Copy link
Contributor

@scarlac scarlac Feb 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See below/above. We may need to add similar code in this file as well.

@scarlac
Copy link
Contributor

scarlac commented Feb 8, 2020

I know I'm not the author but I'm trying to solve this as well. This PR will likely suffer from the same issue as my Gist: ViewControllers underneath the modal will be unmounted after the modal finishes its transition. The net effect is that transparent modals are transparent while animating but once the animation finishes the background turns fully black (despite using overlayFullScreen). This effect is only seen on iOS 12.X devices and is also caused by setting the delegate. I solved it by adding an iOS 13 wrapper:

    if (@available(iOS 13.0, *)) {
      _modalViewController.presentationController.delegate = self;
    }

This fixes the transparency issue to some extent but transparent iOS 13 modals will cast a tiny shadow which will most likely result in new issues being reported to the repo. The reason for that is that iOS 13 now renders an extra shadow view behind the new modal that are out of our control. In most cases users won't notice but a keen eye will notice a very faint shadow while they drag down on the page sheet modals. Issue is described here:
https://stackoverflow.com/a/59524857/2135705

[...] we have inserted some private views in the view hierarchy in between UIWindow and its rootViewController's view

image

The overall conclusion is that the transition to iOS 13-style modals is troublesome and will likely cause reports like "transparency doesn't work on iOS 13" with screenshots of vague shadows (where you see "Private UIKit View(s)") despite using backgroundColor: 'transparent'. This is an iOS 13 novelty that requires custom modals to get rid of.

alloy pushed a commit that referenced this pull request 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
@jacobp100
Copy link
Contributor

@scarlac Are the issues you mentioned only for the page sheet modals, or does the change also cause issues in other modal styles too?

@scarlac
Copy link
Contributor

scarlac commented Feb 20, 2020

@jacobp100 Only for pageSheet modals. The shadow that appears is an iOS bug for page and form sheets aka “cards”.

Regular old full screen modals are fine AFAIK.

osdnk pushed a commit to osdnk/react-native that referenced this pull request Mar 9, 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 facebook#26892

## Changelog

[iOS] [Added] - Add support for onRequestClose prop to Modal on iOS 13+
Pull Request resolved: facebook#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
@noambonnie
Copy link

The issue related to this PR (#26892) is still not resolved, see discussion on the closed issue. Is there a way to reopen the issue?

@Foveluy
Copy link

Foveluy commented Apr 8, 2020

@noambonnie the reason why doesn't solve the problem is that RN - team doesn't release a version that contain this merge.

I have checked the code v0.62.0 - v0.62.1 , it doesn't contain this change at all.

so i have to change the native code by myself and it worked perfectly

@mrousavy
Copy link
Contributor

mrousavy commented May 9, 2020

@Foveluy what changes did you make, to "enable" this functionality for 0.62?

@alloy
Copy link
Contributor

alloy commented May 12, 2020

@noambonnie the reason why doesn't solve the problem is that RN - team doesn't release a version that contain this merge.

I have checked the code v0.62.0 - v0.62.1 , it doesn't contain this change at all.

so i have to change the native code by myself and it worked perfectly

This change went into v0.62.0, see the CHANGELOG. The reason you couldn’t find it is perhaps because it was cherry-picked into the v0.62-stable branch and thus its commit hash changed: 6d00239

@alloy
Copy link
Contributor

alloy commented May 12, 2020

The issue related to this PR (#26892) is still not resolved, see discussion on the closed issue. Is there a way to reopen the issue?

Done.

@mrousavy
Copy link
Contributor

mrousavy commented Jul 9, 2020

@koke @alloy since react-native 0.63 was released the fix is broken. Dragging down doesn't work at all, so onRequestClose doesn't get called anymore either..

I'm using patch-package to fix this, so I've updated my patch: https://gist.github.com/mrousavy/b4a4ce2df5ff61d12f46829edb132c7c

Did I miss something?
Screen Recording 2020-07-09 at 13 40 49 mov

@koke
Copy link
Contributor Author

koke commented Jul 9, 2020

Sorry, I'm not working with React Native any longer

@alekseyanikin
Copy link

alekseyanikin commented Jul 21, 2020

@koke @alloy since react-native 0.63 was released the fix is broken. Dragging down doesn't work at all, so onRequestClose doesn't get called anymore either..

I'm using patch-package to fix this, so I've updated my patch: https://gist.github.com/mrousavy/b4a4ce2df5ff61d12f46829edb132c7c

Did I miss something?

ref: 1e7ed81

you can undo this change if you want this behavior

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Component: Modal Merged This PR has been merged. Platform: iOS iOS applications. Type: Enhancement A new feature or enhancement of an existing feature.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

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