-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Patch RCTScrollView to fix centerContent
- Loading branch information
Showing
1 changed file
with
102 additions
and
147 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]; | ||
} | ||
} | ||
|