Skip to content
Merged
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
7 changes: 7 additions & 0 deletions packages/react-native/React/Base/RCTRootView.m
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,13 @@ - (BOOL)canBecomeFirstResponder
#endif // macOS]
}

#if TARGET_OS_OSX // [macOS
- (void)viewDidEndLiveResize {
[super viewDidEndLiveResize];
[self setNeedsLayout];
}
#endif // macOS]

- (void)setLoadingView:(RCTUIView *)loadingView // [macOS]
{
_loadingView = loadingView;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,15 @@ - (void)disableActivityIndicatorAutoHide:(BOOL)disabled
_autoHideDisabled = disabled;
}

#pragma mark - NSView

#if TARGET_OS_OSX // [macOS
- (void)viewDidEndLiveResize {
[super viewDidEndLiveResize];
[self setNeedsLayout];
}
#endif // macOS]

#pragma mark - isActivityIndicatorViewVisible

- (void)setIsActivityIndicatorViewVisible:(BOOL)visible
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, assign) BOOL snapToEnd;
@property (nonatomic, copy) NSArray<NSNumber *> *snapToOffsets;

#if TARGET_OS_OSX // [macOS
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated;
- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated;
- (void)flashScrollIndicators;
#endif // macOS]

/*
* Makes `setContentOffset:` method no-op when given `block` is executed.
* The block is being executed synchronously.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ - (instancetype)initWithFrame:(CGRect)frame
// NSScrollView.automaticallyAdjustsContentInsets (default YES) adds contentInset.top to push content below the toolbar.
// However, React Native doesn't know about this native contentInset adjustments,causing some caltulation issues
self.automaticallyAdjustsContentInsets = NO;
self.hasHorizontalScroller = YES;
self.hasVerticalScroller = YES;
self.autohidesScrollers = YES;
#endif // macOS]

__weak __typeof(self) weakSelf = self;
Expand Down Expand Up @@ -104,17 +107,84 @@ - (void)setContentOffset:(CGPoint)contentOffset
if (_isSetContentOffsetDisabled) {
return;
}
#if !TARGET_OS_OSX // [macOS]
super.contentOffset = CGPointMake(
RCTSanitizeNaNValue(contentOffset.x, @"scrollView.contentOffset.x"),
RCTSanitizeNaNValue(contentOffset.y, @"scrollView.contentOffset.y"));
#else // [macOS
if (!NSEqualPoints(contentOffset, self.documentVisibleRect.origin)) {
[self.contentView scrollToPoint:contentOffset];
[self reflectScrolledClipView:self.contentView];
}
#endif // macOS]
}

- (void)setFrame:(CGRect)frame
{
#if !TARGET_OS_OSX // [macOS]
[super setFrame:frame];
[self centerContentIfNeeded];
#else // [macOS
// Preserving and revalidating `contentOffset`.
CGPoint originalOffset = self.contentOffset;

[super setFrame:frame];

UIEdgeInsets contentInset = self.contentInset;
CGSize contentSize = self.contentSize;

// If contentSize has not been measured yet we can't check bounds.
if (CGSizeEqualToSize(contentSize, CGSizeZero)) {
self.contentOffset = originalOffset;
} else {
CGSize boundsSize = self.bounds.size;
CGFloat xMaxOffset = contentSize.width - boundsSize.width + contentInset.right;
CGFloat yMaxOffset = contentSize.height - boundsSize.height + contentInset.bottom;
// Make sure offset doesn't exceed bounds. This can happen on screen rotation.
if ((originalOffset.x >= -contentInset.left) && (originalOffset.x <= xMaxOffset) &&
(originalOffset.y >= -contentInset.top) && (originalOffset.y <= yMaxOffset)) {
return;
}
self.contentOffset = CGPointMake(
MAX(-contentInset.left, MIN(xMaxOffset, originalOffset.x)),
MAX(-contentInset.top, MIN(yMaxOffset, originalOffset.y)));
}
#endif // macOS]
}

#if TARGET_OS_OSX // [macOS
- (NSSize)contentSize
{
if (!self.documentView) {
return [super contentSize];
}

return self.documentView.frame.size;
}

- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated
{
if (animated) {
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:0.3];
[[self.contentView animator] setBoundsOrigin:contentOffset];
[NSAnimationContext endGrouping];
} else {
self.contentOffset = contentOffset;
}
}

- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated
{
[self magnifyToFitRect:rect];
}

- (void)flashScrollIndicators
{
[self flashScrollers];
}
#endif // macOS]

- (void)didAddSubview:(RCTPlatformView *)subview // [macOS]
{
[super didAddSubview:subview];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, strong, readonly)
RCTGenericDelegateSplitter<id<RCTUIScrollViewDelegate>> *scrollViewDelegateSplitter;

#if TARGET_OS_OSX // [macOS
@property (nonatomic, assign) UIEdgeInsets contentInset;
#endif // macOS]

@end

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,15 @@ @interface RCTScrollViewComponentView () <
RCTScrollableProtocol,
RCTEnhancedScrollViewOverridingDelegate>

- (void)_preserveContentOffsetIfNeededWithBlock:(void (^)())block;
- (void)_remountChildrenIfNeeded;
- (void)_remountChildren;
- (void)_forceDispatchNextScrollEvent;
- (void)_handleScrollEndIfNeeded;
- (void)_handleFinishedScrolling:(RCTUIScrollView *)scrollView;
- (void)_prepareForMaintainVisibleScrollPosition;
- (void)_adjustForMaintainVisibleContentPosition;

@end

@implementation RCTScrollViewComponentView {
Expand Down Expand Up @@ -151,7 +160,7 @@ - (instancetype)initWithFrame:(CGRect)frame
_containerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[_scrollView setDocumentView:_containerView];
#endif // macOS]

#if !TARGET_OS_OSX // [macOS]
[self.scrollViewDelegateSplitter addDelegate:self];
#endif // [macOS]
Expand Down Expand Up @@ -279,6 +288,19 @@ static inline UIViewAnimationOptions animationOptionsWithCurve(UIViewAnimationCu
}
#endif

#if TARGET_OS_OSX // [macOS
- (void)setContentInset:(UIEdgeInsets)contentInset
{
if (UIEdgeInsetsEqualToEdgeInsets(contentInset, _contentInset)) {
return;
}

_contentInset = contentInset;
_scrollView.contentInset = contentInset;
_scrollView.scrollIndicatorInsets = contentInset;
}
#endif // macOS]

- (RCTGenericDelegateSplitter<id<RCTUIScrollViewDelegate>> *)scrollViewDelegateSplitter
{
return ((RCTEnhancedScrollView *)_scrollView).delegateSplitter;
Expand Down Expand Up @@ -408,7 +430,11 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
MAP_SCROLL_VIEW_PROP(zoomScale);

if (oldScrollViewProps.contentInset != newScrollViewProps.contentInset) {
#if !TARGET_OS_OSX // [macOS]
_scrollView.contentInset = RCTUIEdgeInsetsFromEdgeInsets(newScrollViewProps.contentInset);
#else // [macOS
self.contentInset = RCTUIEdgeInsetsFromEdgeInsets(newScrollViewProps.contentInset);
#endif // macOS]
}

RCTEnhancedScrollView *scrollView = (RCTEnhancedScrollView *)_scrollView;
Expand Down Expand Up @@ -452,7 +478,7 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
_shouldUpdateContentInsetAdjustmentBehavior = NO;
}
#endif // [macOS]

MAP_SCROLL_VIEW_PROP(disableIntervalMomentum);
MAP_SCROLL_VIEW_PROP(snapToInterval);

Expand Down Expand Up @@ -673,6 +699,22 @@ - (void)prepareForRecycle
_firstVisibleView = nil;
}

#if TARGET_OS_OSX // [macOS
#pragma mark - NSScrollView scroll notification

- (void)scrollViewDocumentViewBoundsDidChange:(__unused NSNotification *)notification
{
RCTEnhancedScrollView *scrollView = _scrollView;

if (scrollView.centerContent) {
// Update content centering through contentOffset setter
[scrollView setContentOffset:scrollView.contentOffset];
}

[self scrollViewDidScroll:scrollView];
}
#endif // macOS]

#pragma mark - UIScrollViewDelegate

#if !TARGET_OS_OSX // [macOS]
Expand Down Expand Up @@ -816,6 +858,22 @@ - (void)viewDidMoveToWindow // [macOS]
[super viewDidMoveToWindow];
#endif // [macOS]

#if TARGET_OS_OSX // [macOS
NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
if (self.window == nil) {
// Unregister scrollview's clipview bounds change notifications
[defaultCenter removeObserver:self
name:NSViewBoundsDidChangeNotification
object:_scrollView.contentView];
} else {
// Register for scrollview's clipview bounds change notifications so we can track scrolling
[defaultCenter addObserver:self
selector:@selector(scrollViewDocumentViewBoundsDidChange:)
name:NSViewBoundsDidChangeNotification
object:_scrollView.contentView]; // NSClipView
}
#endif // macOS]

if (!self.window) {
// The view is being removed, ensure that the scroll end event is dispatched
[self _handleScrollEndIfNeeded];
Expand Down Expand Up @@ -898,6 +956,8 @@ - (void)flashScrollIndicators
{
#if !TARGET_OS_OSX // [macOS]
[_scrollView flashScrollIndicators];
#else // [macOS
[(RCTEnhancedScrollView *)_scrollView flashScrollers];
#endif // [macOS]
}

Expand Down Expand Up @@ -997,7 +1057,11 @@ - (void)scrollToOffset:(CGPoint)offset animated:(BOOL)animated

[self _forceDispatchNextScrollEvent];

#if !TARGET_OS_OSX // [macOS]
[_scrollView setContentOffset:offset animated:animated];
#else // [macOS
[(RCTEnhancedScrollView *)_scrollView setContentOffset:offset animated:animated];
#endif // macOS]

if (!animated) {
// When not animated, the expected workflow in ``scrollViewDidEndScrollingAnimation`` after scrolling is not going
Expand All @@ -1010,6 +1074,8 @@ - (void)zoomToRect:(CGRect)rect animated:(BOOL)animated
{
#if !TARGET_OS_OSX // [macOS]
[_scrollView zoomToRect:rect animated:animated];
#else // [macOS
[(RCTEnhancedScrollView *)_scrollView zoomToRect:rect animated:animated];
#endif // [macOS]
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
[self invalidateLayer];
}
}
#else // [macOS SAAD
#else // [macOS
- (void)viewDidChangeEffectiveAppearance
{
[super viewDidChangeEffectiveAppearance];
Expand Down Expand Up @@ -1716,7 +1716,6 @@ - (BOOL)didActivateAccessibilityCustomAction:(UIAccessibilityCustomAction *)acti
}

#if TARGET_OS_OSX // [macOS

- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args
{
if ([commandName isEqualToString:@"focus"]) {
Expand Down
25 changes: 25 additions & 0 deletions packages/react-native/React/Views/ScrollView/RCTScrollView.m
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ @implementation RCTScrollView {
BOOL _allowNextScrollNoMatterWhat;
#if TARGET_OS_OSX // [macOS
BOOL _notifyDidScroll;
BOOL _disableScrollEvents;
NSPoint _lastScrollPosition;
#endif // macOS]
CGRect _lastClippedToRect;
Expand Down Expand Up @@ -570,8 +571,28 @@ - (void)setRemoveClippedSubviews:(__unused BOOL)removeClippedSubviews

- (void)setFrame:(CGRect)frame
{
#if !TARGET_OS_OSX // [macOS]
[super setFrame:frame];
#else // [macOS
/**
* Setting the frame on the scroll view will randomly generate between 0 and 4 scroll events. These events happen
* during the layout phase of the view which generates layout notifications that are sent through the bridge.
* Because the bridge is heavily used, the scroll events are throttled and reach the JS thread with a random delay.
* Because the scroll event stores the clip and content view size, delayed scroll events will submit stale layout
* information that can break virtual list implemenations.
* By disabling scroll events during the execution of the setFrame method and scheduling one notification on
* the next run loop, we can mitigate the delayed scroll event by sending it at a time where the bridge is not busy.
*/
_disableScrollEvents = YES;
[super setFrame:frame];
_disableScrollEvents = NO;

if (self.window != nil && !self.window.inLiveResize) {
[self performSelector:@selector(scrollViewDocumentViewBoundsDidChange:) withObject:nil afterDelay:0];
}
#endif // macOS]
[self centerContentIfNeeded];

}

- (void)insertReactSubview:(RCTPlatformView *)view atIndex:(NSInteger)atIndex // [macOS]
Expand Down Expand Up @@ -867,6 +888,10 @@ - (void)flashScrollIndicators
#if TARGET_OS_OSX // [macOS
- (void)scrollViewDocumentViewBoundsDidChange:(__unused NSNotification *)notification
{
if (_disableScrollEvents) {
return;
}

if (_scrollView.centerContent) {
// contentOffset setter dynamically centers content when _centerContent == YES
[_scrollView setContentOffset:_scrollView.contentOffset];
Expand Down
Loading