Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,13 @@ import type {ColorValue} from '../../StyleSheet/StyleSheet';
import type {
GestureResponderEvent,
LayoutChangeEvent,
SafeAreaInsets,
SafeAreaInsetsChangeEvent,
ScrollEvent,
} from '../../Types/CoreEventTypes';
import type {EventSubscription} from '../../vendor/emitter/EventEmitter';
import type {KeyboardEvent, KeyboardMetrics} from '../Keyboard/Keyboard';
import type {ViewProps} from '../View/ViewPropTypes';
import type {ScrollViewStickyHeaderProps} from './ScrollViewStickyHeader';

import getSafeAreaContentPadding from '../../../src/private/components/scrollview/getSafeAreaContentPadding';
import {
HScrollContentViewNativeComponent,
HScrollViewNativeComponent,
Expand Down Expand Up @@ -891,11 +888,6 @@ class ScrollView extends React.Component<ScrollViewProps, ScrollViewState> {
layoutHeight: null,
};

// The latest safe area insets of the native view. Only the pair across the
// scrollable axis affects the content container, so a change to the other
// pair is kept here without a render.
_safeAreaInsets: ?SafeAreaInsets = null;

componentDidMount() {
this._keyboardMetrics = Keyboard.metrics();
this._additionalScrollOffset = 0;
Expand Down Expand Up @@ -1260,30 +1252,6 @@ class ScrollView extends React.Component<ScrollViewProps, ScrollViewState> {
this.props.onScroll && this.props.onScroll(e);
};

_handleSafeAreaInsetsChange = (e: SafeAreaInsetsChangeEvent) => {
const {insets} = e.nativeEvent;
const previous = this._safeAreaInsets;
this._safeAreaInsets = insets;
const changedAcrossScrollableAxis =
previous == null ||
(this.props.horizontal === true
? previous.top !== insets.top || previous.bottom !== insets.bottom
: previous.left !== insets.left || previous.right !== insets.right);
if (changedAcrossScrollableAxis && this._padsContentForSafeArea()) {
this.forceUpdate();
}
this.props.experimental_onSafeAreaInsetsChange?.(e);
};

/**
* Whether the content container is padded by the safe area across the
* scrollable axis.
*/
_padsContentForSafeArea(): boolean {
const behavior = this.props.contentInsetAdjustmentBehavior;
return Platform.OS === 'ios' && behavior != null && behavior !== 'never';
}

_handleLayout = (e: LayoutChangeEvent) => {
if (this.props.invertStickyHeaders === true) {
this.setState({layoutHeight: e.nativeEvent.layout.height});
Expand Down Expand Up @@ -1784,25 +1752,9 @@ class ScrollView extends React.Component<ScrollViewProps, ScrollViewState> {
? HScrollContentViewNativeComponent
: VScrollContentViewNativeComponent;

// Along the scrollable axis UIKit turns the safe area into content insets,
// so content scrolls under the system UI. Across it there is nothing to
// scroll, so the content container is padded instead and content is laid
// out clear of the system UI, such as the rail on iPhone Duo. Native
// removes whatever UIKit insets across that axis, so the safe area applies
// once.
const padsContentForSafeArea = this._padsContentForSafeArea();
const safeAreaInsets = this._safeAreaInsets;

const contentContainerStyle = [
horizontal && styles.contentContainerHorizontal,
this.props.contentContainerStyle,
padsContentForSafeArea && safeAreaInsets != null
? getSafeAreaContentPadding(
this.props.contentContainerStyle,
safeAreaInsets,
horizontal,
)
: null,
];
if (__DEV__ && this.props.style !== undefined) {
// $FlowFixMe[underconstrained-implicit-instantiation]
Expand Down Expand Up @@ -1939,14 +1891,6 @@ class ScrollView extends React.Component<ScrollViewProps, ScrollViewState> {
onTouchStart: this._handleTouchStart,
onTouchCancel: this._handleTouchCancel,
onScroll: this._handleScroll,
// Routed through the handler whenever the event is observed at all, so
// the insets it keeps are current when the behavior changes from
// 'never' and the content container starts padding for them.
experimental_onSafeAreaInsetsChange:
padsContentForSafeArea ||
this.props.experimental_onSafeAreaInsetsChange != null
? this._handleSafeAreaInsetsChange
: undefined,
endDraggingSensitivityMultiplier:
experimental_endDraggingSensitivityMultiplier,
scrollEventThrottle: hasStickyHeaders
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Class<RCTComponentViewProtocol> RCTInputAccessoryCls(void) __attribute__((used))
Class<RCTComponentViewProtocol> RCTParagraphCls(void) __attribute__((used));
Class<RCTComponentViewProtocol> RCTPullToRefreshViewCls(void) __attribute__((used));
Class<RCTComponentViewProtocol> RCTSafeAreaViewCls(void) __attribute__((used));
Class<RCTComponentViewProtocol> RCTScrollContentViewCls(void) __attribute__((used));
Class<RCTComponentViewProtocol> RCTScrollViewCls(void) __attribute__((used));
Class<RCTComponentViewProtocol> RCTSwitchCls(void) __attribute__((used));
Class<RCTComponentViewProtocol> RCTTextInputCls(void) __attribute__((used));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
{"Paragraph", RCTParagraphCls},
{"PullToRefreshView", RCTPullToRefreshViewCls},
{"SafeAreaView", RCTSafeAreaViewCls},
{"ScrollContentView", RCTScrollContentViewCls},
{"ScrollView", RCTScrollViewCls},
{"Switch", RCTSwitchCls},
{"TextInput", RCTTextInputCls},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#import <UIKit/UIKit.h>

#import <React/RCTViewComponentView.h>

NS_ASSUME_NONNULL_BEGIN

/*
* UIView class for the <ScrollContentView> component, the content container of a <ScrollView>.
*/
@interface RCTScrollContentViewComponentView : RCTViewComponentView

/*
* The safe area the content is laid out clear of, as padding on top of the content container's own. The enclosing
* scroll view sets the pair of insets across its scrollable axis, and zero while its `contentInsetAdjustmentBehavior`
* is `never`.
*/
@property (nonatomic, assign) UIEdgeInsets safeAreaPadding;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#import "RCTScrollContentViewComponentView.h"

#import <React/RCTI18nUtil.h>
#import <React/RCTUtils.h>
#import <react/renderer/components/scrollview/ScrollContentViewComponentDescriptor.h>
#import <react/renderer/components/scrollview/ScrollContentViewState.h>
#import "RCTConversions.h"
#import "RCTFabricComponentsPlugins.h"

using namespace facebook::react;

@implementation RCTScrollContentViewComponentView {
ScrollContentViewShadowNode::ConcreteState::Shared _state;
}

- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
_props = ScrollContentViewShadowNode::defaultSharedProps();
}

return self;
}

- (void)setSafeAreaPadding:(UIEdgeInsets)safeAreaPadding
{
_safeAreaPadding = safeAreaPadding;
[self _updateStateIfNecessary];
}

- (void)_updateStateIfNecessary
{
if (!_state) {
return;
}

UIEdgeInsets insets = _safeAreaPadding;
insets.left = RCTRoundPixelValue(insets.left);
insets.top = RCTRoundPixelValue(insets.top);
insets.right = RCTRoundPixelValue(insets.right);
insets.bottom = RCTRoundPixelValue(insets.bottom);

auto newPadding = RCTEdgeInsetsFromUIEdgeInsets(insets);
RCTI18nUtil *i18nUtil = [RCTI18nUtil sharedInstance];
auto layoutDirection = i18nUtil.isRTL ? LayoutDirection::RightToLeft : LayoutDirection::LeftToRight;
bool swapLeftAndRightInRTL = i18nUtil.isRTL && i18nUtil.doLeftAndRightSwapInRTL;
auto threshold = 1.0 / RCTScreenScale() + 0.01; // Size of a pixel plus some small threshold.

_state->updateState(
[=](const ScrollContentViewShadowNode::ConcreteState::Data &oldData)
-> ScrollContentViewShadowNode::ConcreteState::SharedData {
auto deltaPadding = newPadding - oldData.safeAreaPadding;

if (std::abs(deltaPadding.left) < threshold && std::abs(deltaPadding.top) < threshold &&
std::abs(deltaPadding.right) < threshold && std::abs(deltaPadding.bottom) < threshold &&
oldData.layoutDirection == layoutDirection && oldData.swapLeftAndRightInRTL == swapLeftAndRightInRTL) {
return nullptr;
}

auto newData = oldData;
newData.safeAreaPadding = newPadding;
newData.layoutDirection = layoutDirection;
newData.swapLeftAndRightInRTL = swapLeftAndRightInRTL;
return std::make_shared<const ScrollContentViewShadowNode::ConcreteState::Data>(newData);
});
}

#pragma mark - RCTComponentViewProtocol

+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<ScrollContentViewComponentDescriptor>();
}

- (void)updateState:(const facebook::react::State::Shared &)state
oldState:(const facebook::react::State::Shared &)oldState
{
_state = std::static_pointer_cast<const ScrollContentViewShadowNode::ConcreteState>(state);
}

- (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask
{
[super finalizeUpdates:updateMask];
[self _updateStateIfNecessary];
}

- (void)prepareForRecycle
{
[super prepareForRecycle];
_state.reset();
_safeAreaPadding = UIEdgeInsetsZero;
}

@end

Class<RCTComponentViewProtocol> RCTScrollContentViewCls(void)
{
return RCTScrollContentViewComponentView.class;
}
Loading
Loading