SafeAreaView: A new prop `emulateUnlessSupported` that turns off the custom implementation of `safeAreaInsets` insets
Summary: In some cases, the custom implementation of this prop is undesirable, so this allows to turn it off. Reviewed By: yungsters Differential Revision: D9759228 fbshipit-source-id: 4f61cd900c2da9046977c11a61606a4f5f961177
This commit is contained in:
parent
d6b9ec1c1f
commit
454aa02210
|
@ -1,13 +0,0 @@
|
|||
/**
|
||||
* 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.
|
||||
*
|
||||
* @format
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = require('View');
|
|
@ -1,40 +0,0 @@
|
|||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
|
||||
const DeprecatedViewPropTypes = require('DeprecatedViewPropTypes');
|
||||
const React = require('React');
|
||||
|
||||
const requireNativeComponent = require('requireNativeComponent');
|
||||
|
||||
import type {ViewProps} from 'ViewPropTypes';
|
||||
|
||||
const RCTSafeAreaView = requireNativeComponent('RCTSafeAreaView');
|
||||
|
||||
type Props = ViewProps & {
|
||||
children: any,
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders nested content and automatically applies paddings reflect the portion of the view
|
||||
* that is not covered by navigation bars, tab bars, toolbars, and other ancestor views.
|
||||
* Moreover, and most importantly, Safe Area's paddings reflect physical limitation of the screen,
|
||||
* such as rounded corners or camera notches (aka sensor housing area on iPhone X).
|
||||
*/
|
||||
class SafeAreaView extends React.Component<Props> {
|
||||
static propTypes = {
|
||||
...DeprecatedViewPropTypes,
|
||||
};
|
||||
|
||||
render() {
|
||||
return <RCTSafeAreaView {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SafeAreaView;
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
*/
|
||||
|
||||
const Platform = require('Platform');
|
||||
const React = require('React');
|
||||
const View = require('View');
|
||||
const requireNativeComponent = require('requireNativeComponent');
|
||||
|
||||
import type {ViewProps} from 'ViewPropTypes';
|
||||
|
||||
type Props = $ReadOnly<{|
|
||||
...ViewProps,
|
||||
emulateUnlessSupported?: boolean,
|
||||
|}>;
|
||||
|
||||
let exported;
|
||||
|
||||
/**
|
||||
* Renders nested content and automatically applies paddings reflect the portion
|
||||
* of the view that is not covered by navigation bars, tab bars, toolbars, and
|
||||
* other ancestor views.
|
||||
*
|
||||
* Moreover, and most importantly, Safe Area's paddings reflect physical
|
||||
* limitation of the screen, such as rounded corners or camera notches (aka
|
||||
* sensor housing area on iPhone X).
|
||||
*/
|
||||
if (Platform.OS === 'android') {
|
||||
exported = class SafeAreaView extends React.Component<Props> {
|
||||
render(): React.Node {
|
||||
const {emulateUnlessSupported, ...props} = this.props;
|
||||
return <View {...props} />;
|
||||
}
|
||||
};
|
||||
} else {
|
||||
const RCTSafeAreaView = requireNativeComponent('RCTSafeAreaView');
|
||||
exported = class SafeAreaView extends React.Component<Props> {
|
||||
render(): React.Node {
|
||||
return <RCTSafeAreaView emulateUnlessSupported={true} {...this.props} />;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = exported;
|
|
@ -15,6 +15,7 @@ const Modal = require('Modal');
|
|||
const React = require('react');
|
||||
const SafeAreaView = require('SafeAreaView');
|
||||
const StyleSheet = require('StyleSheet');
|
||||
const Switch = require('Switch');
|
||||
const Text = require('Text');
|
||||
const View = require('View');
|
||||
|
||||
|
@ -26,10 +27,14 @@ exports.description =
|
|||
|
||||
class SafeAreaViewExample extends React.Component<
|
||||
{},
|
||||
{|modalVisible: boolean|},
|
||||
{|
|
||||
modalVisible: boolean,
|
||||
emulateUnlessSupported: boolean,
|
||||
|},
|
||||
> {
|
||||
state = {
|
||||
modalVisible: false,
|
||||
emulateUnlessSupported: true,
|
||||
};
|
||||
|
||||
_setModalVisible = visible => {
|
||||
|
@ -45,12 +50,21 @@ class SafeAreaViewExample extends React.Component<
|
|||
animationType="slide"
|
||||
supportedOrientations={['portrait', 'landscape']}>
|
||||
<View style={styles.modal}>
|
||||
<SafeAreaView style={styles.safeArea}>
|
||||
<SafeAreaView
|
||||
style={styles.safeArea}
|
||||
emulateUnlessSupported={this.state.emulateUnlessSupported}>
|
||||
<View style={styles.safeAreaContent}>
|
||||
<Button
|
||||
onPress={this._setModalVisible.bind(this, false)}
|
||||
title="Close"
|
||||
/>
|
||||
<Text>emulateUnlessSupported:</Text>
|
||||
<Switch
|
||||
onValueChange={value =>
|
||||
this.setState({emulateUnlessSupported: value})
|
||||
}
|
||||
value={this.state.emulateUnlessSupported}
|
||||
/>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
|
@ -59,6 +73,13 @@ class SafeAreaViewExample extends React.Component<
|
|||
onPress={this._setModalVisible.bind(this, true)}
|
||||
title="Present Modal Screen with SafeAreaView"
|
||||
/>
|
||||
<Text>emulateUnlessSupported:</Text>
|
||||
<Switch
|
||||
onValueChange={value =>
|
||||
this.setState({emulateUnlessSupported: value})
|
||||
}
|
||||
value={this.state.emulateUnlessSupported}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge;
|
||||
|
||||
@property (nonatomic, assign) BOOL emulateUnlessSupported;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
{
|
||||
if (self = [super initWithFrame:CGRectZero]) {
|
||||
_bridge = bridge;
|
||||
_emulateUnlessSupported = YES; // The default.
|
||||
}
|
||||
|
||||
return self;
|
||||
|
@ -29,38 +30,29 @@
|
|||
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)decoder)
|
||||
RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
|
||||
|
||||
static BOOL UIEdgeInsetsEqualToEdgeInsetsWithThreshold(UIEdgeInsets insets1, UIEdgeInsets insets2, CGFloat threshold) {
|
||||
return
|
||||
ABS(insets1.left - insets2.left) <= threshold &&
|
||||
ABS(insets1.right - insets2.right) <= threshold &&
|
||||
ABS(insets1.top - insets2.top) <= threshold &&
|
||||
ABS(insets1.bottom - insets2.bottom) <= threshold;
|
||||
- (BOOL)isSupportedByOS
|
||||
{
|
||||
return [self respondsToSelector:@selector(safeAreaInsets)];
|
||||
}
|
||||
|
||||
- (UIEdgeInsets)safeAreaInsetsIfSupportedAndEnabled
|
||||
{
|
||||
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */
|
||||
|
||||
- (void)safeAreaInsetsDidChange
|
||||
{
|
||||
if (![self respondsToSelector:@selector(safeAreaInsets)]) {
|
||||
return;
|
||||
if (self.isSupportedByOS) {
|
||||
return self.safeAreaInsets;
|
||||
}
|
||||
|
||||
[self setSafeAreaInsets:self.safeAreaInsets];
|
||||
#endif
|
||||
return self.emulateUnlessSupported ? self.emulatedSafeAreaInsets : UIEdgeInsetsZero;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Emulate safe area for iOS < 11
|
||||
- (void)layoutSubviews
|
||||
- (UIEdgeInsets)emulatedSafeAreaInsets
|
||||
{
|
||||
[super layoutSubviews];
|
||||
if ([self respondsToSelector:@selector(safeAreaInsets)]) {
|
||||
return;
|
||||
}
|
||||
UIViewController* vc = self.reactViewController;
|
||||
|
||||
if (!vc) {
|
||||
return;
|
||||
return UIEdgeInsetsZero;
|
||||
}
|
||||
|
||||
CGFloat topLayoutOffset = vc.topLayoutGuide.length;
|
||||
CGFloat bottomLayoutOffset = vc.bottomLayoutGuide.length;
|
||||
CGRect safeArea = vc.view.bounds;
|
||||
|
@ -75,7 +67,34 @@ static BOOL UIEdgeInsetsEqualToEdgeInsetsWithThreshold(UIEdgeInsets insets1, UIE
|
|||
safeAreaInsets.bottom = CGRectGetMaxY(self.bounds) - CGRectGetMaxY(localSafeArea);
|
||||
}
|
||||
|
||||
[self setSafeAreaInsets:safeAreaInsets];
|
||||
return safeAreaInsets;
|
||||
}
|
||||
|
||||
static BOOL UIEdgeInsetsEqualToEdgeInsetsWithThreshold(UIEdgeInsets insets1, UIEdgeInsets insets2, CGFloat threshold) {
|
||||
return
|
||||
ABS(insets1.left - insets2.left) <= threshold &&
|
||||
ABS(insets1.right - insets2.right) <= threshold &&
|
||||
ABS(insets1.top - insets2.top) <= threshold &&
|
||||
ABS(insets1.bottom - insets2.bottom) <= threshold;
|
||||
}
|
||||
|
||||
- (void)safeAreaInsetsDidChange
|
||||
{
|
||||
[self invalidateSafeAreaInsets];
|
||||
}
|
||||
|
||||
- (void)invalidateSafeAreaInsets
|
||||
{
|
||||
[self setSafeAreaInsets:self.safeAreaInsetsIfSupportedAndEnabled];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
|
||||
if (!self.isSupportedByOS && self.emulateUnlessSupported) {
|
||||
[self invalidateSafeAreaInsets];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setSafeAreaInsets:(UIEdgeInsets)safeAreaInsets
|
||||
|
@ -90,4 +109,15 @@ static BOOL UIEdgeInsetsEqualToEdgeInsetsWithThreshold(UIEdgeInsets insets1, UIE
|
|||
[_bridge.uiManager setLocalData:localData forView:self];
|
||||
}
|
||||
|
||||
- (void)setEmulateUnlessSupported:(BOOL)emulateUnlessSupported
|
||||
{
|
||||
if (_emulateUnlessSupported == emulateUnlessSupported) {
|
||||
return;
|
||||
}
|
||||
|
||||
_emulateUnlessSupported = emulateUnlessSupported;
|
||||
|
||||
[self invalidateSafeAreaInsets];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(emulateUnlessSupported, BOOL)
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
return [[RCTSafeAreaView alloc] initWithBridge:self.bridge];
|
||||
|
|
Loading…
Reference in New Issue