Skip to content

Allows interactive dismissal of modals on iOS #42309

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

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
75e3e85
interactiveDismissal allows the interactive dismissal of the modal vi…
ChrisSchofieldCheckatrade Jan 16, 2024
6904dff
add example
ChrisSchofieldCheckatrade Jan 16, 2024
c7eaee3
Merge branch 'main' of github.com:ChrisSchofieldCheckatrade/react-native
ChrisSchofieldCheckatrade Jan 16, 2024
bc42c06
fix prettier issues
ChrisSchofieldCheckatrade Jan 16, 2024
9f72db2
fix more linting issues
ChrisSchofieldCheckatrade Jan 16, 2024
c299bbd
explicit truthy check
ChrisSchofieldCheckatrade Jan 16, 2024
cead58d
explicit truthy check
ChrisSchofieldCheckatrade Jan 16, 2024
824dd16
update snapshot
ChrisSchofieldCheckatrade Jan 16, 2024
16c9636
Merge branch 'main' into main
ChrisSchofieldCheckatrade Jan 17, 2024
ad6fa52
add fabric support
ChrisSchofieldCheckatrade Jan 22, 2024
fef3578
Merge branch 'main' of github.com:ChrisSchofieldCheckatrade/react-native
ChrisSchofieldCheckatrade Jan 22, 2024
85467e6
Merge branch 'main' into main
ChrisSchofieldCheckatrade Jan 22, 2024
69d078d
implement UIAdaptivePresentationControllerDelegate so that interactiv…
ChrisSchofieldCheckatrade Jan 22, 2024
48618c0
merge
ChrisSchofieldCheckatrade Jan 22, 2024
2979a66
Merge branch 'main' into main
ChrisSchofieldCheckatrade Jan 22, 2024
01d0799
move interactiveDismissal declaration to new home for NativeProps
ChrisSchofieldCheckatrade Jan 22, 2024
15a35e6
remove duplicate declaration of view property (should no longer be f…
ChrisSchofieldCheckatrade Jan 22, 2024
1962a9d
revert src dir changes
ChrisSchofieldCheckatrade Jan 22, 2024
afd456b
put back the src dir changes again
ChrisSchofieldCheckatrade Jan 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/react-native/Libraries/Modal/Modal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ export interface ModalBaseProps {
* The `onShow` prop allows passing a function that will be called once the modal has been shown.
*/
onShow?: ((event: NativeSyntheticEvent<any>) => void) | undefined;
/**
* Allows the modal to be dismissed by an interactive gesture
*/
interactiveDismissal?: boolean | undefined;
/**
* The `onDismiss` prop allows passing a function that will be called once the modal has been dismissed.
*/
Expand Down
6 changes: 6 additions & 0 deletions packages/react-native/Libraries/Modal/Modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ export type Props = $ReadOnly<{|
* See https://reactnative.dev/docs/modal#onorientationchange
*/
onOrientationChange?: ?DirectEventHandler<OrientationChangeEvent>,

/**
* Allows the modal to be dismissed by an interactive gesture
*/
interactiveDismissal?: boolean,
|}>;

type State = {|
Expand Down Expand Up @@ -229,6 +234,7 @@ class Modal extends React.Component<Props, State> {
onStartShouldSetResponder={this._shouldSetResponder}
supportedOrientations={this.props.supportedOrientations}
onOrientationChange={this.props.onOrientationChange}
interactiveDismissal={this.props.interactiveDismissal}
testID={this.props.testID}>
<VirtualizedListContextResetter>
<ScrollView.Context.Provider value={null}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ type NativeProps = $ReadOnly<{|
* See https://reactnative.dev/docs/modal#onorientationchange
*/
onOrientationChange?: ?DirectEventHandler<OrientationChangeEvent>,

/**
* Allows the modal to be dismissed by an interactive gesture
*/
interactiveDismissal?: WithDefault<boolean, false>,
|}>;

export default (codegenNativeComponent<NativeProps>('ModalHostView', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6479,6 +6479,7 @@ type NativeProps = $ReadOnly<{|
\\"portrait\\",
>,
onOrientationChange?: ?DirectEventHandler<OrientationChangeEvent>,
interactiveDismissal?: WithDefault<boolean, false>,
|}>;
declare export default HostComponent<NativeProps>;
"
Expand Down
3 changes: 3 additions & 0 deletions packages/react-native/React/Views/RCTModalHostView.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
@property (nonatomic, copy) RCTDirectEventBlock onDismiss;
@property (nonatomic, assign) BOOL visible;

// iOS only
@property (nonatomic, assign) BOOL interactiveDismissal;

// Android only
@property (nonatomic, assign) BOOL statusBarTranslucent;
@property (nonatomic, assign) BOOL hardwareAccelerated;
Expand Down
14 changes: 14 additions & 0 deletions packages/react-native/React/Views/RCTModalHostView.m
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ - (instancetype)initWithBridge:(RCTBridge *)bridge
containerView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
_modalViewController.view = containerView;
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:bridge];
_interactiveDismissal = NO;
_isPresented = NO;

__weak typeof(self) weakSelf = self;
Expand All @@ -63,13 +64,25 @@ - (void)setOnRequestClose:(RCTDirectEventBlock)onRequestClose
_onRequestClose = onRequestClose;
}

- (BOOL)presentationControllerShouldDismiss:(UIPresentationController *)presentationController
{
return _interactiveDismissal;
}

- (void)presentationControllerDidAttemptToDismiss:(UIPresentationController *)controller
{
if (_onRequestClose != nil) {
_onRequestClose(nil);
}
}

- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController
{
if (_onDismiss) {
_onDismiss(nil);
}
}

- (void)notifyForOrientationChange
{
if (!_onOrientationChange) {
Expand Down Expand Up @@ -173,6 +186,7 @@ - (void)ensurePresentedOnlyIfNeeded
RCTAssert(self.reactViewController, @"Can't present modal view controller without a presenting view controller");

_modalViewController.supportedInterfaceOrientations = [self supportedOrientationsMask];
_modalViewController.modalInPresentation = !self.interactiveDismissal;

if ([self.animationType isEqualToString:@"fade"]) {
_modalViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ - (void)invalidate
RCT_EXPORT_VIEW_PROPERTY(hardwareAccelerated, BOOL)
RCT_EXPORT_VIEW_PROPERTY(animated, BOOL)
RCT_EXPORT_VIEW_PROPERTY(onShow, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(interactiveDismissal, BOOL)
RCT_EXPORT_VIEW_PROPERTY(onDismiss, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(supportedOrientations, NSArray)
RCT_EXPORT_VIEW_PROPERTY(onOrientationChange, RCTDirectEventBlock)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,12 @@ public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return eventTypeConstants;
}

@Override
@ReactProp(name = "interactiveDismissal")
Copy link
Contributor

Choose a reason for hiding this comment

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

enableGesture like RNSScreen seems like a better name?

Copy link
Author

@ChrisSchofieldCheckatrade ChrisSchofieldCheckatrade Jan 23, 2024

Choose a reason for hiding this comment

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

interactiveDismissal matches the terminology Apple uses for this feature, so I'd argue no, but happy to revise to match consensus. (Reference)

public void setInteractiveDismissal(ReactModalHostView view, boolean interactiveDismissal) {
// iOS only
}

@Override
protected void onAfterUpdateTransaction(ReactModalHostView view) {
super.onAfterUpdateTransaction(view);
Expand Down
34 changes: 33 additions & 1 deletion packages/rn-tester/js/examples/Modal/ModalPresentation.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ function ModalPresentation() {
transparent: false,
hardwareAccelerated: false,
statusBarTranslucent: false,
interactiveDismissal: false,
presentationStyle: Platform.select({
ios: 'fullScreen',
default: undefined,
Expand All @@ -75,6 +76,16 @@ function ModalPresentation() {
const onOrientationChange = event =>
setCurrentOrientation(event.nativeEvent.orientation);

const onDismissInternal = () => {
if (props.interactiveDismissal === true) {
setProps(prev => ({...prev, visible: false}));
}

if (props.onDismiss) {
props.onDismiss();
}
};

const controls = (
<>
<View style={styles.inlineBlock}>
Expand Down Expand Up @@ -146,6 +157,26 @@ function ModalPresentation() {
</Text>
) : null}
</View>
{Platform.OS === 'ios' && (
<View style={styles.block}>
<View style={styles.rowWithSpaceBetween}>
<Text style={styles.title}>Interactive Dismissal</Text>
<Switch
value={props.interactiveDismissal}
onValueChange={enabled =>
setProps(prev => ({...prev, interactiveDismissal: enabled}))
}
/>
</View>
{!['pageSheet', 'formSheet'].includes(presentationStyle) ||
props.animationType !== 'slide' ? (
<Text style={styles.warning}>
Modal can only be dismissed interactively whilst using 'pageSheet'
or 'formSheet' Presentation Style, and 'slide' Animation Type
</Text>
) : null}
</View>
)}
<View style={styles.block}>
<Text style={styles.title}>Supported Orientation ⚫️</Text>
<View style={styles.row}>
Expand Down Expand Up @@ -223,7 +254,8 @@ function ModalPresentation() {
<Modal
{...props}
onRequestClose={onRequestClose}
onOrientationChange={onOrientationChange}>
onOrientationChange={onOrientationChange}
onDismiss={onDismissInternal}>
<View style={styles.modalContainer}>
<View style={styles.modalInnerContainer}>
<Text testID="modal_animationType_text">
Expand Down