Skip to content

Add iOS 15 UISheetPresentationController options #7322

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

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
34 changes: 34 additions & 0 deletions lib/ios/RNNCommandsHandler.m
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,40 @@ - (void)showModal:(NSDictionary *)layout
[_layoutManager addPendingViewController:newVc];

__weak UIViewController *weakNewVC = newVc;

if (@available(iOS 15.0, *)) {
UISheetPresentationController *sheet = [weakNewVC sheetPresentationController];

if (sheet) {
[sheet setPrefersScrollingExpandsWhenScrolledToEdge:[withDefault.modal.prefersScrollingExpandsWhenScrolledToEdge withDefault:true]];
[sheet setPrefersEdgeAttachedInCompactHeight:[withDefault.modal.prefersEdgeAttachedInCompactHeight withDefault:false]];
[sheet setWidthFollowsPreferredContentSizeWhenEdgeAttached:[withDefault.modal.widthFollowsPreferredContentSizeWhenEdgeAttached withDefault:false]];
[sheet setPrefersGrabberVisible:[withDefault.modal.prefersGrabberVisible withDefault:false]];


[sheet setDetents:@[UISheetPresentationControllerDetent.mediumDetent, UISheetPresentationControllerDetent.largeDetent]];


if (withDefault.modal.detents) {
// FIXME: This should be handled with conversion rather than straightforward usage
//[sheet setDetents:withDefault.modal.detents];
}
// FIXME: This should be handled with conversion which is incorrect
if (withDefault.modal.largestUndimmedDetent.hasValue) {
//[sheet setLargestUndimmedDetentIdentifier:[RNNConvert UISheetPresentationControllerDetentIdentifier:[withDefault.modal.largestUndimmedDetent withDefault:nil]]];
[sheet setLargestUndimmedDetentIdentifier:UISheetPresentationControllerDetentIdentifierLarge];
}
// FIXME: This should be handled with conversion rather than straightforward usage
if (withDefault.modal.selectedDetentIdentifier.hasValue) {
//[sheet setSelectedDetentIdentifier:[RNNConvert UISheetPresentationControllerDetentIdentifier:[withDefault.modal.selectedDetentIdentifier withDefault:nil]]];
[sheet setSelectedDetentIdentifier:UISheetPresentationControllerDetentIdentifierMedium];
}
if (withDefault.modal.preferredCornerRadius.hasValue) {
[sheet setPreferredCornerRadius:[withDefault.modal.preferredCornerRadius.get doubleValue]];
}
}
}

newVc.waitForRender = [withDefault.animations.showModal.enter shouldWaitForRender];
newVc.modalPresentationStyle = [RNNConvert
UIModalPresentationStyle:[withDefault.modalPresentationStyle withDefault:@"default"]];
Expand Down
2 changes: 2 additions & 0 deletions lib/ios/RNNConvert.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@

+ (UIModalTransitionStyle)UIModalTransitionStyle:(id)json;

+ (UISheetPresentationControllerDetentIdentifier)UISheetPresentationControllerDetentIdentifier:(id)json;

@end
7 changes: 7 additions & 0 deletions lib/ios/RNNConvert.m
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,11 @@ + (UIModalPresentationStyle)defaultModalPresentationStyle {
}),
UIModalPresentationFullScreen, integerValue)

//FIXME: The conversion is incorrect
//RCT_ENUM_CONVERTER(UISheetPresentationControllerDetentIdentifier, (@{
// @"medium" : UISheetPresentationControllerDetentIdentifierMedium,
// @"large" : UISheetPresentationControllerDetentIdentifierLarge
// }),
// nil, integerValue)

@end
8 changes: 8 additions & 0 deletions lib/ios/RNNModalOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,13 @@
@interface RNNModalOptions : RNNOptions

@property(nonatomic, strong) Bool *swipeToDismiss;
@property(nonatomic, strong) Bool *prefersScrollingExpandsWhenScrolledToEdge;
@property(nonatomic, strong) Bool *prefersEdgeAttachedInCompactHeight;
@property(nonatomic, strong) Bool *widthFollowsPreferredContentSizeWhenEdgeAttached;
@property(nonatomic, strong) Bool *prefersGrabberVisible;
@property(nonatomic, strong) Number *preferredCornerRadius;
@property(nonatomic, strong) NSArray<Text *> *detents;
@property(nonatomic, strong) Text *largestUndimmedDetent;
@property(nonatomic, strong) Text *selectedDetentIdentifier;

@end
29 changes: 29 additions & 0 deletions lib/ios/RNNModalOptions.m
Original file line number Diff line number Diff line change
@@ -1,16 +1,45 @@
#import "RNNModalOptions.h"
#import "OptionsArrayParser.h"

@implementation RNNModalOptions

- (instancetype)initWithDict:(NSDictionary *)dict {
self = [super initWithDict:dict];
self.swipeToDismiss = [BoolParser parse:dict key:@"swipeToDismiss"];
self.prefersScrollingExpandsWhenScrolledToEdge = [BoolParser parse:dict key:@"prefersScrollingExpandsWhenScrolledToEdge"];
self.prefersEdgeAttachedInCompactHeight = [BoolParser parse:dict key:@"prefersEdgeAttachedInCompactHeight"];
self.widthFollowsPreferredContentSizeWhenEdgeAttached = [BoolParser parse:dict key:@"widthFollowsPreferredContentSizeWhenEdgeAttached"];
self.prefersGrabberVisible = [BoolParser parse:dict key:@"prefersGrabberVisible"];
self.preferredCornerRadius = [NumberParser parse:dict key:@"preferredCornerRadius"];
self.largestUndimmedDetent = [TextParser parse:dict key:@"largestUndimmedDetent"];
self.selectedDetentIdentifier = [TextParser parse:dict key:@"selectedDetentIdentifier"];
//FIXME: Probably incorrect usage of the parser and incorrect class
self.detents = [OptionsArrayParser parse:dict key:@"detents" ofClass:Text.class];
return self;
}

- (void)mergeOptions:(RNNModalOptions *)options {
if (options.swipeToDismiss.hasValue)
self.swipeToDismiss = options.swipeToDismiss;
if (options.prefersScrollingExpandsWhenScrolledToEdge.hasValue) {
self.prefersScrollingExpandsWhenScrolledToEdge = options.prefersScrollingExpandsWhenScrolledToEdge;
}
if (options.prefersEdgeAttachedInCompactHeight.hasValue)
self.prefersEdgeAttachedInCompactHeight = options.prefersEdgeAttachedInCompactHeight;
if (options.widthFollowsPreferredContentSizeWhenEdgeAttached.hasValue) {
self.widthFollowsPreferredContentSizeWhenEdgeAttached = options.widthFollowsPreferredContentSizeWhenEdgeAttached;
}
if (options.prefersGrabberVisible.hasValue)
self.prefersGrabberVisible = options.prefersGrabberVisible;
if (options.preferredCornerRadius.hasValue)
self.preferredCornerRadius = options.preferredCornerRadius;
if (options.largestUndimmedDetent.hasValue)
self.largestUndimmedDetent = options.largestUndimmedDetent;
if (options.selectedDetentIdentifier.hasValue)
self.selectedDetentIdentifier = options.selectedDetentIdentifier;
if (options.detents)
self.detents = options.detents;

}

@end
54 changes: 54 additions & 0 deletions lib/src/interfaces/Options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1086,12 +1086,66 @@ export interface OverlayOptions {
handleKeyboardEvents?: boolean;
}

export enum ModalDetent {
medium = "medium",
large = "large",
}

export interface ModalOptions {
/**
* Control whether this modal should be dismiss using swipe gesture when the modalPresentationStyle = 'pageSheet'
* #### (iOS specific)
*/
swipeToDismiss?: boolean;

/**
* The array of heights where a sheet can rest.
* #### (iOS 15+ specific)
*/
detents?: ModalDetent[],

/**
* The largest detent that doesn’t dim the view underneath the sheet.
* #### (iOS 15+ specific)
*/
largestUndimmedDetent?: ModalDetent,

/**
* The identifier of the most recently selected detent.
* #### (iOS 15+ specific)
* */
selectedDetentIdentifier?: ModalDetent,

/**
* A boolean value that determines whether scrolling expands the sheet to a larger detent.
* After the sheet reaches its largest detent, scrolling begins.
* #### (iOS 15+ specific)
*/
prefersScrollingExpandsWhenScrolledToEdge?: boolean,

/**
* A boolean value that determines whether the sheet attaches to the bottom edge of the screen in a compact-height size class.
* #### (iOS 15+ specific)
*/
prefersEdgeAttachedInCompactHeight?: boolean,

/**
* A boolean value that determines whether the sheet's width matches its view controller's preferred content size.
* #### (iOS 15+ specific)
*/
widthFollowsPreferredContentSizeWhenEdgeAttached?: boolean,

/**
* The corner radius that the sheet attempts to present with.
* #### (iOS 15+ specific)
*/
preferredCornerRadius?: number;

/**
* A boolean value that determines whether the sheet shows a grabber at the top.
* #### (iOS 15+ specific)
*/
prefersGrabberVisible?: boolean;
}

export interface OptionsPreviewAction {
Expand Down
17 changes: 16 additions & 1 deletion playground/src/screens/NavigationScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { Platform } from 'react-native';
import { NavigationComponentProps, OptionsModalPresentationStyle } from 'react-native-navigation';
import { ModalDetent, NavigationComponentProps, OptionsModalPresentationStyle } from 'react-native-navigation';
import Root from '../components/Root';
import Button from '../components/Button';
import Navigation from './../services/Navigation';
Expand Down Expand Up @@ -43,6 +43,7 @@ export default class NavigationScreen extends React.Component<Props> {
<Root componentId={this.props.componentId} testID={NAVIGATION_SCREEN}>
<Button label="Set Root" testID={SET_ROOT_BTN} onPress={this.setRoot} />
<Button label="Modal" testID={MODAL_BTN} onPress={this.showModal} />
<Button label="Modal (iOS15)" onPress={this.showModalIOS15} />
<Button
label="PageSheet modal"
testID={PAGE_SHEET_MODAL_BTN}
Expand Down Expand Up @@ -82,6 +83,20 @@ export default class NavigationScreen extends React.Component<Props> {
}

setRoot = () => Navigation.showModal(Screens.SetRoot);

showModalIOS15 = () => Navigation.showModal(Screens.Modal, {
//TODO: Detents can't be used because of incorrect parsing in RNNModalOptions.m
modal: {
prefersEdgeAttachedInCompactHeight: false,
prefersGrabberVisible: true,
preferredCornerRadius: 30,
widthFollowsPreferredContentSizeWhenEdgeAttached: true,
prefersScrollingExpandsWhenScrolledToEdge: true,
largestUndimmedDetent: ModalDetent.large,
selectedDetentIdentifier: ModalDetent.medium,
}
});

showModal = () => Navigation.showModal(Screens.Modal);

showPageSheetModal = () =>
Expand Down
67 changes: 66 additions & 1 deletion website/docs/api/options-modal.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,73 @@ const options = {

## `swipeToDismiss`

Control wether this modal should be dismiss using swipe gesture when the `modalPresentationStyle` is `pageSheet`
Control whether this modal should be dismissed using swipe gesture when the `modalPresentationStyle` is `pageSheet`

| Type | Required | Platform | Default |
| ------------------------------------- | -------- | -------- | -------- |
| boolean | No | Both | true |

## `detents`

The array of heights where a sheet can rest.

| Type | Required | Platform | Default |
| ------------------------------------- | -------- | -------- | -------- |
| ModalDetent[] | No | iOS 15+ | 'large' |

## `largestUndimmedDetent`

The largest detent that doesn’t dim the view underneath the sheet.

| Type | Required | Platform | Default |
| ------------------------------------- | -------- | -------- | --------- |
| ModalDetent | No | iOS 15+ | undefined |

## `selectedDetentIdentifier`

The identifier of the most recently selected detent.

| Type | Required | Platform | Default |
| ------------------------------------- | -------- | -------- | --------- |
| ModalDetent | No | iOS 15+ | undefined |

## `prefersScrollingExpandsWhenScrolledToEdge`

A boolean value that determines whether scrolling expands the sheet to a larger detent.
After the sheet reaches its largest detent, scrolling begins.

| Type | Required | Platform | Default |
| ------------------------------------- | -------- | -------- | --------- |
| boolean | No | iOS 15+ | true |

## `prefersEdgeAttachedInCompactHeight`

A boolean value that determines whether the sheet attaches to the bottom edge of the screen in a compact-height size class.

| Type | Required | Platform | Default |
| ------------------------------------- | -------- | -------- | --------- |
| boolean | No | iOS 15+ | false |

## `widthFollowsPreferredContentSizeWhenEdgeAttached`

A boolean value that determines whether the sheet's width matches its view controller's preferred content size.

| Type | Required | Platform | Default |
| ------------------------------------- | -------- | -------- | --------- |
| boolean | No | iOS 15+ | false |

## `preferredCornerRadius`

The corner radius that the sheet attempts to present with.

| Type | Required | Platform | Default |
| ------------------------------------- | -------- | -------- | --------- |
| number | No | iOS 15+ | undefined |

## `prefersGrabberVisible`

A boolean value that determines whether the sheet shows a grabber at the top.

| Type | Required | Platform | Default |
| ------------------------------------- | -------- | -------- | --------- |
| boolean | No | iOS 15+ | false |