/
githubmr
/
facebook-react-native
Обзор
Документация
Войти
/
githubmr
/
facebook-react-native
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTVirtualViewContainerState.mm
190 строк
5 KB
Luna Wei
Fix VirtualViewExperimental alignment for empty cases (#53519)
29 авг 2025, 00:34
29 авг 2025, 00:34
41a1467
Код
Авторство
О чём код?
/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import <React/RCTLog.h> #import <React/RCTScrollViewComponentView.h> #import <React/RCTVirtualViewMode.h> #import <UIKit/UIKit.h> #import <react/featureflags/ReactNativeFeatureFlags.h> #import "RCTVirtualViewContainerState.h" using namespace facebook; using namespace facebook::react; #if RCT_DEBUG static void debugLog(NSString *msg, ...) { auto debugEnabled = ReactNativeFeatureFlags::enableVirtualViewDebugFeatures(); if (!debugEnabled) { return; } va_list args; va_start(args, msg); NSString *msgString = [[NSString alloc] initWithFormat:msg arguments:args]; RCTLogInfo(@"%@", msgString); va_end(args); // Don't forget to call va_end to clean up } #endif /** * Checks whether one CGRect overlaps with another CGRect. * * This is different from CGRectIntersectsRect because a CGRect representing * a line or a point is considered to overlap with another CGRect if the line * or point is within the rect bounds. However, two CGRects are not considered * to overlap if they only share a boundary. */ static BOOL CGRectOverlaps(CGRect rect1, CGRect rect2) { CGFloat minY1 = CGRectGetMinY(rect1); CGFloat maxY1 = CGRectGetMaxY(rect1); CGFloat minY2 = CGRectGetMinY(rect2); CGFloat maxY2 = CGRectGetMaxY(rect2); if (minY1 >= maxY2 || minY2 >= maxY1) { // No overlap on the y-axis. return NO; } CGFloat minX1 = CGRectGetMinX(rect1); CGFloat maxX1 = CGRectGetMaxX(rect1); CGFloat minX2 = CGRectGetMinX(rect2); CGFloat maxX2 = CGRectGetMaxX(rect2); if (minX1 >= maxX2 || minX2 >= maxX1) { // No overlap on the x-axis. return NO; } return YES; } @interface RCTVirtualViewContainerState () <UIScrollViewDelegate> @end @interface RCTVirtualViewContainerState () { NSMutableSet<id<RCTVirtualViewProtocol>> *_virtualViews; CGRect _emptyRect; CGRect _prerenderRect; __weak RCTScrollViewComponentView *_scrollViewComponentView; CGFloat _prerenderRatio; } @end @implementation RCTVirtualViewContainerState - (instancetype)initWithScrollView:(RCTScrollViewComponentView *)scrollView { self = [super init]; if (self != nil) { _virtualViews = [NSMutableSet set]; _emptyRect = CGRectZero; _prerenderRect = CGRectZero; _scrollViewComponentView = scrollView; _prerenderRatio = ReactNativeFeatureFlags::virtualViewPrerenderRatio(); [_scrollViewComponentView addScrollListener:self]; #if RCT_DEBUG debugLog(@"initWithScrollView"); #endif } return self; } - (void)dealloc { #if RCT_DEBUG debugLog(@"dealloc"); #endif if (_scrollViewComponentView != nil) { [_scrollViewComponentView removeScrollListener:self]; _scrollViewComponentView = nil; } [_virtualViews removeAllObjects]; } #pragma mark - Public API - (void)onChange:(id<RCTVirtualViewProtocol>)virtualView { if (![_virtualViews containsObject:virtualView]) { [_virtualViews addObject:virtualView]; #if RCT_DEBUG debugLog(@"Add virtualViewID=%@", virtualView.virtualViewID); #endif } else { #if RCT_DEBUG debugLog(@"Update virtualViewID=%@", virtualView.virtualViewID); #endif } [self _updateModes:virtualView]; } - (void)remove:(id<RCTVirtualViewProtocol>)virtualView { if (![_virtualViews containsObject:virtualView]) { RCTLogError(@"Attempting to remove non-existent VirtualView: %@", virtualView.virtualViewID); } [_virtualViews removeObject:virtualView]; #if RCT_DEBUG debugLog(@"Remove virtualViewID=%@", virtualView.virtualViewID); #endif } #pragma mark - Private Helpers - (void)_updateModes:(id<RCTVirtualViewProtocol>)virtualView { auto scrollView = _scrollViewComponentView.scrollView; CGRect visibleRect = CGRectMake( scrollView.contentOffset.x, scrollView.contentOffset.y, scrollView.frame.size.width, scrollView.frame.size.height); _prerenderRect = visibleRect; _prerenderRect = CGRectInset( _prerenderRect, -_prerenderRect.size.width * _prerenderRatio, -_prerenderRect.size.height * _prerenderRatio); NSArray<id<RCTVirtualViewProtocol>> *virtualViewsIt = (virtualView != nullptr) ? @[ virtualView ] : [_virtualViews allObjects]; for (id<RCTVirtualViewProtocol> vv = nullptr in virtualViewsIt) { CGRect rect = [vv containerRelativeRect:scrollView]; RCTVirtualViewMode mode = RCTVirtualViewModeHidden; CGRect thresholdRect = _emptyRect; if (CGRectOverlaps(rect, visibleRect)) { thresholdRect = visibleRect; mode = RCTVirtualViewModeVisible; } else if (CGRectOverlaps(rect, _prerenderRect)) { mode = RCTVirtualViewModePrerender; thresholdRect = _prerenderRect; } #if RCT_DEBUG debugLog( @"UpdateModes virtualView=%@ mode=%ld rect=%@ thresholdRect=%@", vv.virtualViewID, (long)mode, NSStringFromCGRect(rect), NSStringFromCGRect(thresholdRect)); #endif [vv onModeChange:mode targetRect:rect thresholdRect:thresholdRect]; } } #pragma mark - UIScrollViewDelegate - (void)scrollViewDidScroll:(UIScrollView *)scrollView { [self _updateModes:nil]; } @end