diff --git a/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm b/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm index 1b02e8b2d39672063551411d5c403a69b671a869..b3481c1b98b45dea769035140dc2fd8d9b088b24 100644 --- a/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm +++ b/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm @@ -102,6 +102,20 @@ - (void)setContentOffset:(CGPoint)contentOffset RCTSanitizeNaNValue(contentOffset.y, @"scrollView.contentOffset.y")); } +- (void)setCenterContent:(BOOL)centerContent +{ + if (_centerContent != centerContent) { + _centerContent = centerContent; + [self centerContentIfNeeded]; + } +} + +- (void)setContentSize:(CGSize)contentSize +{ + [super setContentSize:contentSize]; + [self centerContentIfNeeded]; +} + - (void)setFrame:(CGRect)frame { [super setFrame:frame]; diff --git a/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm b/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm index a087536f3af0d33b13fe38d8abd1bc6d7935def2..01f5c884ea4772350c0ebe6263723d97632f2b74 100644 --- a/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm +++ b/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm @@ -396,7 +396,15 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared & MAP_SCROLL_VIEW_PROP(zoomScale); - if (oldScrollViewProps.contentInset != newScrollViewProps.contentInset) { + // When disabling centerContent, reset inset to prop value + // (enabling is handled automatically by the setCenterContent: setter) + if (oldScrollViewProps.centerContent && !newScrollViewProps.centerContent) { + _scrollView.contentInset = RCTUIEdgeInsetsFromEdgeInsets(newScrollViewProps.contentInset); + } + + // Only apply contentInset from props if centerContent is disabled + // When centerContent is enabled, the inset is calculated by centerContentIfNeeded + if (oldScrollViewProps.contentInset != newScrollViewProps.contentInset && !newScrollViewProps.centerContent) { _scrollView.contentInset = RCTUIEdgeInsetsFromEdgeInsets(newScrollViewProps.contentInset); } @@ -523,7 +531,7 @@ - (UIView *)betterHitTest:(CGPoint)point withEvent:(UIEvent *)event } } - return isPointInside ? self : nil; + return isPointInside ? _scrollView : nil; } /* @@ -1133,6 +1141,11 @@ - (RCTVirtualViewContainerState *)virtualViewContainerState return _virtualViewContainerState; } ++ (BOOL)shouldBeRecycled +{ + return NO; +} + @end Class RCTScrollViewCls(void) diff --git a/React/Views/RefreshControl/RCTRefreshControl.h b/React/Views/RefreshControl/RCTRefreshControl.h index ed306d7cadbf36a2fed79be8bd9d68b5dca135bd..d447dad534fefa9fcbdbbde6dcbbdcddadd5a824 100644 --- a/React/Views/RefreshControl/RCTRefreshControl.h +++ b/React/Views/RefreshControl/RCTRefreshControl.h @@ -18,6 +18,7 @@ __attribute__((deprecated("This API will be removed along with the legacy archit @property (nonatomic, copy) NSString *title; @property (nonatomic, copy) RCTDirectEventBlock onRefresh; @property (nonatomic, weak) UIScrollView *scrollView; +@property (nonatomic, copy) UIColor *customTintColor; @end diff --git a/React/Views/RefreshControl/RCTRefreshControl.m b/React/Views/RefreshControl/RCTRefreshControl.m index 2dc86e464264c9450eef18d7b153d35bf6a5cc55..6661dc69a04766afa0284d6e83839b219e98cf57 100644 --- a/React/Views/RefreshControl/RCTRefreshControl.m +++ b/React/Views/RefreshControl/RCTRefreshControl.m @@ -25,6 +25,7 @@ @implementation RCTRefreshControl { UIColor *_titleColor; CGFloat _progressViewOffset; BOOL _hasMovedToWindow; + UIColor *_customTintColor; } - (instancetype)init @@ -60,6 +61,12 @@ - (void)layoutSubviews _isInitialRender = false; } +- (void)didMoveToSuperview +{ + [super didMoveToSuperview]; + [self setTintColor:_customTintColor]; +} + - (void)didMoveToWindow { [super didMoveToWindow]; @@ -225,6 +232,18 @@ - (void)refreshControlValueChanged } } +// 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]; + } +} + @end #endif // RCT_REMOVE_LEGACY_ARCH diff --git a/React/Views/RefreshControl/RCTRefreshControlManager.m b/React/Views/RefreshControl/RCTRefreshControlManager.m index 1e9ff527f4e6691d716da624031113a397876981..44329c5422c6f24d8a437fa35c6f2bad6bf8622b 100644 --- a/React/Views/RefreshControl/RCTRefreshControlManager.m +++ b/React/Views/RefreshControl/RCTRefreshControlManager.m @@ -24,11 +24,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) { [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt index 59775241c80bec99ad3ec080f2425aacc8900c24..426de3aa77cda2032d7b0991e2ca3f8482a438d3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt +++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt @@ -459,6 +459,13 @@ public open class ReactViewGroup public constructor(context: Context?) : inSubviewClippingLoop = true var clippedSoFar = 0 for (i in 0..