Skip to content

Fix new arch refresh control not shown when refreshing on mount #49240

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

Closed
wants to merge 2 commits into from
Closed
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 @@ -24,6 +24,7 @@ @interface RCTPullToRefreshViewComponentView () <RCTPullToRefreshViewViewProtoco
@end

@implementation RCTPullToRefreshViewComponentView {
BOOL _isBeforeInitialLayout;
UIRefreshControl *_refreshControl;
RCTScrollViewComponentView *__weak _scrollViewComponentView;
}
Expand All @@ -36,7 +37,7 @@ - (instancetype)initWithFrame:(CGRect)frame
// The pull-to-refresh view is not a subview of this view.
self.hidden = YES;

_props = PullToRefreshViewShadowNode::defaultSharedProps();
_isBeforeInitialLayout = YES;
[self _initializeUIRefreshControl];
}

Expand All @@ -49,11 +50,6 @@ - (void)_initializeUIRefreshControl
[_refreshControl addTarget:self
action:@selector(handleUIControlEventValueChanged)
forControlEvents:UIControlEventValueChanged];

const auto &concreteProps = static_cast<const PullToRefreshViewProps &>(*_props);

_refreshControl.tintColor = RCTUIColorFromSharedColor(concreteProps.tintColor);
[self _updateProgressViewOffset:concreteProps.progressViewOffset];
}

#pragma mark - RCTComponentViewProtocol
Expand All @@ -67,21 +63,21 @@ - (void)prepareForRecycle
{
[super prepareForRecycle];
_scrollViewComponentView = nil;
_props = nil;
_isBeforeInitialLayout = YES;
[self _initializeUIRefreshControl];
}

- (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps
{
const auto &oldConcreteProps = static_cast<const PullToRefreshViewProps &>(*_props);
const auto &newConcreteProps = static_cast<const PullToRefreshViewProps &>(*props);

if (newConcreteProps.refreshing != oldConcreteProps.refreshing) {
if (newConcreteProps.refreshing) {
[_refreshControl beginRefreshing];
} else {
[_refreshControl endRefreshing];
}
// Prop updates are ignored by _refreshControl until after the initial layout, so just store them in _props until then
if (_isBeforeInitialLayout) {
_props = std::static_pointer_cast<const BaseViewProps>(props);
return;
}

const auto &oldConcreteProps = static_cast<const PullToRefreshViewProps &>(*oldProps);
const auto &newConcreteProps = static_cast<const PullToRefreshViewProps &>(*props);

if (newConcreteProps.tintColor != oldConcreteProps.tintColor) {
_refreshControl.tintColor = RCTUIColorFromSharedColor(newConcreteProps.tintColor);
Expand All @@ -106,6 +102,15 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
if (needsUpdateTitle) {
[self _updateTitle];
}

// All prop updates must happen above the call to begin refreshing, or else _refreshControl will ignore the updates
if (newConcreteProps.refreshing != oldConcreteProps.refreshing) {
if (newConcreteProps.refreshing) {
[self beginRefreshingProgrammatically];
} else {
[_refreshControl endRefreshing];
}
}
}

#pragma mark -
Expand Down Expand Up @@ -144,6 +149,18 @@ - (void)_updateTitle

#pragma mark - Attaching & Detaching

- (void)layoutSubviews
{
[super layoutSubviews];

// Attempts to begin refreshing before the initial layout are ignored by _refreshControl. So if the control is refreshing when mounted, we need to call beginRefreshing in layoutSubviews or it won't work.
if (_isBeforeInitialLayout) {
_isBeforeInitialLayout = NO;

[self updateProps:_props oldProps:PullToRefreshViewShadowNode::defaultSharedProps()];
}
}

- (void)didMoveToSuperview
{
[super didMoveToSuperview];
Expand All @@ -167,6 +184,9 @@ - (void)_attach

if (@available(macCatalyst 13.1, *)) {
_scrollViewComponentView.scrollView.refreshControl = _refreshControl;

// This ensures that layoutSubviews is called. Without this, recycled instances won't refresh on mount
[self setNeedsLayout];
}
}

Expand All @@ -185,6 +205,20 @@ - (void)_detach
_scrollViewComponentView = nil;
}

- (void)beginRefreshingProgrammatically
{
if (!_scrollViewComponentView) {
return;
}

// When refreshing programmatically (i.e. without pulling down), we must explicitly adjust the ScrollView content offset, or else the _refreshControl won't be visible
UIScrollView *scrollView = _scrollViewComponentView.scrollView;
CGPoint offset = {scrollView.contentOffset.x, scrollView.contentOffset.y - _refreshControl.frame.size.height};
[scrollView setContentOffset:offset];

[_refreshControl beginRefreshing];
}

#pragma mark - Native commands

- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args
Expand All @@ -195,7 +229,7 @@ - (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args
- (void)setNativeRefreshing:(BOOL)refreshing
{
if (refreshing) {
[_refreshControl beginRefreshing];
[self beginRefreshingProgrammatically];
} else {
[_refreshControl endRefreshing];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ class RefreshControlExample extends React.Component {
})),
};

componentDidMount() {
this._onRefresh();
}

_onClick = row => {
row.clicks++;
this.setState({
Expand Down