Skip to content

Commit

Permalink
Patch RCTScrollView to fix centerContent
Browse files Browse the repository at this point in the history
  • Loading branch information
gaearon committed Nov 13, 2024
1 parent aa55798 commit 875accd
Showing 1 changed file with 102 additions and 147 deletions.
249 changes: 102 additions & 147 deletions patches/react-native+0.74.1.patch
Original file line number Diff line number Diff line change
@@ -1,170 +1,125 @@
diff --git a/node_modules/react-native/Libraries/Blob/RCTFileReaderModule.mm b/node_modules/react-native/Libraries/Blob/RCTFileReaderModule.mm
index caa5540..c5d4e67 100644
--- a/node_modules/react-native/Libraries/Blob/RCTFileReaderModule.mm
+++ b/node_modules/react-native/Libraries/Blob/RCTFileReaderModule.mm
@@ -73,7 +73,7 @@ @implementation RCTFileReaderModule
} else {
NSString *type = [RCTConvert NSString:blob[@"type"]];
NSString *text = [NSString stringWithFormat:@"data:%@;base64,%@",
- type != nil && [type length] > 0 ? type : @"application/octet-stream",
+ ![type isEqual:[NSNull null]] && [type length] > 0 ? type : @"application/octet-stream",
[data base64EncodedStringWithOptions:0]];

resolve(text);
diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm
index b0d71dc..41b9a0e 100644
--- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm
+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputView.mm
@@ -377,10 +377,6 @@ - (void)textInputDidBeginEditing
self.backedTextInputView.attributedText = [NSAttributedString new];
diff --git a/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m b/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m
index 1aead51..6749d79 100644
--- a/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m
+++ b/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m
@@ -50,6 +50,7 @@ - (instancetype)initWithFrame:(CGRect)frame

_pinchGestureEnabled = YES;
}

- if (_selectTextOnFocus) {
- [self.backedTextInputView selectAll:nil];
- }
-
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus
reactTag:self.reactTag
text:[self.backedTextInputView.attributedText.string copy]
@@ -611,6 +607,10 @@ - (UIView *)reactAccessibilityElement
- (void)reactFocus
{
[self.backedTextInputView reactFocus];
+
+ if (_selectTextOnFocus) {
+ [self.backedTextInputView selectAll:nil];
+ }
return self;
}

- (void)reactBlur
diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h
index e9b330f..ec5f58c 100644
--- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h
+++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h
@@ -15,5 +15,8 @@
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) RCTDirectEventBlock onRefresh;
@property (nonatomic, weak) UIScrollView *scrollView;
+@property (nonatomic, copy) UIColor *customTintColor;
+
+- (void)forwarderBeginRefreshing;

@end
diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m
index b09e653..288e60c 100644
--- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m
+++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m
@@ -22,6 +22,7 @@ @implementation RCTRefreshControl {
NSString *_title;
UIColor *_titleColor;
CGFloat _progressViewOffset;
+ UIColor *_customTintColor;

@@ -159,31 +160,6 @@ - (BOOL)touchesShouldCancelInContentView:(__unused UIView *)view
return !shouldDisableScrollInteraction;
}

- (instancetype)init
@@ -56,6 +57,12 @@ - (void)layoutSubviews
_isInitialRender = false;

-/*
- * Automatically centers the content such that if the content is smaller than the
- * ScrollView, we force it to be centered, but when you zoom or the content otherwise
- * becomes larger than the ScrollView, there is no padding around the content but it
- * can still fill the whole view.
- */
-- (void)setContentOffset:(CGPoint)contentOffset
-{
- UIView *contentView = [self contentView];
- if (contentView && _centerContent && !CGSizeEqualToSize(contentView.frame.size, CGSizeZero)) {
- CGSize subviewSize = contentView.frame.size;
- CGSize scrollViewSize = self.bounds.size;
- if (subviewSize.width <= scrollViewSize.width) {
- contentOffset.x = -(scrollViewSize.width - subviewSize.width) / 2.0;
- }
- if (subviewSize.height <= scrollViewSize.height) {
- contentOffset.y = -(scrollViewSize.height - subviewSize.height) / 2.0;
- }
- }
-
- super.contentOffset = CGPointMake(
- RCTSanitizeNaNValue(contentOffset.x, @"scrollView.contentOffset.x"),
- RCTSanitizeNaNValue(contentOffset.y, @"scrollView.contentOffset.y"));
-}
-
- (void)setFrame:(CGRect)frame
{
// Preserving and revalidating `contentOffset`.
@@ -427,6 +403,11 @@ - (void)setRemoveClippedSubviews:(__unused BOOL)removeClippedSubviews
// Does nothing
}

+- (void)didMoveToSuperview
+{
+ [super didMoveToSuperview];
+ [self setTintColor:_customTintColor];

+- (void)setFrame:(CGRect)frame {
+ [super setFrame:frame];
+ [self centerContentIfNeeded];
+}
+
- (void)beginRefreshingProgrammatically
- (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex
{
UInt64 beginRefreshingTimestamp = _currentRefreshingStateTimestamp;
@@ -203,4 +210,58 @@ - (void)refreshControlValueChanged
[super insertReactSubview:view atIndex:atIndex];
@@ -441,9 +422,14 @@ - (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex
@"RCTScrollView may only contain a single subview, the already set subview looks like: %@",
[_contentView react_recursiveDescription]);
_contentView = view;
+
+
RCTApplyTransformationAccordingLayoutDirection(_contentView, self.reactLayoutDirection);
+
[_scrollView addSubview:view];
}
+
+ [self centerContentIfNeeded];
}

+- (void)setCustomTintColor:(UIColor *)customTintColor

- (void)removeReactSubview:(UIView *)subview
@@ -652,9 +638,44 @@ -(void)delegateMethod : (UIScrollView *)scrollView \
}

RCT_SCROLL_EVENT_HANDLER(scrollViewWillBeginDecelerating, onMomentumScrollBegin)
-RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, onScroll)
RCT_SCROLL_EVENT_HANDLER(scrollViewDidScrollToTop, onScrollToTop)

+-(void)scrollViewDidZoom : (UIScrollView *)scrollView
+{
+ _customTintColor = customTintColor;
+ [self setTintColor:customTintColor];
+}
+ [self centerContentIfNeeded];
+
+// Fix for https://github.com/facebook/react-native/issues/43388
+// A bug in iOS 17.4 causes the haptic to not play when refreshing if the tintColor
+// is set before the refresh control gets added to the scrollview. We'll call this
+// function whenever the superview changes. We'll also call it if the value of customTintColor
+// changes.
+- (void)setTintColor:(UIColor *)tintColor
+{
+ if ([self.superview isKindOfClass:[UIScrollView class]] && self.tintColor != tintColor) {
+ [super setTintColor:tintColor];
+ }
+ RCT_SEND_SCROLL_EVENT(onScroll, nil);
+ RCT_FORWARD_SCROLL_EVENT(scrollViewDidZoom : scrollView);
+}
+
+/*
+ This method is used by Bluesky's ExpoScrollForwarder. This allows other React Native
+ libraries to perform a refresh of a scrollview and access the refresh control's onRefresh
+ function.
+ * Automatically centers the content such that if the content is smaller than the
+ * ScrollView, we force it to be centered, but when you zoom or the content otherwise
+ * becomes larger than the ScrollView, there is no padding around the content but it
+ * can still fill the whole view.
+ *
+ * PATCHED: This deviates from the original React Native implementation to fix two issues:
+ *
+ * - The scroll view was swallowing any taps immediately after pinching
+ * - The scroll view was jittering when crossing the full screen threshold while pinching
+ *
+ * This implementation is based on https://petersteinberger.com/blog/2013/how-to-center-uiscrollview/.
+ */
+- (void)forwarderBeginRefreshing
+-(void)centerContentIfNeeded
+{
+ _refreshingProgrammatically = NO;
+
+ [self sizeToFit];
+
+ if (!self.scrollView) {
+ return;
+ }
+
+ UIScrollView *scrollView = (UIScrollView *)self.scrollView;
+
+ [UIView animateWithDuration:0.3
+ delay:0
+ options:UIViewAnimationOptionBeginFromCurrentState
+ animations:^(void) {
+ // Whenever we call this method, the scrollview will always be at a position of
+ // -130 or less. Scrolling back to -65 simulates the default behavior of RCTRefreshControl
+ [scrollView setContentOffset:CGPointMake(0, -65)];
+ if (_scrollView.centerContent) {
+ CGFloat top = 0, left = 0;
+ if (self.contentSize.width < self.bounds.size.width) {
+ left = (self.bounds.size.width - self.contentSize.width) * 0.5f;
+ }
+ completion:^(__unused BOOL finished) {
+ [super beginRefreshing];
+ [self setCurrentRefreshingState:super.refreshing];
+
+ if (self->_onRefresh) {
+ self->_onRefresh(nil);
+ }
+ if (self.contentSize.height < self.bounds.size.height) {
+ top = (self.bounds.size.height - self.contentSize.height) * 0.5f;
+ }
+ ];
+ _scrollView.contentInset = UIEdgeInsetsMake(top, left, top, left);
+ }
+}
+
@end
diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m
index 40aaf9c..1c60164 100644
--- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m
+++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m
@@ -22,11 +22,12 @@ - (UIView *)view

RCT_EXPORT_VIEW_PROPERTY(onRefresh, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(refreshing, BOOL)
-RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(title, NSString)
RCT_EXPORT_VIEW_PROPERTY(titleColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(progressViewOffset, CGFloat)

+RCT_REMAP_VIEW_PROPERTY(tintColor, customTintColor, UIColor)
+
RCT_EXPORT_METHOD(setNativeRefreshing : (nonnull NSNumber *)viewTag toRefreshing : (BOOL)refreshing)
- (void)addScrollListener:(NSObject<UIScrollViewDelegate> *)scrollListener
{
[self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.java
index 5f5e1ab..aac00b6 100644
--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.java
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.java
@@ -99,8 +99,9 @@ public class JavaTimerManager {
}

// If the JS thread is busy for multiple frames we cancel any other pending runnable.
- if (mCurrentIdleCallbackRunnable != null) {
- mCurrentIdleCallbackRunnable.cancel();
+ IdleCallbackRunnable currentRunnable = mCurrentIdleCallbackRunnable;
+ if (currentRunnable != null) {
+ currentRunnable.cancel();
}

mCurrentIdleCallbackRunnable = new IdleCallbackRunnable(frameTimeNanos);
[_scrollListeners addObject:scrollListener];
@@ -913,6 +934,7 @@ - (void)updateContentSizeIfNeeded
CGSize contentSize = self.contentSize;
if (!CGSizeEqualToSize(_scrollView.contentSize, contentSize)) {
_scrollView.contentSize = contentSize;
+ [self centerContentIfNeeded];
}
}

0 comments on commit 875accd

Please sign in to comment.