diff --git a/Libraries/Components/SafeAreaView/SafeAreaView.android.js b/Libraries/Components/SafeAreaView/SafeAreaView.android.js new file mode 100644 index 000000000..462659955 --- /dev/null +++ b/Libraries/Components/SafeAreaView/SafeAreaView.android.js @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule SafeAreaView + * @flow + */ +'use strict'; + +module.exports = require('View'); diff --git a/Libraries/Components/SafeAreaView/SafeAreaView.ios.js b/Libraries/Components/SafeAreaView/SafeAreaView.ios.js new file mode 100644 index 000000000..35ae985a4 --- /dev/null +++ b/Libraries/Components/SafeAreaView/SafeAreaView.ios.js @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule SafeAreaView + * @flow + * @format + */ + +const React = require('React'); +const ViewPropTypes = require('ViewPropTypes'); +const requireNativeComponent = require('requireNativeComponent'); + +import type {ViewProps} from 'ViewPropTypes'; + +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 feflect physical limitation of the screen, + * such as rounded corners or camera notches (aka sensor housing area on iPhone X). + */ +class SafeAreaView extends React.Component { + static propTypes = { + ...ViewPropTypes, + }; + + render() { + return ; + } +} + +const RCTSafeAreaView = requireNativeComponent('RCTSafeAreaView', { + name: 'RCTSafeAreaView', + displayName: 'RCTSafeAreaView', + propTypes: { + ...ViewPropTypes, + }, +}); + +module.exports = SafeAreaView; diff --git a/Libraries/react-native/react-native-implementation.js b/Libraries/react-native/react-native-implementation.js index 74cb8622c..9019d15d1 100644 --- a/Libraries/react-native/react-native-implementation.js +++ b/Libraries/react-native/react-native-implementation.js @@ -37,6 +37,7 @@ const ReactNative = { get PickerIOS() { return require('PickerIOS'); }, get ProgressBarAndroid() { return require('ProgressBarAndroid'); }, get ProgressViewIOS() { return require('ProgressViewIOS'); }, + get SafeAreaView() { return require('SafeAreaView'); }, get ScrollView() { return require('ScrollView'); }, get SectionList() { return require('SectionList'); }, get SegmentedControlIOS() { return require('SegmentedControlIOS'); }, diff --git a/React/Views/SafeAreaView/RCTSafeAreaShadowView.h b/React/Views/SafeAreaView/RCTSafeAreaShadowView.h new file mode 100644 index 000000000..9987b22e5 --- /dev/null +++ b/React/Views/SafeAreaView/RCTSafeAreaShadowView.h @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RCTSafeAreaShadowView : RCTShadowView + +@end + +NS_ASSUME_NONNULL_END diff --git a/React/Views/SafeAreaView/RCTSafeAreaShadowView.m b/React/Views/SafeAreaView/RCTSafeAreaShadowView.m new file mode 100644 index 000000000..8b749bcce --- /dev/null +++ b/React/Views/SafeAreaView/RCTSafeAreaShadowView.m @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTSafeAreaShadowView.h" + +#import +#import + +#import "RCTSafeAreaViewLocalData.h" + +@implementation RCTSafeAreaShadowView + +- (void)setLocalData:(RCTSafeAreaViewLocalData *)localData +{ + RCTAssert([localData isKindOfClass:[RCTSafeAreaViewLocalData class]], + @"Local data object for `RCTSafeAreaShadowView` must be `RCTSafeAreaViewLocalData` instance."); + + UIEdgeInsets insets = localData.insets; + + super.paddingLeft = (YGValue){insets.left, YGUnitPoint}; + super.paddingRight = (YGValue){insets.right, YGUnitPoint}; + super.paddingTop = (YGValue){insets.top, YGUnitPoint}; + super.paddingBottom = (YGValue){insets.bottom, YGUnitPoint}; + + [self didSetProps:@[@"paddingLeft", @"paddingRight", @"paddingTop", @"paddingBottom"]]; +} + +/** + * Removing support for setting padding from any outside code + * to prevent interferring this with local data. + */ +- (void)setPadding:(YGValue)value {} +- (void)setPaddingLeft:(YGValue)value {} +- (void)setPaddingRight:(YGValue)value {} +- (void)setPaddingTop:(YGValue)value {} +- (void)setPaddingBottom:(YGValue)value {} + +@end diff --git a/React/Views/SafeAreaView/RCTSafeAreaView.h b/React/Views/SafeAreaView/RCTSafeAreaView.h new file mode 100644 index 000000000..c2605f095 --- /dev/null +++ b/React/Views/SafeAreaView/RCTSafeAreaView.h @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RCTBridge; + +@interface RCTSafeAreaView : RCTView + +- (instancetype)initWithBridge:(RCTBridge *)bridge; + +@end + +NS_ASSUME_NONNULL_END diff --git a/React/Views/SafeAreaView/RCTSafeAreaView.m b/React/Views/SafeAreaView/RCTSafeAreaView.m new file mode 100644 index 000000000..3cf1494f3 --- /dev/null +++ b/React/Views/SafeAreaView/RCTSafeAreaView.m @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTSafeAreaView.h" + +#import +#import + +#import "RCTSafeAreaViewLocalData.h" + +@implementation RCTSafeAreaView { + RCTBridge *_bridge; + UIEdgeInsets _currentSafeAreaInsets; +} + +- (instancetype)initWithBridge:(RCTBridge *)bridge +{ + if (self = [super initWithFrame:CGRectZero]) { + _bridge = bridge; + } + + return self; +} + +RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)decoder) +RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame) + +#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */ + +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 +{ + if (![self respondsToSelector:@selector(safeAreaInsets)]) { + return; + } + + UIEdgeInsets safeAreaInsets = self.safeAreaInsets; + + if (UIEdgeInsetsEqualToEdgeInsetsWithThreshold(safeAreaInsets, _currentSafeAreaInsets, 1.0 / RCTScreenScale())) { + return; + } + + _currentSafeAreaInsets = safeAreaInsets; + + RCTSafeAreaViewLocalData *localData = + [[RCTSafeAreaViewLocalData alloc] initWithInsets:safeAreaInsets]; + [_bridge.uiManager setLocalData:localData forView:self]; +} + +#endif + +@end diff --git a/React/Views/SafeAreaView/RCTSafeAreaViewLocalData.h b/React/Views/SafeAreaView/RCTSafeAreaViewLocalData.h new file mode 100644 index 000000000..8e2fdac7b --- /dev/null +++ b/React/Views/SafeAreaView/RCTSafeAreaViewLocalData.h @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RCTSafeAreaViewLocalData : NSObject + +- (instancetype)initWithInsets:(UIEdgeInsets)insets; + +@property (atomic, readonly) UIEdgeInsets insets; + +@end + +NS_ASSUME_NONNULL_END diff --git a/React/Views/SafeAreaView/RCTSafeAreaViewLocalData.m b/React/Views/SafeAreaView/RCTSafeAreaViewLocalData.m new file mode 100644 index 000000000..674c9ff35 --- /dev/null +++ b/React/Views/SafeAreaView/RCTSafeAreaViewLocalData.m @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTSafeAreaViewLocalData.h" + +@implementation RCTSafeAreaViewLocalData + +- (instancetype)initWithInsets:(UIEdgeInsets)insets +{ + if (self = [super init]) { + _insets = insets; + } + + return self; +} + +@end diff --git a/React/Views/SafeAreaView/RCTSafeAreaViewManager.h b/React/Views/SafeAreaView/RCTSafeAreaViewManager.h new file mode 100644 index 000000000..9c8f5f28b --- /dev/null +++ b/React/Views/SafeAreaView/RCTSafeAreaViewManager.h @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RCTSafeAreaViewManager : RCTViewManager + +@end + +NS_ASSUME_NONNULL_END diff --git a/React/Views/SafeAreaView/RCTSafeAreaViewManager.m b/React/Views/SafeAreaView/RCTSafeAreaViewManager.m new file mode 100644 index 000000000..bdbe7394c --- /dev/null +++ b/React/Views/SafeAreaView/RCTSafeAreaViewManager.m @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTSafeAreaViewManager.h" + +#import "RCTSafeAreaShadowView.h" +#import "RCTSafeAreaView.h" +#import "RCTUIManager.h" + +@implementation RCTSafeAreaViewManager + +RCT_EXPORT_MODULE() + +- (UIView *)view +{ + return [[RCTSafeAreaView alloc] initWithBridge:self.bridge]; +} + +- (RCTSafeAreaShadowView *)shadowView +{ + return [RCTSafeAreaShadowView new]; +} + +@end