@onekeyfe/react-native-pager-view 3.0.178 → 3.0.179-alpha.2026101501
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerViewManager.kt +10 -0
- package/android/src/main/java/com/reactnativepagerview/NativeScrollerViewManager.kt +4 -0
- package/docs/SPEC.md +75 -1
- package/ios/RNCCollapsiblePagerViewComponentView.mm +837 -138
- package/ios/RNCNativeScrollerComponentView.mm +93 -38
- package/ios/RNCPagerViewComponentView.h +7 -0
- package/ios/RNCPagerViewComponentView.mm +34 -0
- package/lib/module/CollapsiblePagerView.js +27 -1
- package/lib/module/CollapsiblePagerView.web.js +4 -0
- package/lib/module/CollapsiblePagerViewNativeComponent.ts +6 -0
- package/lib/module/NativeScroller.js +105 -82
- package/lib/module/NativeScroller.web.js +2 -0
- package/lib/module/NativeScrollerNativeComponent.ts +2 -0
- package/lib/typescript/src/CollapsiblePagerView.d.ts +10 -1
- package/lib/typescript/src/CollapsiblePagerView.web.d.ts +4 -0
- package/lib/typescript/src/CollapsiblePagerViewNativeComponent.d.ts +8 -0
- package/lib/typescript/src/NativeScroller.d.ts +4 -0
- package/lib/typescript/src/NativeScrollerNativeComponent.d.ts +2 -0
- package/package.json +2 -2
- package/src/CollapsiblePagerView.tsx +44 -0
- package/src/CollapsiblePagerView.web.tsx +8 -0
- package/src/CollapsiblePagerViewNativeComponent.ts +6 -0
- package/src/NativeScroller.tsx +27 -2
- package/src/NativeScroller.web.tsx +2 -0
- package/src/NativeScrollerNativeComponent.ts +2 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#import "RNCCollapsiblePagerViewComponentView.h"
|
|
2
|
+
#import "RNCPagerViewComponentView.h"
|
|
2
3
|
#import "RNCNativeScrollerComponentView.h"
|
|
3
4
|
#import "RNCCollapsiblePagerReleasedStatePolicy.h"
|
|
4
5
|
|
|
@@ -194,6 +195,23 @@ static BOOL RNCIsEnabledHorizontalScrollOwner(UIScrollView *scroll)
|
|
|
194
195
|
scroll.contentSize.height <= CGRectGetHeight(scroll.bounds) + 0.5;
|
|
195
196
|
}
|
|
196
197
|
|
|
198
|
+
static UIScrollView *RNCNestedOuterReactPagerScrollView(UIView *view)
|
|
199
|
+
{
|
|
200
|
+
Class outerPagerClass = NSClassFromString(@"RNCPagerViewComponentView");
|
|
201
|
+
if (outerPagerClass == Nil) return nil;
|
|
202
|
+
UIScrollView *pagingAncestor = nil;
|
|
203
|
+
for (UIView *ancestor = view.superview; ancestor != nil; ancestor = ancestor.superview) {
|
|
204
|
+
if ([ancestor isKindOfClass:UIScrollView.class] &&
|
|
205
|
+
((UIScrollView *)ancestor).pagingEnabled) {
|
|
206
|
+
pagingAncestor = (UIScrollView *)ancestor;
|
|
207
|
+
}
|
|
208
|
+
if ([ancestor isKindOfClass:outerPagerClass]) {
|
|
209
|
+
return pagingAncestor.panGestureRecognizer.enabled ? pagingAncestor : nil;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return nil;
|
|
213
|
+
}
|
|
214
|
+
|
|
197
215
|
static void RNCGuardNestedHorizontalPans(UIView *view, UIGestureRecognizer *guard)
|
|
198
216
|
{
|
|
199
217
|
if ([view isKindOfClass:UIScrollView.class]) {
|
|
@@ -219,6 +237,7 @@ static void RNCCollapsiblePagerLog(NSString *message)
|
|
|
219
237
|
logFunction(logClass, selector, @"CollapsiblePager", message);
|
|
220
238
|
}
|
|
221
239
|
|
|
240
|
+
|
|
222
241
|
static BOOL RNCGetColorComponents(UIColor *color, UITraitCollection *traits,
|
|
223
242
|
CGFloat *red, CGFloat *green, CGFloat *blue, CGFloat *alpha)
|
|
224
243
|
{
|
|
@@ -1042,7 +1061,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1042
1061
|
CGFloat maximum = MAX(0, inner.contentSize.width - CGRectGetWidth(inner.bounds));
|
|
1043
1062
|
BOOL canScroll = maximum > 0 && (intent.x < 0
|
|
1044
1063
|
? inner.contentOffset.x < maximum - 0.5 : inner.contentOffset.x > 0.5);
|
|
1045
|
-
if (canScroll)
|
|
1064
|
+
if (canScroll) {
|
|
1065
|
+
return NO;
|
|
1066
|
+
}
|
|
1046
1067
|
}
|
|
1047
1068
|
}
|
|
1048
1069
|
if (gestureRecognizer == self.panGestureRecognizer && self.excludedHeaderView != nil) {
|
|
@@ -1052,7 +1073,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1052
1073
|
return NO;
|
|
1053
1074
|
}
|
|
1054
1075
|
}
|
|
1055
|
-
|
|
1076
|
+
BOOL result = [super gestureRecognizerShouldBegin:gestureRecognizer];
|
|
1077
|
+
return result;
|
|
1056
1078
|
}
|
|
1057
1079
|
|
|
1058
1080
|
@end
|
|
@@ -1241,6 +1263,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1241
1263
|
|
|
1242
1264
|
@interface RNCCollapsiblePagerOuterPagerPanGestureRecognizer : UIPanGestureRecognizer
|
|
1243
1265
|
@property (nonatomic, strong) NSHashTable<UIGestureRecognizer *> *blockedPagerGestures;
|
|
1266
|
+
@property (nonatomic, weak) UITouch *trackedTouch;
|
|
1267
|
+
@property (nonatomic, assign) BOOL protectsNestedBody;
|
|
1244
1268
|
@end
|
|
1245
1269
|
|
|
1246
1270
|
@implementation RNCCollapsiblePagerOuterPagerPanGestureRecognizer
|
|
@@ -1265,6 +1289,14 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1265
1289
|
return NO;
|
|
1266
1290
|
}
|
|
1267
1291
|
|
|
1292
|
+
- (void)reset
|
|
1293
|
+
{
|
|
1294
|
+
[super reset];
|
|
1295
|
+
self.trackedTouch = nil;
|
|
1296
|
+
self.protectsNestedBody = NO;
|
|
1297
|
+
self.cancelsTouchesInView = YES;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1268
1300
|
@end
|
|
1269
1301
|
|
|
1270
1302
|
@interface RNCCollapsiblePagerVerticalPagerGuardGestureRecognizer : UIGestureRecognizer
|
|
@@ -1408,7 +1440,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1408
1440
|
UIScrollViewDelegate,
|
|
1409
1441
|
UIGestureRecognizerDelegate,
|
|
1410
1442
|
RCTMountingTransactionObserving,
|
|
1411
|
-
RNCNativeScrollerInsetCoordinator
|
|
1443
|
+
RNCNativeScrollerInsetCoordinator,
|
|
1444
|
+
RNCPagerViewNestedOverlayObserver
|
|
1412
1445
|
>
|
|
1413
1446
|
- (void)finishPagerScrollEmittingSelection:(BOOL)emitSelection;
|
|
1414
1447
|
- (void)completeTransitionOnNextRunLoopForGeneration:(NSUInteger)generation
|
|
@@ -1418,6 +1451,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1418
1451
|
- (void)liftSharedHeadersForPagerTransition:(NSString *)reason;
|
|
1419
1452
|
- (void)restoreSharedHeadersToContainer;
|
|
1420
1453
|
- (void)layoutSharedHeaders;
|
|
1454
|
+
- (void)restorePinnedStickyHeader;
|
|
1455
|
+
- (void)updatePinnedStickyHeader;
|
|
1421
1456
|
- (UIScrollView *)currentVerticalScrollViewForParentPager;
|
|
1422
1457
|
- (UIScrollView *)verticalScrollViewForPageAtIndex:(NSInteger)pageIndex;
|
|
1423
1458
|
- (UIScrollView *)findExplicitNativeScrollerInView:(UIView *)view;
|
|
@@ -1429,19 +1464,32 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1429
1464
|
- (void)detachSharedHeaderPressCancellationGesture;
|
|
1430
1465
|
- (void)updateRefreshControlForScrollView:(UIScrollView *)scrollView;
|
|
1431
1466
|
- (void)releaseRefreshControlForScrollView:(UIScrollView *)scrollView;
|
|
1467
|
+
- (void)cancelTopInsetReboundCheck;
|
|
1468
|
+
- (void)scheduleTopInsetReboundCheck;
|
|
1469
|
+
- (void)syncScrollEdgeAppearance;
|
|
1470
|
+
- (void)updateScrollEdgeContentMasks;
|
|
1471
|
+
- (void)restoreScrollEdgeContentMasks;
|
|
1432
1472
|
- (void)sharedHeaderPressCancellationGestureChanged:(UIPanGestureRecognizer *)recognizer;
|
|
1433
1473
|
- (void)outerPagerGestureChanged:(UIPanGestureRecognizer *)recognizer;
|
|
1434
1474
|
- (void)verticalPagerGuardGestureChanged:(UIPanGestureRecognizer *)recognizer;
|
|
1435
1475
|
- (void)connectVerticalPagerGuard;
|
|
1436
1476
|
- (void)observedListPanChanged:(UIPanGestureRecognizer *)recognizer;
|
|
1477
|
+
- (BOOL)resolveExternalTopOverlay;
|
|
1478
|
+
- (void)registerExternalOuterPager;
|
|
1479
|
+
- (CGFloat)effectiveTopOverlayHeight;
|
|
1480
|
+
- (BOOL)hasFixedTopOverlay;
|
|
1437
1481
|
@end
|
|
1438
1482
|
|
|
1439
1483
|
@implementation RNCCollapsiblePagerViewComponentView {
|
|
1440
|
-
#if DEBUG
|
|
1441
|
-
NSUInteger _offsetDiagSequence;
|
|
1442
|
-
#endif
|
|
1443
1484
|
UIView *_containerView;
|
|
1444
1485
|
RNCCollapsiblePagerSharedHeaderView *_sharedHeaderHostView;
|
|
1486
|
+
UIView *_pinnedStickyHostView;
|
|
1487
|
+
BOOL _stickyHeaderIsPinned;
|
|
1488
|
+
UIView *_sharedHeaderMaskView;
|
|
1489
|
+
UIView *_originalSharedHeaderMaskView;
|
|
1490
|
+
NSMapTable<UIView *, id> *_originalPageMasks;
|
|
1491
|
+
NSMapTable<UIView *, UIView *> *_ownedPageMasks;
|
|
1492
|
+
CGFloat _pageMaskBoundary;
|
|
1445
1493
|
UIPageViewController *_pageViewController;
|
|
1446
1494
|
UIScrollView *_pageViewControllerScrollView;
|
|
1447
1495
|
UIScrollView *_pagerScrollView;
|
|
@@ -1449,6 +1497,16 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1449
1497
|
NSMutableArray<UIViewController *> *_pageControllers;
|
|
1450
1498
|
UIView *_headerView;
|
|
1451
1499
|
UIView *_stickyHeaderView;
|
|
1500
|
+
UIView *_topOverlayView;
|
|
1501
|
+
UIView *_topOverlayHostView;
|
|
1502
|
+
UIScrollEdgeElementContainerInteraction *_topEdgeInteraction;
|
|
1503
|
+
UIScrollEdgeElementContainerInteraction *_stickyEdgeInteraction;
|
|
1504
|
+
UILabel *_topEdgeMarker;
|
|
1505
|
+
UILabel *_stickyEdgeMarker;
|
|
1506
|
+
__weak UIView *_topEdgeInteractionHost;
|
|
1507
|
+
__weak UIView *_stickyEdgeInteractionHost;
|
|
1508
|
+
__weak UIScrollView *_topEdgeScrollView;
|
|
1509
|
+
UIScrollEdgeEffectStyle *_originalTopEdgeStyle;
|
|
1452
1510
|
RNCCollapsiblePagerNativeTabBarView *_nativeTabBarView;
|
|
1453
1511
|
RNCCollapsiblePagerNativeSubHeaderView *_nativeSubHeaderView;
|
|
1454
1512
|
NSInteger _currentIndex;
|
|
@@ -1456,6 +1514,22 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1456
1514
|
NSInteger _pendingInitialPage;
|
|
1457
1515
|
NSInteger _headerHeight;
|
|
1458
1516
|
NSInteger _stickyHeaderHeight;
|
|
1517
|
+
CGFloat _topOverlayHeight;
|
|
1518
|
+
NSInteger _externalTopOverlayTag;
|
|
1519
|
+
__weak UIView *_externalTopOverlayView;
|
|
1520
|
+
CGFloat _externalTopOverlayHeight;
|
|
1521
|
+
__weak RNCPagerViewComponentView *_externalOuterPager;
|
|
1522
|
+
BOOL _externalOuterPageActive;
|
|
1523
|
+
BOOL _externalOuterTransitioning;
|
|
1524
|
+
BOOL _topScrollEdgeHard;
|
|
1525
|
+
NSString *_topOverlayColorScheme;
|
|
1526
|
+
BOOL _pendingTopOverlayAppearanceCommit;
|
|
1527
|
+
BOOL _applyingTopOverlayInsetChange;
|
|
1528
|
+
BOOL _correctingTopInsetRebound;
|
|
1529
|
+
__weak UIScrollView *_pendingTopInsetReboundScrollView;
|
|
1530
|
+
NSString *_pendingTopInsetReboundPageKey;
|
|
1531
|
+
NSUInteger _pendingTopInsetReboundGeneration;
|
|
1532
|
+
NSTimer *_topInsetReboundTimer;
|
|
1459
1533
|
CGFloat _nativeTabBarHeight;
|
|
1460
1534
|
CGFloat _nativeSubHeaderHeight;
|
|
1461
1535
|
CGFloat _headerOffset;
|
|
@@ -1475,6 +1549,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1475
1549
|
NSInteger _pendingGoToIndex;
|
|
1476
1550
|
BOOL _pendingGoToAnimated;
|
|
1477
1551
|
BOOL _needsSlotRebuild;
|
|
1552
|
+
BOOL _deferTopEdgeSlotRebuild;
|
|
1478
1553
|
BOOL _hasAppliedInitialPage;
|
|
1479
1554
|
BOOL _needsPropsReapply;
|
|
1480
1555
|
NSString *_layoutDirection;
|
|
@@ -1512,9 +1587,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1512
1587
|
NSUInteger _observedPageGeneration;
|
|
1513
1588
|
NSDictionary<NSString *, NSNumber *> *_transitionPageOffsets;
|
|
1514
1589
|
NSMutableIndexSet *_transitionCompensatedPages;
|
|
1515
|
-
#if DEBUG
|
|
1516
|
-
NSInteger _transitionDiagBucket;
|
|
1517
|
-
#endif
|
|
1518
1590
|
NSInteger _transitionSourceIndex;
|
|
1519
1591
|
BOOL _syntheticPageRestore;
|
|
1520
1592
|
BOOL _headerSettledAtTop;
|
|
@@ -1580,6 +1652,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1580
1652
|
_originalAlwaysBounceVertical = [NSMapTable weakToStrongObjectsMapTable];
|
|
1581
1653
|
_originalDirectionalLockEnabled = [NSMapTable weakToStrongObjectsMapTable];
|
|
1582
1654
|
_originalInsetAdjustmentBehavior = [NSMapTable weakToStrongObjectsMapTable];
|
|
1655
|
+
_originalPageMasks = [NSMapTable strongToStrongObjectsMapTable];
|
|
1656
|
+
_ownedPageMasks = [NSMapTable strongToStrongObjectsMapTable];
|
|
1583
1657
|
_pageKeys = @[];
|
|
1584
1658
|
_retainedPages = @"[]";
|
|
1585
1659
|
_currentIndex = 0;
|
|
@@ -1607,6 +1681,12 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1607
1681
|
_sharedHeaderHostView = [RNCCollapsiblePagerSharedHeaderView new];
|
|
1608
1682
|
_sharedHeaderHostView.clipsToBounds = NO;
|
|
1609
1683
|
[_containerView addSubview:_sharedHeaderHostView];
|
|
1684
|
+
_pinnedStickyHostView = [UIView new];
|
|
1685
|
+
_pinnedStickyHostView.hidden = YES;
|
|
1686
|
+
[_containerView addSubview:_pinnedStickyHostView];
|
|
1687
|
+
_topOverlayHostView = [UIView new];
|
|
1688
|
+
_topOverlayHostView.hidden = YES;
|
|
1689
|
+
[_containerView addSubview:_topOverlayHostView];
|
|
1610
1690
|
|
|
1611
1691
|
_verticalPagerGesture =
|
|
1612
1692
|
[[RNCCollapsiblePagerVerticalPagerGuardGestureRecognizer alloc]
|
|
@@ -1664,14 +1744,39 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1664
1744
|
viewProps.pointerEvents == PointerEventsMode::BoxOnly) {
|
|
1665
1745
|
return [super hitTest:point withEvent:event];
|
|
1666
1746
|
}
|
|
1747
|
+
if (_topOverlayView != nil) {
|
|
1748
|
+
CGPoint overlayPoint = [_topOverlayView convertPoint:point fromView:self];
|
|
1749
|
+
UIView *overlayHitView = [_topOverlayView hitTest:overlayPoint withEvent:event];
|
|
1750
|
+
if (overlayHitView != nil) return overlayHitView;
|
|
1751
|
+
}
|
|
1752
|
+
if (_stickyHeaderIsPinned) {
|
|
1753
|
+
CGPoint stickyPoint = [_pinnedStickyHostView convertPoint:point fromView:self];
|
|
1754
|
+
UIView *stickyHitView = [_pinnedStickyHostView hitTest:stickyPoint withEvent:event];
|
|
1755
|
+
if (stickyHitView != nil) return stickyHitView;
|
|
1756
|
+
}
|
|
1757
|
+
CGPoint headerPoint = [_sharedHeaderHostView convertPoint:point fromView:self];
|
|
1758
|
+
BOOL sharedHeaderPointIsVisible =
|
|
1759
|
+
_sharedHeaderHostView.maskView != _sharedHeaderMaskView ||
|
|
1760
|
+
_sharedHeaderMaskView == nil ||
|
|
1761
|
+
CGRectContainsPoint(_sharedHeaderMaskView.frame, headerPoint);
|
|
1667
1762
|
if (_nativeSmoothHeaderScrollEnabled && !_isBeingRecycled &&
|
|
1668
|
-
_sharedHeaderHostView.superview != _containerView
|
|
1669
|
-
|
|
1763
|
+
_sharedHeaderHostView.superview != _containerView &&
|
|
1764
|
+
sharedHeaderPointIsVisible) {
|
|
1670
1765
|
UIView *headerHitView =
|
|
1671
1766
|
[_sharedHeaderHostView hitTest:headerPoint withEvent:event];
|
|
1672
1767
|
if (headerHitView != nil) return headerHitView;
|
|
1673
1768
|
}
|
|
1674
|
-
|
|
1769
|
+
UIView *hitView = [super hitTest:point withEvent:event];
|
|
1770
|
+
if (hitView != nil && !sharedHeaderPointIsVisible &&
|
|
1771
|
+
(hitView == _sharedHeaderHostView ||
|
|
1772
|
+
[hitView isDescendantOfView:_sharedHeaderHostView])) return nil;
|
|
1773
|
+
if (hitView != nil && _ownedPageMasks.count > 0 &&
|
|
1774
|
+
[_containerView convertPoint:point fromView:self].y < _pageMaskBoundary) {
|
|
1775
|
+
for (UIView *page in _ownedPageMasks.keyEnumerator.allObjects) {
|
|
1776
|
+
if ([hitView isDescendantOfView:page]) return nil;
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
return hitView;
|
|
1675
1780
|
}
|
|
1676
1781
|
|
|
1677
1782
|
- (void)willMoveToSuperview:(UIView *)newSuperview
|
|
@@ -1686,11 +1791,18 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1686
1791
|
- (void)didMoveToWindow
|
|
1687
1792
|
{
|
|
1688
1793
|
[super didMoveToWindow];
|
|
1794
|
+
[self registerExternalOuterPager];
|
|
1795
|
+
BOOL externalHeightChanged = [self resolveExternalTopOverlay];
|
|
1796
|
+
if (self.window == nil) [self restoreScrollEdgeContentMasks];
|
|
1797
|
+
[self syncScrollEdgeAppearance];
|
|
1798
|
+
[self emitTopScrollEdgeCapability];
|
|
1799
|
+
[self updateTopScrollEdgeForScrollView:_observedScrollView];
|
|
1689
1800
|
[self updateSharedHeaderPressCancellationGesture];
|
|
1690
1801
|
[self updateHeaderSettledStateForceBusy:NO];
|
|
1691
1802
|
for (UIScrollView *scrollView in _originalInsets.keyEnumerator) {
|
|
1692
|
-
[self updateNativeScrollerViewport:scrollView stickyHeight:self.window == nil ? 0 : _stickyHeaderHeight];
|
|
1803
|
+
[self updateNativeScrollerViewport:scrollView stickyHeight:self.window == nil ? 0 : _stickyHeaderHeight + self.effectiveTopOverlayHeight];
|
|
1693
1804
|
}
|
|
1805
|
+
if (externalHeightChanged) [self reapplyInsetsToObservedScrollView];
|
|
1694
1806
|
// OneKey patch: removing a decelerating page from its window can suppress
|
|
1695
1807
|
// UIKit's final scroll callback. Restore the last acknowledged page on reattach.
|
|
1696
1808
|
if (self.window != nil && _isPagerDragging &&
|
|
@@ -1712,8 +1824,319 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1712
1824
|
}
|
|
1713
1825
|
}
|
|
1714
1826
|
|
|
1827
|
+
- (CGFloat)effectiveTopOverlayHeight
|
|
1828
|
+
{
|
|
1829
|
+
return _topOverlayView != nil ? _topOverlayHeight : _externalTopOverlayHeight;
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
- (BOOL)hasFixedTopOverlay
|
|
1833
|
+
{
|
|
1834
|
+
return _topOverlayView != nil ||
|
|
1835
|
+
(_externalTopOverlayTag > 0 && _externalTopOverlayHeight > 0);
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
- (void)registerExternalOuterPager
|
|
1839
|
+
{
|
|
1840
|
+
RNCPagerViewComponentView *outer = nil;
|
|
1841
|
+
if (_externalTopOverlayTag > 0 && self.window != nil && !_isBeingRecycled) {
|
|
1842
|
+
for (UIView *ancestor = self.superview; ancestor != nil; ancestor = ancestor.superview) {
|
|
1843
|
+
if ([ancestor isKindOfClass:RNCPagerViewComponentView.class]) {
|
|
1844
|
+
outer = (RNCPagerViewComponentView *)ancestor;
|
|
1845
|
+
break;
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
if (_externalOuterPager == outer) return;
|
|
1850
|
+
[_externalOuterPager removeNestedOverlayObserver:self];
|
|
1851
|
+
_externalOuterPager = outer;
|
|
1852
|
+
_externalOuterPageActive = NO;
|
|
1853
|
+
_externalOuterTransitioning = NO;
|
|
1854
|
+
[outer addNestedOverlayObserver:self];
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
- (BOOL)resolveExternalTopOverlay
|
|
1858
|
+
{
|
|
1859
|
+
UIView *nextView = nil;
|
|
1860
|
+
CGFloat nextHeight = 0;
|
|
1861
|
+
if (_externalTopOverlayTag > 0 && _topOverlayView == nil &&
|
|
1862
|
+
self.window != nil && !_isBeingRecycled) {
|
|
1863
|
+
for (UIView *ancestor = self.superview; ancestor != nil; ancestor = ancestor.superview) {
|
|
1864
|
+
UIView *candidate = [ancestor viewWithTag:_externalTopOverlayTag];
|
|
1865
|
+
if (candidate == nil || candidate == self ||
|
|
1866
|
+
[candidate isDescendantOfView:self] || candidate.window != self.window) continue;
|
|
1867
|
+
nextView = candidate;
|
|
1868
|
+
break;
|
|
1869
|
+
}
|
|
1870
|
+
if (nextView != nil && CGRectGetHeight(nextView.bounds) > 0) {
|
|
1871
|
+
nextHeight = MAX(0, CGRectGetMaxY([nextView convertRect:nextView.bounds
|
|
1872
|
+
toView:_containerView]));
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
if (nextView == nil && _externalOuterTransitioning &&
|
|
1876
|
+
_externalTopOverlayTag > 0 && self.window != nil) {
|
|
1877
|
+
nextHeight = _externalTopOverlayHeight;
|
|
1878
|
+
}
|
|
1879
|
+
BOOL changed = _externalTopOverlayView != nextView ||
|
|
1880
|
+
fabs(_externalTopOverlayHeight - nextHeight) > 0.5;
|
|
1881
|
+
_externalTopOverlayView = nextView;
|
|
1882
|
+
_externalTopOverlayHeight = nextHeight;
|
|
1883
|
+
_topOverlayHostView.hidden = !self.hasFixedTopOverlay;
|
|
1884
|
+
_topOverlayHostView.userInteractionEnabled = _topOverlayView != nil;
|
|
1885
|
+
return changed;
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
- (void)outerPagerDidUpdateOverlayOwner:(BOOL)active transitioning:(BOOL)transitioning
|
|
1889
|
+
{
|
|
1890
|
+
if (_isBeingRecycled || _externalOuterPager == nil || _externalTopOverlayTag <= 0) return;
|
|
1891
|
+
if (_externalOuterPageActive == active && _externalOuterTransitioning == transitioning) return;
|
|
1892
|
+
BOOL wasTransitioning = _externalOuterTransitioning;
|
|
1893
|
+
_externalOuterPageActive = active;
|
|
1894
|
+
_externalOuterTransitioning = transitioning;
|
|
1895
|
+
if (transitioning && !wasTransitioning) {
|
|
1896
|
+
[self updateTopScrollEdgeForScrollView:nil];
|
|
1897
|
+
[self liftSharedHeadersForPagerTransition:@"outer"];
|
|
1898
|
+
} else if (!transitioning && wasTransitioning &&
|
|
1899
|
+
!_transitioning && !_isPagerDragging && active) {
|
|
1900
|
+
[self attachSharedHeadersToScrollView:_observedScrollView];
|
|
1901
|
+
}
|
|
1902
|
+
[self updateTopScrollEdgeForScrollView:transitioning ? nil : _observedScrollView];
|
|
1903
|
+
[self updateScrollEdgeContentMasks];
|
|
1904
|
+
[self setNeedsLayout];
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
|
|
1908
|
+
{
|
|
1909
|
+
[super traitCollectionDidChange:previousTraitCollection];
|
|
1910
|
+
if (previousTraitCollection.userInterfaceStyle != self.traitCollection.userInterfaceStyle) {
|
|
1911
|
+
[self syncScrollEdgeAppearance];
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
- (void)syncScrollEdgeAppearance
|
|
1916
|
+
{
|
|
1917
|
+
BOOL active = _topScrollEdgeHard &&
|
|
1918
|
+
(self.hasFixedTopOverlay || _stickyHeaderView != nil) && self.window != nil &&
|
|
1919
|
+
!_isBeingRecycled;
|
|
1920
|
+
UIUserInterfaceStyle style = UIUserInterfaceStyleUnspecified;
|
|
1921
|
+
if (active) {
|
|
1922
|
+
if ([_topOverlayColorScheme isEqualToString:@"dark"]) {
|
|
1923
|
+
style = UIUserInterfaceStyleDark;
|
|
1924
|
+
} else if ([_topOverlayColorScheme isEqualToString:@"light"]) {
|
|
1925
|
+
style = UIUserInterfaceStyleLight;
|
|
1926
|
+
} else {
|
|
1927
|
+
style = self.traitCollection.userInterfaceStyle;
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
if (_topOverlayHostView.overrideUserInterfaceStyle == style) return;
|
|
1931
|
+
_topOverlayHostView.overrideUserInterfaceStyle = style;
|
|
1932
|
+
_pinnedStickyHostView.overrideUserInterfaceStyle = style;
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
- (void)restoreScrollEdgeContentMasks
|
|
1936
|
+
{
|
|
1937
|
+
if (_sharedHeaderHostView.maskView == _sharedHeaderMaskView) {
|
|
1938
|
+
_sharedHeaderHostView.maskView = _originalSharedHeaderMaskView;
|
|
1939
|
+
}
|
|
1940
|
+
_sharedHeaderMaskView = nil;
|
|
1941
|
+
_originalSharedHeaderMaskView = nil;
|
|
1942
|
+
for (UIView *page in _ownedPageMasks.keyEnumerator.allObjects) {
|
|
1943
|
+
if (page.maskView == [_ownedPageMasks objectForKey:page]) {
|
|
1944
|
+
id original = [_originalPageMasks objectForKey:page];
|
|
1945
|
+
page.maskView = original == NSNull.null ? nil : original;
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
[_ownedPageMasks removeAllObjects];
|
|
1949
|
+
[_originalPageMasks removeAllObjects];
|
|
1950
|
+
_pageMaskBoundary = 0;
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
- (void)updateScrollEdgeContentMasks
|
|
1954
|
+
{
|
|
1955
|
+
BOOL enabled = _topScrollEdgeHard &&
|
|
1956
|
+
(self.hasFixedTopOverlay || _stickyHeaderView != nil) &&
|
|
1957
|
+
self.window != nil && !_isBeingRecycled;
|
|
1958
|
+
if (!enabled) {
|
|
1959
|
+
[self restoreScrollEdgeContentMasks];
|
|
1960
|
+
return;
|
|
1961
|
+
}
|
|
1962
|
+
if (self.hasFixedTopOverlay && _sharedHeaderHostView.window == self.window &&
|
|
1963
|
+
CGRectGetHeight(_sharedHeaderHostView.bounds) > 0 &&
|
|
1964
|
+
CGRectGetHeight(_topOverlayHostView.bounds) > 0) {
|
|
1965
|
+
if (_sharedHeaderMaskView == nil) {
|
|
1966
|
+
_originalSharedHeaderMaskView = _sharedHeaderHostView.maskView;
|
|
1967
|
+
_sharedHeaderMaskView = [UIView new];
|
|
1968
|
+
_sharedHeaderMaskView.backgroundColor = UIColor.blackColor;
|
|
1969
|
+
_sharedHeaderHostView.maskView = _sharedHeaderMaskView;
|
|
1970
|
+
}
|
|
1971
|
+
CGFloat topBottom = CGRectGetMaxY([_topOverlayHostView
|
|
1972
|
+
convertRect:_topOverlayHostView.bounds toView:_containerView]);
|
|
1973
|
+
CGFloat localTop = [_containerView convertPoint:CGPointMake(0, topBottom)
|
|
1974
|
+
toView:_sharedHeaderHostView].y;
|
|
1975
|
+
_sharedHeaderMaskView.frame = CGRectMake(0, localTop,
|
|
1976
|
+
CGRectGetWidth(_sharedHeaderHostView.bounds),
|
|
1977
|
+
MAX(0, CGRectGetHeight(_sharedHeaderHostView.bounds) - localTop));
|
|
1978
|
+
} else if (_sharedHeaderMaskView != nil) {
|
|
1979
|
+
if (_sharedHeaderHostView.maskView == _sharedHeaderMaskView) {
|
|
1980
|
+
_sharedHeaderHostView.maskView = _originalSharedHeaderMaskView;
|
|
1981
|
+
}
|
|
1982
|
+
_sharedHeaderMaskView = nil;
|
|
1983
|
+
_originalSharedHeaderMaskView = nil;
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
BOOL transitioning = _transitioning || _isPagerDragging || _externalOuterTransitioning ||
|
|
1987
|
+
_sharedHeadersLiftedForPagerTransition;
|
|
1988
|
+
CGFloat boundary = CGRectGetMaxY([_topOverlayHostView
|
|
1989
|
+
convertRect:_topOverlayHostView.bounds toView:_containerView]);
|
|
1990
|
+
UIView *sticky = _stickyHeaderIsPinned ? _pinnedStickyHostView : _stickyHeaderView;
|
|
1991
|
+
if (sticky != nil && !sticky.hidden && sticky.window == self.window &&
|
|
1992
|
+
CGRectGetHeight(sticky.bounds) > 0) {
|
|
1993
|
+
boundary = MAX(boundary, CGRectGetMaxY([sticky
|
|
1994
|
+
convertRect:sticky.bounds toView:_containerView]));
|
|
1995
|
+
}
|
|
1996
|
+
_pageMaskBoundary = boundary;
|
|
1997
|
+
NSMutableSet<UIView *> *visiblePages = [NSMutableSet new];
|
|
1998
|
+
if (transitioning) {
|
|
1999
|
+
for (UIViewController *controller in _pageControllers) {
|
|
2000
|
+
UIView *page = controller.view;
|
|
2001
|
+
if (page.window != self.window ||
|
|
2002
|
+
!CGRectIntersectsRect([page convertRect:page.bounds toView:_containerView],
|
|
2003
|
+
_containerView.bounds)) continue;
|
|
2004
|
+
[visiblePages addObject:page];
|
|
2005
|
+
UIView *mask = [_ownedPageMasks objectForKey:page];
|
|
2006
|
+
if (mask == nil) {
|
|
2007
|
+
[_originalPageMasks setObject:page.maskView ?: NSNull.null forKey:page];
|
|
2008
|
+
mask = [UIView new];
|
|
2009
|
+
mask.backgroundColor = UIColor.blackColor;
|
|
2010
|
+
[_ownedPageMasks setObject:mask forKey:page];
|
|
2011
|
+
page.maskView = mask;
|
|
2012
|
+
}
|
|
2013
|
+
CGFloat localTop = [_containerView convertPoint:CGPointMake(0, boundary)
|
|
2014
|
+
toView:page].y;
|
|
2015
|
+
mask.frame = CGRectMake(0, localTop, CGRectGetWidth(page.bounds),
|
|
2016
|
+
MAX(0, CGRectGetHeight(page.bounds) - localTop));
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
for (UIView *page in _ownedPageMasks.keyEnumerator.allObjects) {
|
|
2020
|
+
if ([visiblePages containsObject:page]) continue;
|
|
2021
|
+
UIView *mask = [_ownedPageMasks objectForKey:page];
|
|
2022
|
+
if (page.maskView == mask) {
|
|
2023
|
+
id original = [_originalPageMasks objectForKey:page];
|
|
2024
|
+
page.maskView = original == NSNull.null ? nil : original;
|
|
2025
|
+
}
|
|
2026
|
+
[_ownedPageMasks removeObjectForKey:page];
|
|
2027
|
+
[_originalPageMasks removeObjectForKey:page];
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
- (void)updateEventEmitter:(const EventEmitter::Shared &)eventEmitter
|
|
2032
|
+
{
|
|
2033
|
+
[super updateEventEmitter:eventEmitter];
|
|
2034
|
+
[self emitTopScrollEdgeCapability];
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
- (void)emitTopScrollEdgeCapability
|
|
2038
|
+
{
|
|
2039
|
+
if (self.window == nil || _isBeingRecycled) return;
|
|
2040
|
+
const auto emitter = [self eventEmitter];
|
|
2041
|
+
if (!emitter) return;
|
|
2042
|
+
BOOL available = NO;
|
|
2043
|
+
if (@available(iOS 26.0, *)) available = YES;
|
|
2044
|
+
emitter->onScrollEdgeCapability({.available = (bool)available});
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
- (void)updateTopScrollEdgeForScrollView:(UIScrollView *)scrollView
|
|
2048
|
+
{
|
|
2049
|
+
[self syncScrollEdgeAppearance];
|
|
2050
|
+
if (@available(iOS 26.0, *)) {
|
|
2051
|
+
if (_pendingTopOverlayAppearanceCommit) scrollView = nil;
|
|
2052
|
+
BOOL externalOwnerReady = _externalTopOverlayTag <= 0 ||
|
|
2053
|
+
(_externalOuterPageActive && !_externalOuterTransitioning);
|
|
2054
|
+
BOOL hasOverlay = _topScrollEdgeHard && self.hasFixedTopOverlay &&
|
|
2055
|
+
externalOwnerReady && self.window != nil;
|
|
2056
|
+
BOOL hasSticky = _topScrollEdgeHard && _stickyHeaderView != nil &&
|
|
2057
|
+
externalOwnerReady && self.window != nil;
|
|
2058
|
+
BOOL enabled = (hasOverlay || hasSticky) && scrollView != nil;
|
|
2059
|
+
BOOL topHostChanged = (_topEdgeInteraction != nil) != hasOverlay ||
|
|
2060
|
+
(hasOverlay && _topEdgeInteractionHost != _topOverlayHostView);
|
|
2061
|
+
if (_topEdgeScrollView != nil && (!enabled || _topEdgeScrollView != scrollView ||
|
|
2062
|
+
topHostChanged)) {
|
|
2063
|
+
[_topEdgeInteractionHost removeInteraction:_topEdgeInteraction];
|
|
2064
|
+
_topEdgeInteraction.scrollView = nil;
|
|
2065
|
+
[_stickyEdgeInteractionHost removeInteraction:_stickyEdgeInteraction];
|
|
2066
|
+
_stickyEdgeInteraction.scrollView = nil;
|
|
2067
|
+
[_topEdgeMarker removeFromSuperview];
|
|
2068
|
+
[_stickyEdgeMarker removeFromSuperview];
|
|
2069
|
+
_topEdgeScrollView.topEdgeEffect.style = _originalTopEdgeStyle;
|
|
2070
|
+
_topEdgeInteraction = nil;
|
|
2071
|
+
_stickyEdgeInteraction = nil;
|
|
2072
|
+
_topEdgeMarker = nil;
|
|
2073
|
+
_stickyEdgeMarker = nil;
|
|
2074
|
+
_topEdgeInteractionHost = nil;
|
|
2075
|
+
_stickyEdgeInteractionHost = nil;
|
|
2076
|
+
_topEdgeScrollView = nil;
|
|
2077
|
+
_originalTopEdgeStyle = nil;
|
|
2078
|
+
}
|
|
2079
|
+
if (enabled && _topEdgeScrollView == nil) {
|
|
2080
|
+
_originalTopEdgeStyle = scrollView.topEdgeEffect.style;
|
|
2081
|
+
_topEdgeScrollView = scrollView;
|
|
2082
|
+
scrollView.topEdgeEffect.style = UIScrollEdgeEffectStyle.hardStyle;
|
|
2083
|
+
}
|
|
2084
|
+
if (hasOverlay && enabled && _topEdgeInteraction == nil) {
|
|
2085
|
+
// UIKit needs a native element to shape the edge effect around React-drawn content.
|
|
2086
|
+
_topEdgeMarker = [UILabel new];
|
|
2087
|
+
_topEdgeMarker.frame = _topOverlayHostView.bounds;
|
|
2088
|
+
_topEdgeMarker.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
2089
|
+
_topEdgeMarker.text = @" ";
|
|
2090
|
+
_topEdgeMarker.textColor = UIColor.clearColor;
|
|
2091
|
+
_topEdgeMarker.backgroundColor = UIColor.clearColor;
|
|
2092
|
+
_topEdgeMarker.userInteractionEnabled = NO;
|
|
2093
|
+
_topEdgeMarker.isAccessibilityElement = NO;
|
|
2094
|
+
_topEdgeMarker.accessibilityElementsHidden = YES;
|
|
2095
|
+
[_topOverlayHostView addSubview:_topEdgeMarker];
|
|
2096
|
+
_topEdgeInteraction = [UIScrollEdgeElementContainerInteraction new];
|
|
2097
|
+
_topEdgeInteraction.edge = UIRectEdgeTop;
|
|
2098
|
+
_topEdgeInteraction.scrollView = scrollView;
|
|
2099
|
+
[_topOverlayHostView addInteraction:_topEdgeInteraction];
|
|
2100
|
+
_topEdgeInteractionHost = _topOverlayHostView;
|
|
2101
|
+
}
|
|
2102
|
+
BOOL legacyStickyPinned = !_nativeSmoothHeaderScrollEnabled &&
|
|
2103
|
+
_stickyHeaderView != nil && _headerOffset >= _headerHeight - 0.5;
|
|
2104
|
+
BOOL stickyEnabled = enabled && (_stickyHeaderIsPinned || legacyStickyPinned);
|
|
2105
|
+
UIView *stickyInteractionHost = _stickyHeaderIsPinned
|
|
2106
|
+
? _pinnedStickyHostView : _stickyHeaderView;
|
|
2107
|
+
if (_stickyEdgeInteractionHost != nil &&
|
|
2108
|
+
(!stickyEnabled || _stickyEdgeInteractionHost != stickyInteractionHost)) {
|
|
2109
|
+
[_stickyEdgeInteractionHost removeInteraction:_stickyEdgeInteraction];
|
|
2110
|
+
_stickyEdgeInteraction.scrollView = nil;
|
|
2111
|
+
[_stickyEdgeMarker removeFromSuperview];
|
|
2112
|
+
_stickyEdgeMarker = nil;
|
|
2113
|
+
_stickyEdgeInteraction = nil;
|
|
2114
|
+
_stickyEdgeInteractionHost = nil;
|
|
2115
|
+
}
|
|
2116
|
+
if (stickyEnabled && _stickyEdgeInteraction == nil) {
|
|
2117
|
+
_stickyEdgeMarker = [UILabel new];
|
|
2118
|
+
_stickyEdgeMarker.frame = stickyInteractionHost.bounds;
|
|
2119
|
+
_stickyEdgeMarker.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
2120
|
+
_stickyEdgeMarker.text = @" ";
|
|
2121
|
+
_stickyEdgeMarker.textColor = UIColor.clearColor;
|
|
2122
|
+
_stickyEdgeMarker.backgroundColor = UIColor.clearColor;
|
|
2123
|
+
_stickyEdgeMarker.userInteractionEnabled = NO;
|
|
2124
|
+
_stickyEdgeMarker.isAccessibilityElement = NO;
|
|
2125
|
+
_stickyEdgeMarker.accessibilityElementsHidden = YES;
|
|
2126
|
+
[stickyInteractionHost addSubview:_stickyEdgeMarker];
|
|
2127
|
+
_stickyEdgeInteraction = [UIScrollEdgeElementContainerInteraction new];
|
|
2128
|
+
_stickyEdgeInteraction.edge = UIRectEdgeTop;
|
|
2129
|
+
_stickyEdgeInteraction.scrollView = scrollView;
|
|
2130
|
+
[stickyInteractionHost addInteraction:_stickyEdgeInteraction];
|
|
2131
|
+
_stickyEdgeInteractionHost = stickyInteractionHost;
|
|
2132
|
+
}
|
|
2133
|
+
[self updateScrollEdgeContentMasks];
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
|
|
1715
2137
|
- (void)dealloc
|
|
1716
2138
|
{
|
|
2139
|
+
[self updateTopScrollEdgeForScrollView:nil];
|
|
1717
2140
|
[self cancelHeaderSettledCheck];
|
|
1718
2141
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"OneKeyNativeListScrollCommandAccepted" object:nil];
|
|
1719
2142
|
for (UIScrollView *scrollView in _originalInsets.keyEnumerator) {
|
|
@@ -1766,6 +2189,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1766
2189
|
_hasPendingDirectNativePagerChange = NO;
|
|
1767
2190
|
if (_directNativePagerEnabled == enabled) return;
|
|
1768
2191
|
|
|
2192
|
+
[self restoreScrollEdgeContentMasks];
|
|
1769
2193
|
[self detachScrollObserver];
|
|
1770
2194
|
[self restoreSharedHeadersToContainer];
|
|
1771
2195
|
if (_blockerGesture != nil) {
|
|
@@ -1841,8 +2265,33 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1841
2265
|
|
|
1842
2266
|
#pragma mark - Smooth shared header ownership
|
|
1843
2267
|
|
|
2268
|
+
- (void)restorePinnedStickyHeader
|
|
2269
|
+
{
|
|
2270
|
+
if (!_stickyHeaderIsPinned) return;
|
|
2271
|
+
_stickyHeaderIsPinned = NO;
|
|
2272
|
+
_stickyHeaderView.transform = CGAffineTransformIdentity;
|
|
2273
|
+
_nativeTabBarView.transform = CGAffineTransformIdentity;
|
|
2274
|
+
_nativeSubHeaderView.transform = CGAffineTransformIdentity;
|
|
2275
|
+
[_stickyHeaderView removeFromSuperview];
|
|
2276
|
+
[_nativeTabBarView removeFromSuperview];
|
|
2277
|
+
[_nativeSubHeaderView removeFromSuperview];
|
|
2278
|
+
if (_stickyHeaderView != nil) [_sharedHeaderHostView addSubview:_stickyHeaderView];
|
|
2279
|
+
[_sharedHeaderHostView addSubview:_nativeTabBarView];
|
|
2280
|
+
[_sharedHeaderHostView addSubview:_nativeSubHeaderView];
|
|
2281
|
+
_pinnedStickyHostView.hidden = YES;
|
|
2282
|
+
_pinnedStickyHostView.layer.zPosition = 0;
|
|
2283
|
+
[_containerView addSubview:_pinnedStickyHostView];
|
|
2284
|
+
if ([_pagerScrollView isKindOfClass:RNCCollapsiblePagerDirectScrollView.class]) {
|
|
2285
|
+
((RNCCollapsiblePagerDirectScrollView *)_pagerScrollView).excludedHeaderView = _sharedHeaderHostView;
|
|
2286
|
+
}
|
|
2287
|
+
[_nativeTabBarView updateAncestorGesturePrecedence];
|
|
2288
|
+
[_nativeSubHeaderView updateAncestorGesturePrecedence];
|
|
2289
|
+
}
|
|
2290
|
+
|
|
1844
2291
|
- (void)restoreSharedHeadersToContainer
|
|
1845
2292
|
{
|
|
2293
|
+
[self restorePinnedStickyHeader];
|
|
2294
|
+
[self updateTopScrollEdgeForScrollView:_observedScrollView];
|
|
1846
2295
|
_sharedHeaderScrollView = nil;
|
|
1847
2296
|
_sharedHeadersLiftedForPagerTransition = NO;
|
|
1848
2297
|
_sharedHeaderHostView.layer.zPosition = 0;
|
|
@@ -1850,11 +2299,15 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1850
2299
|
[_containerView addSubview:_sharedHeaderHostView];
|
|
1851
2300
|
}
|
|
1852
2301
|
[self layoutSharedHeaders];
|
|
2302
|
+
if (self.hasFixedTopOverlay) {
|
|
2303
|
+
[_containerView bringSubviewToFront:_topOverlayHostView];
|
|
2304
|
+
}
|
|
1853
2305
|
}
|
|
1854
2306
|
|
|
1855
2307
|
- (void)attachSharedHeadersToScrollView:(UIScrollView *)scrollView
|
|
1856
2308
|
{
|
|
1857
2309
|
if (!_nativeSmoothHeaderScrollEnabled || _transitioning || _isPagerDragging ||
|
|
2310
|
+
_externalOuterTransitioning ||
|
|
1858
2311
|
scrollView == nil || _isBeingRecycled) return;
|
|
1859
2312
|
if ((UIView *)scrollView == _sharedHeaderHostView ||
|
|
1860
2313
|
[scrollView isDescendantOfView:_sharedHeaderHostView]) {
|
|
@@ -1866,13 +2319,16 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1866
2319
|
}
|
|
1867
2320
|
if (_sharedHeaderScrollView == scrollView &&
|
|
1868
2321
|
_sharedHeaderHostView.superview == scrollView) {
|
|
2322
|
+
_sharedHeaderHostView.layer.zPosition =
|
|
2323
|
+
_topScrollEdgeHard && (self.hasFixedTopOverlay || _stickyHeaderView != nil) ? 0 : 1000;
|
|
1869
2324
|
[self layoutSharedHeaders];
|
|
1870
2325
|
return;
|
|
1871
2326
|
}
|
|
1872
2327
|
|
|
1873
2328
|
_sharedHeaderScrollView = scrollView;
|
|
1874
2329
|
_sharedHeadersLiftedForPagerTransition = NO;
|
|
1875
|
-
_sharedHeaderHostView.layer.zPosition =
|
|
2330
|
+
_sharedHeaderHostView.layer.zPosition =
|
|
2331
|
+
_topScrollEdgeHard && (self.hasFixedTopOverlay || _stickyHeaderView != nil) ? 0 : 1000;
|
|
1876
2332
|
if (_sharedHeaderHostView.superview != scrollView) {
|
|
1877
2333
|
[scrollView addSubview:_sharedHeaderHostView];
|
|
1878
2334
|
}
|
|
@@ -1919,56 +2375,114 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1919
2375
|
|
|
1920
2376
|
_sharedHeaderHostView.frame = CGRectMake(
|
|
1921
2377
|
0,
|
|
1922
|
-
attachedToList ? -totalHeight :
|
|
2378
|
+
attachedToList ? -totalHeight : self.effectiveTopOverlayHeight,
|
|
1923
2379
|
width,
|
|
1924
2380
|
totalHeight
|
|
1925
2381
|
);
|
|
1926
2382
|
CGFloat stickyY = _headerHeight;
|
|
1927
2383
|
_headerView.frame = CGRectMake(0, 0, width, _headerHeight);
|
|
1928
|
-
|
|
2384
|
+
CGPoint pinnedOrigin = _stickyHeaderIsPinned
|
|
2385
|
+
? [_sharedHeaderScrollView convertPoint:CGPointMake(0, self.effectiveTopOverlayHeight) fromView:_containerView]
|
|
2386
|
+
: CGPointMake(0, self.effectiveTopOverlayHeight);
|
|
2387
|
+
_pinnedStickyHostView.frame = CGRectMake(
|
|
2388
|
+
pinnedOrigin.x, pinnedOrigin.y, width, _stickyHeaderHeight);
|
|
2389
|
+
CGFloat stickyChildY = _stickyHeaderIsPinned ? 0 : stickyY;
|
|
2390
|
+
_stickyHeaderView.frame = CGRectMake(0, stickyChildY, width, _stickyHeaderHeight);
|
|
1929
2391
|
CGFloat nativeTabBarVisibleHeight = _nativeTabBarView.hidden
|
|
1930
2392
|
? 0
|
|
1931
2393
|
: MIN(_stickyHeaderHeight, _nativeTabBarHeight);
|
|
1932
2394
|
_nativeTabBarView.frame = CGRectMake(
|
|
1933
2395
|
0,
|
|
1934
|
-
|
|
2396
|
+
stickyChildY,
|
|
1935
2397
|
width,
|
|
1936
2398
|
nativeTabBarVisibleHeight
|
|
1937
2399
|
);
|
|
1938
2400
|
_nativeSubHeaderView.frame = CGRectMake(
|
|
1939
2401
|
0,
|
|
1940
|
-
|
|
2402
|
+
stickyChildY + nativeTabBarVisibleHeight,
|
|
1941
2403
|
width,
|
|
1942
2404
|
MIN(
|
|
1943
2405
|
MAX(0, _stickyHeaderHeight - nativeTabBarVisibleHeight),
|
|
1944
2406
|
_nativeSubHeaderHeight
|
|
1945
2407
|
)
|
|
1946
2408
|
);
|
|
2409
|
+
_topOverlayHostView.frame = CGRectMake(0, 0, width, self.effectiveTopOverlayHeight);
|
|
2410
|
+
_topOverlayView.frame = _topOverlayHostView.bounds;
|
|
1947
2411
|
[self applyHeaderOffset];
|
|
1948
2412
|
}
|
|
1949
2413
|
|
|
1950
|
-
- (void)
|
|
2414
|
+
- (void)updatePinnedStickyHeader
|
|
1951
2415
|
{
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
if (!
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
(
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
2416
|
+
BOOL attachedToList = _nativeSmoothHeaderScrollEnabled &&
|
|
2417
|
+
_sharedHeaderScrollView != nil && !_sharedHeadersLiftedForPagerTransition;
|
|
2418
|
+
if (!attachedToList && !_stickyHeaderIsPinned) return;
|
|
2419
|
+
UIScrollView *scrollView = _sharedHeaderScrollView;
|
|
2420
|
+
NSNumber *appliedTop = [_appliedTopInsets objectForKey:scrollView];
|
|
2421
|
+
CGFloat refreshInset = scrollView.refreshControl != nil && appliedTop != nil
|
|
2422
|
+
? MAX(0, scrollView.contentInset.top - appliedTop.doubleValue) : 0;
|
|
2423
|
+
CGFloat pinnedTranslation = MAX(0, _currentLogicalOffset - _headerHeight - refreshInset);
|
|
2424
|
+
CGFloat stickyTop = [_sharedHeaderHostView convertPoint:
|
|
2425
|
+
CGPointMake(0, _headerHeight + pinnedTranslation) toView:self].y;
|
|
2426
|
+
BOOL shouldPin = _topScrollEdgeHard &&
|
|
2427
|
+
_stickyHeaderView != nil && attachedToList && !_transitioning &&
|
|
2428
|
+
!_externalOuterTransitioning &&
|
|
2429
|
+
!_isPagerDragging && _headerOffset >= _headerHeight - 0.5 &&
|
|
2430
|
+
stickyTop <= self.effectiveTopOverlayHeight + 0.5;
|
|
2431
|
+
if (_stickyHeaderIsPinned && shouldPin && _pinnedStickyHostView.superview != scrollView) {
|
|
2432
|
+
[self updateTopScrollEdgeForScrollView:nil];
|
|
2433
|
+
[scrollView addSubview:_pinnedStickyHostView];
|
|
2434
|
+
[self updateTopScrollEdgeForScrollView:_observedScrollView];
|
|
2435
|
+
}
|
|
2436
|
+
if (_stickyHeaderIsPinned == shouldPin) {
|
|
2437
|
+
if (shouldPin) {
|
|
2438
|
+
CGPoint origin = [scrollView convertPoint:CGPointMake(0, self.effectiveTopOverlayHeight)
|
|
2439
|
+
fromView:_containerView];
|
|
2440
|
+
_pinnedStickyHostView.frame = CGRectMake(origin.x, origin.y,
|
|
2441
|
+
CGRectGetWidth(self.bounds), _stickyHeaderHeight);
|
|
2442
|
+
}
|
|
2443
|
+
return;
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
_stickyHeaderIsPinned = shouldPin;
|
|
2447
|
+
UIView *host = shouldPin ? _pinnedStickyHostView : _sharedHeaderHostView;
|
|
2448
|
+
_stickyHeaderView.transform = CGAffineTransformIdentity;
|
|
2449
|
+
_nativeTabBarView.transform = CGAffineTransformIdentity;
|
|
2450
|
+
_nativeSubHeaderView.transform = CGAffineTransformIdentity;
|
|
2451
|
+
[_stickyHeaderView removeFromSuperview];
|
|
2452
|
+
[_nativeTabBarView removeFromSuperview];
|
|
2453
|
+
[_nativeSubHeaderView removeFromSuperview];
|
|
2454
|
+
if (_stickyHeaderView != nil) [host addSubview:_stickyHeaderView];
|
|
2455
|
+
[host addSubview:_nativeTabBarView];
|
|
2456
|
+
[host addSubview:_nativeSubHeaderView];
|
|
2457
|
+
CGFloat childY = shouldPin ? 0 : _headerHeight;
|
|
2458
|
+
CGFloat width = CGRectGetWidth(self.bounds);
|
|
2459
|
+
CGFloat tabHeight = _nativeTabBarView.hidden ? 0 : MIN(_stickyHeaderHeight, _nativeTabBarHeight);
|
|
2460
|
+
_stickyHeaderView.frame = CGRectMake(0, childY, width, _stickyHeaderHeight);
|
|
2461
|
+
_nativeTabBarView.frame = CGRectMake(0, childY, width, tabHeight);
|
|
2462
|
+
_nativeSubHeaderView.frame = CGRectMake(0, childY + tabHeight, width,
|
|
2463
|
+
MIN(MAX(0, _stickyHeaderHeight - tabHeight), _nativeSubHeaderHeight));
|
|
2464
|
+
_pinnedStickyHostView.hidden = !shouldPin;
|
|
2465
|
+
if ([_pagerScrollView isKindOfClass:RNCCollapsiblePagerDirectScrollView.class]) {
|
|
2466
|
+
((RNCCollapsiblePagerDirectScrollView *)_pagerScrollView).excludedHeaderView =
|
|
2467
|
+
shouldPin ? _pinnedStickyHostView : _sharedHeaderHostView;
|
|
2468
|
+
}
|
|
2469
|
+
if (shouldPin) {
|
|
2470
|
+
[scrollView addSubview:_pinnedStickyHostView];
|
|
2471
|
+
_pinnedStickyHostView.layer.zPosition = 1000;
|
|
2472
|
+
CGPoint origin = [scrollView convertPoint:CGPointMake(0, self.effectiveTopOverlayHeight)
|
|
2473
|
+
fromView:_containerView];
|
|
2474
|
+
_pinnedStickyHostView.frame = CGRectMake(origin.x, origin.y, width, _stickyHeaderHeight);
|
|
2475
|
+
} else {
|
|
2476
|
+
_pinnedStickyHostView.layer.zPosition = 0;
|
|
2477
|
+
[_containerView addSubview:_pinnedStickyHostView];
|
|
2478
|
+
}
|
|
2479
|
+
[_nativeTabBarView updateAncestorGesturePrecedence];
|
|
2480
|
+
[_nativeSubHeaderView updateAncestorGesturePrecedence];
|
|
2481
|
+
[self updateTopScrollEdgeForScrollView:_observedScrollView];
|
|
1967
2482
|
}
|
|
1968
2483
|
|
|
1969
2484
|
- (void)preparePageForHorizontalTransitionAtIndex:(NSInteger)pageIndex
|
|
1970
2485
|
{
|
|
1971
|
-
[self offsetDiag:@"prepare-check" page:pageIndex scroll:nil];
|
|
1972
2486
|
if (!_nativeSmoothHeaderScrollEnabled ||
|
|
1973
2487
|
pageIndex < 0 || pageIndex >= _pageControllers.count) return;
|
|
1974
2488
|
UIScrollView *scrollView = [self verticalScrollViewForPageAtIndex:pageIndex];
|
|
@@ -1987,11 +2501,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1987
2501
|
_transitionHeaderOffset = _headerOffset;
|
|
1988
2502
|
_transitionPageOffsets = [_pageOffsets copy];
|
|
1989
2503
|
_transitionCompensatedPages = [NSMutableIndexSet new];
|
|
1990
|
-
#if DEBUG
|
|
1991
|
-
_transitionDiagBucket = -1;
|
|
1992
|
-
[self offsetDiag:[NSString stringWithFormat:@"screen-begin-source=%ld-savedQ=%.4f", (long)_transitionSourceIndex,
|
|
1993
|
-
[_transitionPageOffsets[self.currentPageKey] doubleValue]] page:_currentIndex scroll:nil];
|
|
1994
|
-
#endif
|
|
1995
2504
|
}
|
|
1996
2505
|
|
|
1997
2506
|
- (void)updatePageStateTransitionProgress:(CGFloat)progress
|
|
@@ -2008,16 +2517,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2008
2517
|
CGFloat firstQ = [_transitionPageOffsets[[self pageKeyForIndex:first]] doubleValue];
|
|
2009
2518
|
CGFloat secondQ = [_transitionPageOffsets[[self pageKeyForIndex:second]] doubleValue];
|
|
2010
2519
|
_headerOffset = _transitionHeaderOffset;
|
|
2011
|
-
#if DEBUG
|
|
2012
|
-
NSInteger bucket = (NSInteger)floor(fraction * 4);
|
|
2013
|
-
if (_transitionDiagBucket != bucket) {
|
|
2014
|
-
_transitionDiagBucket = bucket;
|
|
2015
|
-
[self offsetDiag:[NSString stringWithFormat:
|
|
2016
|
-
@"screen-progress-kind=%@-source=%ld-target=%ld-sourceQ=%.4f-targetQ=%.4f-position=%.4f-fraction=%.4f",
|
|
2017
|
-
_isPagerDragging ? @"interactive" : @"programmatic-endpoint", (long)first, (long)second,
|
|
2018
|
-
firstQ, secondQ, progress, fraction] page:_currentIndex scroll:nil];
|
|
2019
|
-
}
|
|
2020
|
-
#endif
|
|
2021
2520
|
_syntheticPageRestore = YES;
|
|
2022
2521
|
NSInteger visibleFirst = MAX(0, MIN((NSInteger)floor(progress), (NSInteger)_pageControllers.count - 1));
|
|
2023
2522
|
NSInteger visibleLast = MIN(visibleFirst + 1, (NSInteger)_pageControllers.count - 1);
|
|
@@ -2059,10 +2558,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2059
2558
|
_transitionPageOffsets = nil;
|
|
2060
2559
|
_transitionCompensatedPages = nil;
|
|
2061
2560
|
[self layoutSharedHeaders];
|
|
2062
|
-
#if DEBUG
|
|
2063
|
-
[self offsetDiag:[NSString stringWithFormat:@"screen-finish-source=%ld-cancelled=%d", (long)_transitionSourceIndex,
|
|
2064
|
-
_currentIndex == _transitionSourceIndex] page:_currentIndex scroll:nil];
|
|
2065
|
-
#endif
|
|
2066
2561
|
}
|
|
2067
2562
|
|
|
2068
2563
|
#pragma mark - React mounting
|
|
@@ -2119,9 +2614,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2119
2614
|
if (!_nativeSmoothHeaderScrollEnabled && _transitionPageOffsets == nil && !_syntheticPageRestore &&
|
|
2120
2615
|
![_pendingPageRestoreKeys containsObject:pageKey] &&
|
|
2121
2616
|
(ownsObservedPage || _pageOffsets[pageKey] == nil)) {
|
|
2122
|
-
[self offsetDiag:@"mount-release-save-before" page:index scroll:scrollView];
|
|
2123
2617
|
_pageOffsets[pageKey] = @(scrollView.contentOffset.y + scrollView.contentInset.top);
|
|
2124
|
-
[self offsetDiag:@"mount-release-save-after" page:index scroll:scrollView];
|
|
2125
2618
|
}
|
|
2126
2619
|
break;
|
|
2127
2620
|
}
|
|
@@ -2137,6 +2630,27 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2137
2630
|
- (void)mountingTransactionDidMount:(const MountingTransaction &)transaction
|
|
2138
2631
|
withSurfaceTelemetry:(const SurfaceTelemetry &)surfaceTelemetry
|
|
2139
2632
|
{
|
|
2633
|
+
if (_needsSlotRebuild && !_deferTopEdgeSlotRebuild &&
|
|
2634
|
+
!_transitioning && !_isPagerDragging) {
|
|
2635
|
+
[self rebuildSlots];
|
|
2636
|
+
}
|
|
2637
|
+
[self registerExternalOuterPager];
|
|
2638
|
+
if ([self resolveExternalTopOverlay]) {
|
|
2639
|
+
[self reapplyInsetsToObservedScrollView];
|
|
2640
|
+
[self setNeedsLayout];
|
|
2641
|
+
}
|
|
2642
|
+
if (_pendingTopOverlayAppearanceCommit) {
|
|
2643
|
+
_pendingTopOverlayAppearanceCommit = NO;
|
|
2644
|
+
BOOL localOverlayReady = _topOverlayView.superview == _topOverlayHostView &&
|
|
2645
|
+
_topOverlayView.window == self.window;
|
|
2646
|
+
BOOL externalOverlayReady = _externalTopOverlayTag > 0 &&
|
|
2647
|
+
_externalTopOverlayView.window == self.window;
|
|
2648
|
+
if (!_isBeingRecycled && _topScrollEdgeHard && self.window != nil &&
|
|
2649
|
+
(localOverlayReady || externalOverlayReady)) {
|
|
2650
|
+
[self updateTopScrollEdgeForScrollView:_observedScrollView];
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
[self updateScrollEdgeContentMasks];
|
|
2140
2654
|
[_mountingRemovedScrollViews removeAllObjects];
|
|
2141
2655
|
if (!_nativeSmoothHeaderScrollEnabled || _isBeingRecycled || self.window == nil ||
|
|
2142
2656
|
_currentIndex < 0 || _currentIndex >= _pageControllers.count) return;
|
|
@@ -2214,7 +2728,11 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2214
2728
|
if (_isBeingRecycled) return;
|
|
2215
2729
|
NSInteger safeIndex = MIN(MAX(index, 0), _logicalChildren.count);
|
|
2216
2730
|
[_logicalChildren insertObject:childComponentView atIndex:safeIndex];
|
|
2217
|
-
|
|
2731
|
+
if (_topScrollEdgeHard || _topOverlayView != nil) {
|
|
2732
|
+
_needsSlotRebuild = YES;
|
|
2733
|
+
} else {
|
|
2734
|
+
[self rebuildSlots];
|
|
2735
|
+
}
|
|
2218
2736
|
}
|
|
2219
2737
|
|
|
2220
2738
|
- (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView
|
|
@@ -2225,16 +2743,32 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2225
2743
|
if (existingIndex == NSNotFound) return;
|
|
2226
2744
|
[childComponentView removeFromSuperview];
|
|
2227
2745
|
[_logicalChildren removeObjectAtIndex:existingIndex];
|
|
2228
|
-
|
|
2746
|
+
if (_topScrollEdgeHard || _topOverlayView != nil) {
|
|
2747
|
+
_needsSlotRebuild = YES;
|
|
2748
|
+
} else {
|
|
2749
|
+
[self rebuildSlots];
|
|
2750
|
+
}
|
|
2229
2751
|
}
|
|
2230
2752
|
|
|
2231
2753
|
- (void)rebuildSlots
|
|
2232
2754
|
{
|
|
2755
|
+
if (_deferTopEdgeSlotRebuild) {
|
|
2756
|
+
_needsSlotRebuild = YES;
|
|
2757
|
+
return;
|
|
2758
|
+
}
|
|
2233
2759
|
if (_transitioning || _isPagerDragging) {
|
|
2234
2760
|
_needsSlotRebuild = YES;
|
|
2235
2761
|
return;
|
|
2236
2762
|
}
|
|
2763
|
+
if (_topScrollEdgeHard &&
|
|
2764
|
+
_logicalChildren.count != _pageKeys.count + 2 &&
|
|
2765
|
+
_logicalChildren.count != _pageKeys.count + 3) {
|
|
2766
|
+
_needsSlotRebuild = YES;
|
|
2767
|
+
return;
|
|
2768
|
+
}
|
|
2237
2769
|
_needsSlotRebuild = NO;
|
|
2770
|
+
_deferTopEdgeSlotRebuild = NO;
|
|
2771
|
+
[self restoreScrollEdgeContentMasks];
|
|
2238
2772
|
[self detachScrollObserver];
|
|
2239
2773
|
[self restoreSharedHeadersToContainer];
|
|
2240
2774
|
// OneKey patch: Fabric reuses these views without resetting transforms
|
|
@@ -2246,11 +2780,15 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2246
2780
|
_nativeSubHeaderView.transform = CGAffineTransformIdentity;
|
|
2247
2781
|
[_headerView removeFromSuperview];
|
|
2248
2782
|
[_stickyHeaderView removeFromSuperview];
|
|
2783
|
+
[self updateTopScrollEdgeForScrollView:nil];
|
|
2784
|
+
[_topOverlayView removeFromSuperview];
|
|
2249
2785
|
for (UIViewController *controller in _pageControllers) {
|
|
2250
2786
|
[controller.view removeFromSuperview];
|
|
2251
2787
|
}
|
|
2252
2788
|
_headerView = nil;
|
|
2253
2789
|
_stickyHeaderView = nil;
|
|
2790
|
+
_topOverlayView = nil;
|
|
2791
|
+
[self updateTopScrollEdgeForScrollView:nil];
|
|
2254
2792
|
[_pageControllers removeAllObjects];
|
|
2255
2793
|
|
|
2256
2794
|
if (_logicalChildren.count > 0) {
|
|
@@ -2261,7 +2799,15 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2261
2799
|
_stickyHeaderView = _logicalChildren[1];
|
|
2262
2800
|
[_sharedHeaderHostView addSubview:_stickyHeaderView];
|
|
2263
2801
|
}
|
|
2264
|
-
|
|
2802
|
+
BOOL hasOverlaySlot = _topScrollEdgeHard &&
|
|
2803
|
+
_logicalChildren.count == _pageKeys.count + 3;
|
|
2804
|
+
NSInteger pageSlotStart = hasOverlaySlot ? 3 : 2;
|
|
2805
|
+
if (hasOverlaySlot) {
|
|
2806
|
+
_topOverlayView = _logicalChildren[2];
|
|
2807
|
+
[_topOverlayHostView addSubview:_topOverlayView];
|
|
2808
|
+
}
|
|
2809
|
+
[self resolveExternalTopOverlay];
|
|
2810
|
+
for (NSInteger index = pageSlotStart; index < _logicalChildren.count; index++) {
|
|
2265
2811
|
UIView *page = _logicalChildren[index];
|
|
2266
2812
|
UIViewController *controller = [UIViewController new];
|
|
2267
2813
|
[controller.view addSubview:page];
|
|
@@ -2300,6 +2846,10 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2300
2846
|
if (!_nativeTabBarView.hidden) [_sharedHeaderHostView bringSubviewToFront:_nativeTabBarView];
|
|
2301
2847
|
if (!_nativeSubHeaderView.hidden) [_sharedHeaderHostView bringSubviewToFront:_nativeSubHeaderView];
|
|
2302
2848
|
[_containerView bringSubviewToFront:_sharedHeaderHostView];
|
|
2849
|
+
if (_stickyHeaderIsPinned) {
|
|
2850
|
+
[_pinnedStickyHostView.superview bringSubviewToFront:_pinnedStickyHostView];
|
|
2851
|
+
}
|
|
2852
|
+
if (self.hasFixedTopOverlay) [_containerView bringSubviewToFront:_topOverlayHostView];
|
|
2303
2853
|
[self setNeedsLayout];
|
|
2304
2854
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
2305
2855
|
if (!self->_isBeingRecycled) {
|
|
@@ -2312,7 +2862,10 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2312
2862
|
- (void)layoutSubviews
|
|
2313
2863
|
{
|
|
2314
2864
|
[super layoutSubviews];
|
|
2865
|
+
[self syncScrollEdgeAppearance];
|
|
2315
2866
|
_containerView.frame = self.bounds;
|
|
2867
|
+
[self registerExternalOuterPager];
|
|
2868
|
+
BOOL externalHeightChanged = [self resolveExternalTopOverlay];
|
|
2316
2869
|
if (_directNativePagerEnabled) {
|
|
2317
2870
|
_pagerScrollView.frame = _containerView.bounds;
|
|
2318
2871
|
} else {
|
|
@@ -2375,6 +2928,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2375
2928
|
}
|
|
2376
2929
|
}
|
|
2377
2930
|
[self layoutSharedHeaders];
|
|
2931
|
+
if (externalHeightChanged) [self reapplyInsetsToObservedScrollView];
|
|
2932
|
+
[self updateTopScrollEdgeForScrollView:_observedScrollView];
|
|
2933
|
+
[self updateScrollEdgeContentMasks];
|
|
2378
2934
|
[self updateHeaderSettledStateForceBusy:NO];
|
|
2379
2935
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
2380
2936
|
if (!self->_isBeingRecycled) {
|
|
@@ -2386,9 +2942,16 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2386
2942
|
|
|
2387
2943
|
- (void)prepareForRecycle
|
|
2388
2944
|
{
|
|
2945
|
+
[_externalOuterPager removeNestedOverlayObserver:self];
|
|
2946
|
+
_externalOuterPager = nil;
|
|
2947
|
+
[self cancelTopInsetReboundCheck];
|
|
2948
|
+
[self restoreScrollEdgeContentMasks];
|
|
2949
|
+
[self updateTopScrollEdgeForScrollView:nil];
|
|
2389
2950
|
[self cancelHeaderSettledCheck];
|
|
2390
2951
|
[self publishHeaderSettledAtTop:NO identityChanged:NO];
|
|
2391
2952
|
_isBeingRecycled = YES;
|
|
2953
|
+
_pendingTopOverlayAppearanceCommit = NO;
|
|
2954
|
+
[self syncScrollEdgeAppearance];
|
|
2392
2955
|
_generation++;
|
|
2393
2956
|
[_mountingRemovedScrollViews removeAllObjects];
|
|
2394
2957
|
_needsScrollObserverReattachAfterMount = NO;
|
|
@@ -2408,9 +2971,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2408
2971
|
if (appliedTop != nil && scrollView.refreshControl != nil) {
|
|
2409
2972
|
original.top += MAX(0, scrollView.contentInset.top - appliedTop.doubleValue);
|
|
2410
2973
|
}
|
|
2411
|
-
[self offsetDiag:@"release-original-inset-before" page:_currentIndex scroll:scrollView];
|
|
2412
2974
|
scrollView.contentInset = original;
|
|
2413
|
-
[self offsetDiag:@"release-original-inset-after" page:_currentIndex scroll:scrollView];
|
|
2414
2975
|
}
|
|
2415
2976
|
NSValue *indicatorValue = [_originalIndicatorInsets objectForKey:scrollView];
|
|
2416
2977
|
if (indicatorValue != nil) {
|
|
@@ -2439,6 +3000,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2439
3000
|
_nativeSubHeaderView.transform = CGAffineTransformIdentity;
|
|
2440
3001
|
[_headerView removeFromSuperview];
|
|
2441
3002
|
[_stickyHeaderView removeFromSuperview];
|
|
3003
|
+
[_topOverlayView removeFromSuperview];
|
|
2442
3004
|
for (UIViewController *controller in _pageControllers) {
|
|
2443
3005
|
[controller.view removeFromSuperview];
|
|
2444
3006
|
}
|
|
@@ -2446,6 +3008,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2446
3008
|
[_pageControllers removeAllObjects];
|
|
2447
3009
|
_headerView = nil;
|
|
2448
3010
|
_stickyHeaderView = nil;
|
|
3011
|
+
_topOverlayView = nil;
|
|
3012
|
+
_topOverlayHostView.hidden = YES;
|
|
3013
|
+
[self updateTopScrollEdgeForScrollView:nil];
|
|
2449
3014
|
_pageViewController.dataSource = nil;
|
|
2450
3015
|
_pageViewController.delegate = nil;
|
|
2451
3016
|
_pagerScrollView.delegate = nil;
|
|
@@ -2478,6 +3043,13 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2478
3043
|
_pendingInitialPage = 0;
|
|
2479
3044
|
_headerHeight = 0;
|
|
2480
3045
|
_stickyHeaderHeight = 0;
|
|
3046
|
+
_topOverlayHeight = 0;
|
|
3047
|
+
_externalTopOverlayTag = 0;
|
|
3048
|
+
_externalTopOverlayView = nil;
|
|
3049
|
+
_externalTopOverlayHeight = 0;
|
|
3050
|
+
_externalOuterPageActive = NO;
|
|
3051
|
+
_externalOuterTransitioning = NO;
|
|
3052
|
+
_topScrollEdgeHard = NO;
|
|
2481
3053
|
_nativeTabBarHeight = 44;
|
|
2482
3054
|
_nativeSubHeaderHeight = 74;
|
|
2483
3055
|
_headerOffset = 0;
|
|
@@ -2501,6 +3073,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2501
3073
|
_pendingGoToIndex = -1;
|
|
2502
3074
|
_pendingGoToAnimated = YES;
|
|
2503
3075
|
_needsSlotRebuild = NO;
|
|
3076
|
+
_deferTopEdgeSlotRebuild = NO;
|
|
2504
3077
|
_hasAppliedInitialPage = NO;
|
|
2505
3078
|
// OneKey patch: Fabric retains old props when recycling this view.
|
|
2506
3079
|
_needsPropsReapply = YES;
|
|
@@ -2517,6 +3090,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2517
3090
|
{
|
|
2518
3091
|
const auto &oldViewProps = *std::static_pointer_cast<const RNCCollapsiblePagerViewProps>(_props);
|
|
2519
3092
|
const auto &newViewProps = *std::static_pointer_cast<const RNCCollapsiblePagerViewProps>(props);
|
|
3093
|
+
_applyingTopOverlayInsetChange =
|
|
3094
|
+
oldViewProps.topScrollEdgeHard != newViewProps.topScrollEdgeHard ||
|
|
3095
|
+
oldViewProps.topOverlayHeight != newViewProps.topOverlayHeight;
|
|
2520
3096
|
|
|
2521
3097
|
if (_needsPropsReapply || oldViewProps.initialPage != newViewProps.initialPage) {
|
|
2522
3098
|
_pendingInitialPage = MAX(0, newViewProps.initialPage);
|
|
@@ -2566,6 +3142,43 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2566
3142
|
_stickyHeaderHeight = MAX(0, newViewProps.stickyHeaderHeight);
|
|
2567
3143
|
insetsChanged = YES;
|
|
2568
3144
|
}
|
|
3145
|
+
if (_needsPropsReapply || oldViewProps.topScrollEdgeHard != newViewProps.topScrollEdgeHard) {
|
|
3146
|
+
_topScrollEdgeHard = newViewProps.topScrollEdgeHard;
|
|
3147
|
+
if (oldViewProps.topScrollEdgeHard || newViewProps.topScrollEdgeHard) {
|
|
3148
|
+
_deferTopEdgeSlotRebuild = YES;
|
|
3149
|
+
[self rebuildSlots];
|
|
3150
|
+
NSUInteger generation = _generation;
|
|
3151
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
3152
|
+
if (self->_generation != generation || self->_isBeingRecycled ||
|
|
3153
|
+
!self->_deferTopEdgeSlotRebuild) return;
|
|
3154
|
+
self->_deferTopEdgeSlotRebuild = NO;
|
|
3155
|
+
[self rebuildSlots];
|
|
3156
|
+
});
|
|
3157
|
+
}
|
|
3158
|
+
insetsChanged = YES;
|
|
3159
|
+
}
|
|
3160
|
+
if (_needsPropsReapply ||
|
|
3161
|
+
oldViewProps.topOverlayColorScheme != newViewProps.topOverlayColorScheme) {
|
|
3162
|
+
NSString *nextScheme = RCTNSStringFromString(newViewProps.topOverlayColorScheme);
|
|
3163
|
+
if (self.hasFixedTopOverlay && _topScrollEdgeHard &&
|
|
3164
|
+
_topOverlayColorScheme != nil &&
|
|
3165
|
+
![_topOverlayColorScheme isEqualToString:nextScheme]) {
|
|
3166
|
+
_pendingTopOverlayAppearanceCommit = YES;
|
|
3167
|
+
[self updateTopScrollEdgeForScrollView:nil];
|
|
3168
|
+
}
|
|
3169
|
+
_topOverlayColorScheme = nextScheme;
|
|
3170
|
+
[self syncScrollEdgeAppearance];
|
|
3171
|
+
}
|
|
3172
|
+
if (_needsPropsReapply || oldViewProps.topOverlayHeight != newViewProps.topOverlayHeight) {
|
|
3173
|
+
_topOverlayHeight = MAX(0, newViewProps.topOverlayHeight);
|
|
3174
|
+
insetsChanged = YES;
|
|
3175
|
+
}
|
|
3176
|
+
if (_needsPropsReapply ||
|
|
3177
|
+
oldViewProps.externalTopOverlayTag != newViewProps.externalTopOverlayTag) {
|
|
3178
|
+
_externalTopOverlayTag = newViewProps.externalTopOverlayTag;
|
|
3179
|
+
[self registerExternalOuterPager];
|
|
3180
|
+
insetsChanged = [self resolveExternalTopOverlay] || insetsChanged;
|
|
3181
|
+
}
|
|
2569
3182
|
if (_needsPropsReapply || oldViewProps.pageKeys != newViewProps.pageKeys) {
|
|
2570
3183
|
NSString *json = RCTNSStringFromString(newViewProps.pageKeys);
|
|
2571
3184
|
NSData *data = [json dataUsingEncoding:NSUTF8StringEncoding];
|
|
@@ -2633,17 +3246,23 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2633
3246
|
|
|
2634
3247
|
_needsPropsReapply = NO;
|
|
2635
3248
|
[super updateProps:props oldProps:oldProps];
|
|
3249
|
+
[self updateTopScrollEdgeForScrollView:_observedScrollView];
|
|
2636
3250
|
if (insetsChanged) {
|
|
2637
3251
|
[self reapplyInsetsToObservedScrollView];
|
|
2638
3252
|
[self setNeedsLayout];
|
|
2639
3253
|
}
|
|
3254
|
+
_applyingTopOverlayInsetChange = NO;
|
|
2640
3255
|
if (!_nativeTabBarView.hidden) {
|
|
2641
|
-
[
|
|
3256
|
+
[_nativeTabBarView.superview bringSubviewToFront:_nativeTabBarView];
|
|
2642
3257
|
}
|
|
2643
3258
|
if (!_nativeSubHeaderView.hidden) {
|
|
2644
|
-
[
|
|
3259
|
+
[_nativeSubHeaderView.superview bringSubviewToFront:_nativeSubHeaderView];
|
|
2645
3260
|
}
|
|
2646
3261
|
[_sharedHeaderHostView.superview bringSubviewToFront:_sharedHeaderHostView];
|
|
3262
|
+
if (_stickyHeaderIsPinned) {
|
|
3263
|
+
[_pinnedStickyHostView.superview bringSubviewToFront:_pinnedStickyHostView];
|
|
3264
|
+
}
|
|
3265
|
+
if (self.hasFixedTopOverlay) [_containerView bringSubviewToFront:_topOverlayHostView];
|
|
2647
3266
|
}
|
|
2648
3267
|
|
|
2649
3268
|
#pragma mark - Collapsible scroll coordination
|
|
@@ -2877,21 +3496,17 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2877
3496
|
return;
|
|
2878
3497
|
}
|
|
2879
3498
|
|
|
2880
|
-
[self offsetDiag:@"observer-candidate" page:_currentIndex scroll:candidate];
|
|
2881
3499
|
BOOL replacingCurrentScrollView = _observingContentOffset &&
|
|
2882
3500
|
(!_nativeSmoothHeaderScrollEnabled || [_observedPageKey isEqualToString:self.currentPageKey]);
|
|
2883
3501
|
[self detachScrollObserver];
|
|
2884
3502
|
if (replacingCurrentScrollView) {
|
|
2885
3503
|
// Legacy replacement discards body state; smooth replacement waits for layout.
|
|
2886
|
-
[self offsetDiag:@"observer-replacement-cache-before" page:_currentIndex scroll:candidate];
|
|
2887
3504
|
if (!_nativeSmoothHeaderScrollEnabled) _pageOffsets[self.currentPageKey] = @(_headerOffset);
|
|
2888
|
-
[self offsetDiag:@"observer-replacement-cache-after" page:_currentIndex scroll:candidate];
|
|
2889
3505
|
}
|
|
2890
3506
|
_observedScrollView = candidate;
|
|
2891
3507
|
_observedPageKey = [self.currentPageKey copy];
|
|
2892
3508
|
_observedPageIndex = _currentIndex;
|
|
2893
3509
|
_observedPageGeneration = _generation;
|
|
2894
|
-
[self offsetDiag:@"observer-attached-before-restore" page:_currentIndex scroll:candidate];
|
|
2895
3510
|
[self applyInsetsToScrollView:candidate pageIndex:_currentIndex restore:YES];
|
|
2896
3511
|
[candidate addObserver:self
|
|
2897
3512
|
forKeyPath:@"contentOffset"
|
|
@@ -2909,6 +3524,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2909
3524
|
_observingContentOffset = YES;
|
|
2910
3525
|
_needsScrollObserverReattachAfterMount = NO;
|
|
2911
3526
|
_currentLogicalOffset = candidate.contentOffset.y + candidate.contentInset.top;
|
|
3527
|
+
[self updateTopScrollEdgeForScrollView:candidate];
|
|
2912
3528
|
[self attachSharedHeadersToScrollView:candidate];
|
|
2913
3529
|
[self updateSharedHeaderPressCancellationGesture];
|
|
2914
3530
|
[self updateHeaderSettledStateForceBusy:NO];
|
|
@@ -2916,6 +3532,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2916
3532
|
|
|
2917
3533
|
- (void)detachScrollObserver
|
|
2918
3534
|
{
|
|
3535
|
+
[self cancelTopInsetReboundCheck];
|
|
3536
|
+
[self restorePinnedStickyHeader];
|
|
3537
|
+
[self updateTopScrollEdgeForScrollView:nil];
|
|
2919
3538
|
[self cancelHeaderSettledCheck];
|
|
2920
3539
|
_headerCommandOwner = nil;
|
|
2921
3540
|
_headerSettledGeometry = nil;
|
|
@@ -2925,7 +3544,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2925
3544
|
UIScrollView *scrollView = _observedScrollView;
|
|
2926
3545
|
if (scrollView != nil) {
|
|
2927
3546
|
[scrollView.panGestureRecognizer removeTarget:self action:@selector(observedListPanChanged:)];
|
|
2928
|
-
[self offsetDiag:@"detach-save-before" page:_currentIndex scroll:scrollView];
|
|
2929
3547
|
NSString *ownerKey = _nativeSmoothHeaderScrollEnabled ? _observedPageKey : self.currentPageKey;
|
|
2930
3548
|
BOOL ownerKeyExists = ownerKey != nil && ([_pageKeys containsObject:ownerKey] ||
|
|
2931
3549
|
(_observedPageIndex >= 0 && _observedPageIndex < _pageControllers.count &&
|
|
@@ -2933,7 +3551,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2933
3551
|
if (ownerKey != nil && !_nativeSmoothHeaderScrollEnabled) {
|
|
2934
3552
|
_pageOffsets[ownerKey] = @(scrollView.contentOffset.y + scrollView.contentInset.top);
|
|
2935
3553
|
}
|
|
2936
|
-
[self offsetDiag:@"detach-save-after" page:_currentIndex scroll:scrollView];
|
|
2937
3554
|
if (_observingContentOffset) {
|
|
2938
3555
|
[scrollView removeObserver:self
|
|
2939
3556
|
forKeyPath:@"contentOffset"
|
|
@@ -2975,6 +3592,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2975
3592
|
removeGestureRecognizer:_sharedHeaderOuterPagerGesture];
|
|
2976
3593
|
}
|
|
2977
3594
|
_sharedHeaderOuterPagerGesture.blockedPagerGestures = nil;
|
|
3595
|
+
_sharedHeaderOuterPagerGesture.trackedTouch = nil;
|
|
3596
|
+
_sharedHeaderOuterPagerGesture.protectsNestedBody = NO;
|
|
3597
|
+
_sharedHeaderOuterPagerGesture.cancelsTouchesInView = YES;
|
|
2978
3598
|
_sharedHeaderPressCancellationGestureHost = nil;
|
|
2979
3599
|
_reactTouchHandler = nil;
|
|
2980
3600
|
}
|
|
@@ -3024,7 +3644,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3024
3644
|
}
|
|
3025
3645
|
}
|
|
3026
3646
|
if (_sharedHeaderPressCancellationGesture.view == gestureHost &&
|
|
3027
|
-
_sharedHeaderOuterPagerGesture.view ==
|
|
3647
|
+
_sharedHeaderOuterPagerGesture.view == _containerView &&
|
|
3028
3648
|
_reactTouchHandler == reactTouchHandler) {
|
|
3029
3649
|
_sharedHeaderOuterPagerGesture.blockedPagerGestures = outerPagerGestures;
|
|
3030
3650
|
return;
|
|
@@ -3066,7 +3686,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3066
3686
|
_reactTouchHandler = reactTouchHandler;
|
|
3067
3687
|
_sharedHeaderPressCancellationGestureHost = gestureHost;
|
|
3068
3688
|
[gestureHost addGestureRecognizer:_sharedHeaderPressCancellationGesture];
|
|
3069
|
-
[
|
|
3689
|
+
[_containerView addGestureRecognizer:_sharedHeaderOuterPagerGesture];
|
|
3070
3690
|
RNCCollapsiblePagerLog([NSString stringWithFormat:
|
|
3071
3691
|
@"press-cancel-bridge attached=1 outer-pagers=%lu",
|
|
3072
3692
|
(unsigned long)outerPagerGestures.count]);
|
|
@@ -3138,6 +3758,11 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3138
3758
|
|
|
3139
3759
|
- (void)observedListPanChanged:(UIPanGestureRecognizer *)recognizer
|
|
3140
3760
|
{
|
|
3761
|
+
if (recognizer == _pendingTopInsetReboundScrollView.panGestureRecognizer &&
|
|
3762
|
+
(recognizer.state == UIGestureRecognizerStateBegan ||
|
|
3763
|
+
recognizer.state == UIGestureRecognizerStateChanged)) {
|
|
3764
|
+
[self cancelTopInsetReboundCheck];
|
|
3765
|
+
}
|
|
3141
3766
|
if (recognizer == _observedScrollView.panGestureRecognizer) {
|
|
3142
3767
|
[self updateHeaderSettledStateForceBusy:
|
|
3143
3768
|
recognizer.state == UIGestureRecognizerStateBegan || recognizer.state == UIGestureRecognizerStateChanged];
|
|
@@ -3205,7 +3830,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3205
3830
|
[_managedRefreshControls removeObjectForKey:scrollView];
|
|
3206
3831
|
if (current != nil) [_managedRefreshControls setObject:current forKey:scrollView];
|
|
3207
3832
|
}
|
|
3208
|
-
[RNCRefreshControlOffsetState setOffset:_headerHeight + _stickyHeaderHeight
|
|
3833
|
+
[RNCRefreshControlOffsetState setOffset:self.effectiveTopOverlayHeight + _headerHeight + _stickyHeaderHeight
|
|
3209
3834
|
forControl:current owner:self];
|
|
3210
3835
|
}
|
|
3211
3836
|
|
|
@@ -3219,6 +3844,53 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3219
3844
|
[_managedRefreshControls removeObjectForKey:scrollView];
|
|
3220
3845
|
}
|
|
3221
3846
|
|
|
3847
|
+
- (void)cancelTopInsetReboundCheck
|
|
3848
|
+
{
|
|
3849
|
+
[_topInsetReboundTimer invalidate];
|
|
3850
|
+
_topInsetReboundTimer = nil;
|
|
3851
|
+
_pendingTopInsetReboundScrollView = nil;
|
|
3852
|
+
_pendingTopInsetReboundPageKey = nil;
|
|
3853
|
+
}
|
|
3854
|
+
|
|
3855
|
+
- (void)scheduleTopInsetReboundCheck
|
|
3856
|
+
{
|
|
3857
|
+
if (_pendingTopInsetReboundScrollView == nil ||
|
|
3858
|
+
_pendingTopInsetReboundScrollView != _observedScrollView) return;
|
|
3859
|
+
[_topInsetReboundTimer invalidate];
|
|
3860
|
+
__weak __typeof__(self) weakSelf = self;
|
|
3861
|
+
_topInsetReboundTimer = [NSTimer scheduledTimerWithTimeInterval:0.15 repeats:NO block:^(NSTimer *timer) {
|
|
3862
|
+
__strong __typeof__(weakSelf) self = weakSelf;
|
|
3863
|
+
if (self == nil || self->_topInsetReboundTimer != timer) return;
|
|
3864
|
+
self->_topInsetReboundTimer = nil;
|
|
3865
|
+
UIScrollView *scrollView = self->_pendingTopInsetReboundScrollView;
|
|
3866
|
+
if (scrollView == nil || scrollView != self->_observedScrollView ||
|
|
3867
|
+
self->_pendingTopInsetReboundGeneration != self->_generation ||
|
|
3868
|
+
![self->_pendingTopInsetReboundPageKey isEqualToString:self.currentPageKey] ||
|
|
3869
|
+
self->_isBeingRecycled || self.window == nil ||
|
|
3870
|
+
self->_transitioning || self->_isPagerDragging ||
|
|
3871
|
+
self->_headerCommandOwner == scrollView || self->_pagerTopPageKey != nil) {
|
|
3872
|
+
[self cancelTopInsetReboundCheck];
|
|
3873
|
+
return;
|
|
3874
|
+
}
|
|
3875
|
+
UIGestureRecognizerState pan = scrollView.panGestureRecognizer.state;
|
|
3876
|
+
if (scrollView.refreshControl.isRefreshing || scrollView.isDragging ||
|
|
3877
|
+
scrollView.isDecelerating || scrollView.isTracking ||
|
|
3878
|
+
pan == UIGestureRecognizerStateBegan || pan == UIGestureRecognizerStateChanged ||
|
|
3879
|
+
self->_syntheticPageRestore || self->_transitionPageOffsets != nil) {
|
|
3880
|
+
[self scheduleTopInsetReboundCheck];
|
|
3881
|
+
return;
|
|
3882
|
+
}
|
|
3883
|
+
CGFloat minimumOffset = -scrollView.adjustedContentInset.top;
|
|
3884
|
+
[self cancelTopInsetReboundCheck];
|
|
3885
|
+
if (scrollView.contentOffset.y < minimumOffset - 0.5) {
|
|
3886
|
+
// UIKit can finish a refresh rebound at the inset that preceded an overlay resize.
|
|
3887
|
+
self->_correctingTopInsetRebound = YES;
|
|
3888
|
+
[scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, minimumOffset) animated:NO];
|
|
3889
|
+
self->_correctingTopInsetRebound = NO;
|
|
3890
|
+
}
|
|
3891
|
+
}];
|
|
3892
|
+
}
|
|
3893
|
+
|
|
3222
3894
|
// OneKey patch: release observers, headers and saved state while the scroll view
|
|
3223
3895
|
// still represents the old list, before Fabric applies its next owner's props.
|
|
3224
3896
|
- (void)restoreScrollViewState:(UIScrollView *)scrollView resetSavedOffset:(BOOL)resetSavedOffset
|
|
@@ -3227,9 +3899,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3227
3899
|
[self releaseRefreshControlForScrollView:scrollView];
|
|
3228
3900
|
if (scrollView == _observedScrollView) {
|
|
3229
3901
|
[self detachScrollObserver];
|
|
3230
|
-
[self offsetDiag:resetSavedOffset ? @"release-reset-before" : @"release-retain-before" page:_currentIndex scroll:scrollView];
|
|
3231
3902
|
if (resetSavedOffset && !_nativeSmoothHeaderScrollEnabled) _pageOffsets[self.currentPageKey] = @(_headerOffset);
|
|
3232
|
-
[self offsetDiag:@"release-reset-after" page:_currentIndex scroll:scrollView];
|
|
3233
3903
|
}
|
|
3234
3904
|
if (scrollView == _sharedHeaderScrollView) {
|
|
3235
3905
|
[self restoreSharedHeadersToContainer];
|
|
@@ -3249,10 +3919,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3249
3919
|
[_originalDirectionalLockEnabled objectForKey:scrollView].boolValue;
|
|
3250
3920
|
scrollView.contentInsetAdjustmentBehavior = (UIScrollViewContentInsetAdjustmentBehavior)
|
|
3251
3921
|
[_originalInsetAdjustmentBehavior objectForKey:scrollView].integerValue;
|
|
3252
|
-
[self offsetDiag:@"release-original-offset-before" page:_currentIndex scroll:scrollView];
|
|
3253
3922
|
[scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, logicalOffset - original.top)
|
|
3254
3923
|
animated:NO];
|
|
3255
|
-
[self offsetDiag:@"release-original-offset-after" page:_currentIndex scroll:scrollView];
|
|
3256
3924
|
[_originalInsets removeObjectForKey:scrollView];
|
|
3257
3925
|
[_appliedTopInsets removeObjectForKey:scrollView];
|
|
3258
3926
|
[_originalIndicatorInsets removeObjectForKey:scrollView];
|
|
@@ -3283,7 +3951,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3283
3951
|
{
|
|
3284
3952
|
// Smooth pages retain their complete offset while replacement identity and
|
|
3285
3953
|
// layout settle. Legacy pages retain the historical header-only policy.
|
|
3286
|
-
[self offsetDiag:restore ? @"restore-enter" : @"insets-enter" page:pageIndex scroll:scrollView];
|
|
3287
3954
|
NSString *pageKey = [self pageKeyForIndex:pageIndex];
|
|
3288
3955
|
RNCCollapsiblePagerReleasedScrollState *released = _releasedPageScrollStates[pageKey];
|
|
3289
3956
|
BOOL deferredReleasedIdentity = NO;
|
|
@@ -3329,13 +3996,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3329
3996
|
deferredReleasedIdentity = now - released.pendingFallbackDetectedAt <
|
|
3330
3997
|
RNCCollapsiblePagerFallbackDebounceInterval;
|
|
3331
3998
|
}
|
|
3332
|
-
#if DEBUG
|
|
3333
|
-
[self offsetDiag:[NSString stringWithFormat:@"released-identity-match=%d-deferred=%d-oldTag=%ld-oldScroll=%p", sameList, deferredReleasedIdentity, (long)released.componentTag, (__bridge void *)released.scrollView] page:pageIndex scroll:scrollView];
|
|
3334
|
-
#endif
|
|
3335
3999
|
if (!deferredReleasedIdentity) {
|
|
3336
|
-
[self offsetDiag:@"released-identity-cache-before" page:pageIndex scroll:scrollView];
|
|
3337
4000
|
if (!sameList && !_nativeSmoothHeaderScrollEnabled) _pageOffsets[pageKey] = @(_headerOffset);
|
|
3338
|
-
[self offsetDiag:@"released-identity-cache-after" page:pageIndex scroll:scrollView];
|
|
3339
4001
|
[_releasedPageScrollStates removeObjectForKey:pageKey];
|
|
3340
4002
|
}
|
|
3341
4003
|
}
|
|
@@ -3360,22 +4022,24 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3360
4022
|
// suppress its later vertical movement while the pager is still pending.
|
|
3361
4023
|
scrollView.directionalLockEnabled = _nativeSmoothHeaderScrollEnabled
|
|
3362
4024
|
? NO : [_originalDirectionalLockEnabled objectForKey:scrollView].boolValue;
|
|
3363
|
-
[self updateNativeScrollerViewport:scrollView stickyHeight:_stickyHeaderHeight];
|
|
4025
|
+
[self updateNativeScrollerViewport:scrollView stickyHeight:_stickyHeaderHeight + self.effectiveTopOverlayHeight];
|
|
3364
4026
|
[self updateRefreshControlForScrollView:scrollView];
|
|
3365
4027
|
CGFloat previousTopInset = scrollView.contentInset.top;
|
|
3366
4028
|
CGFloat previousLogicalOffset = scrollView.contentOffset.y + previousTopInset;
|
|
4029
|
+
// Preserve UIKit's rebound after refresh while the content is beyond its top inset.
|
|
4030
|
+
BOOL wasReturningFromPull = scrollView.contentOffset.y < -scrollView.adjustedContentInset.top - 0.5;
|
|
3367
4031
|
NSNumber *appliedTop = [_appliedTopInsets objectForKey:scrollView];
|
|
3368
4032
|
// UIRefreshControl owns its temporary top inset, including while ending.
|
|
3369
4033
|
CGFloat refreshInset = appliedTop != nil && scrollView.refreshControl != nil
|
|
3370
4034
|
? MAX(0, previousTopInset - appliedTop.doubleValue) : 0;
|
|
3371
4035
|
UIEdgeInsets next = original;
|
|
3372
|
-
next.top += _headerHeight + _stickyHeaderHeight;
|
|
4036
|
+
next.top += self.effectiveTopOverlayHeight + _headerHeight + _stickyHeaderHeight;
|
|
3373
4037
|
CGFloat pagerTopInset = next.top;
|
|
3374
4038
|
next.top += refreshInset;
|
|
3375
4039
|
[_appliedTopInsets setObject:@(pagerTopInset) forKey:scrollView];
|
|
3376
4040
|
// Short pages still need enough range to collapse the header; long pages
|
|
3377
4041
|
// must end at the last row rather than leave a header-sized blank footer.
|
|
3378
|
-
next.bottom += MAX(0, scrollView.bounds.size.height - _stickyHeaderHeight
|
|
4042
|
+
next.bottom += MAX(0, scrollView.bounds.size.height - _stickyHeaderHeight - self.effectiveTopOverlayHeight
|
|
3379
4043
|
- original.top - original.bottom - scrollView.contentSize.height);
|
|
3380
4044
|
if (UIEdgeInsetsEqualToEdgeInsets(scrollView.contentInset, next) && !restore) {
|
|
3381
4045
|
// UICollectionView may report contentSize during a bounce without changing
|
|
@@ -3385,9 +4049,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3385
4049
|
}
|
|
3386
4050
|
BOOL previousSyntheticRestore = _syntheticPageRestore;
|
|
3387
4051
|
if (_nativeSmoothHeaderScrollEnabled || [_alignedBodyKeys containsObject:pageKey]) _syntheticPageRestore = YES;
|
|
3388
|
-
[self offsetDiag:@"inset-assignment-before" page:pageIndex scroll:scrollView];
|
|
3389
4052
|
scrollView.contentInset = next;
|
|
3390
|
-
[self offsetDiag:@"inset-assignment-after" page:pageIndex scroll:scrollView];
|
|
3391
4053
|
UIEdgeInsets indicatorInsets = scrollView.verticalScrollIndicatorInsets;
|
|
3392
4054
|
indicatorInsets.top = pagerTopInset;
|
|
3393
4055
|
scrollView.verticalScrollIndicatorInsets = indicatorInsets;
|
|
@@ -3419,9 +4081,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3419
4081
|
} else {
|
|
3420
4082
|
restoredLogicalOffset = deferredReleasedIdentity || saved == nil ? _headerOffset : saved.doubleValue;
|
|
3421
4083
|
}
|
|
3422
|
-
#if DEBUG
|
|
3423
|
-
[self offsetDiag:[NSString stringWithFormat:@"restore-candidate-saved=%.4f-headerAlignedLogical=%.4f", saved.doubleValue, restoredLogicalOffset] page:pageIndex scroll:scrollView];
|
|
3424
|
-
#endif
|
|
3425
4084
|
intendedLogicalOffset = restoredLogicalOffset;
|
|
3426
4085
|
// Cancellation restores the visible source, while retaining its unreachable reading intent.
|
|
3427
4086
|
if (_nativeSmoothHeaderScrollEnabled && _headerOffset >= _headerHeight &&
|
|
@@ -3434,9 +4093,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3434
4093
|
}
|
|
3435
4094
|
// An inactive list may shrink after its offset observer was detached.
|
|
3436
4095
|
// Do not overwrite its corrected offset with a now-unreachable cached one.
|
|
3437
|
-
#if DEBUG
|
|
3438
|
-
[self offsetDiag:[NSString stringWithFormat:@"restore-clamp-before-candidate=%.4f-max=%.4f", targetOffset, MAX(-next.top, scrollView.contentSize.height - scrollView.bounds.size.height + next.bottom)] page:pageIndex scroll:scrollView];
|
|
3439
|
-
#endif
|
|
3440
4096
|
targetOffset = RNCLimitRestoredScrollOffset(
|
|
3441
4097
|
targetOffset,
|
|
3442
4098
|
scrollView.contentSize.height,
|
|
@@ -3445,13 +4101,17 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3445
4101
|
next.bottom
|
|
3446
4102
|
);
|
|
3447
4103
|
}
|
|
3448
|
-
#if DEBUG
|
|
3449
|
-
[self offsetDiag:[NSString stringWithFormat:@"restore-final-target=%.4f-restore=%d", targetOffset, restore] page:pageIndex scroll:scrollView];
|
|
3450
|
-
#endif
|
|
3451
4104
|
// A bottom-inset change must not cancel UIKit's refresh or bounce settlement.
|
|
3452
|
-
if (restore || previousTopInset != next.top) {
|
|
4105
|
+
if (restore || (previousTopInset != next.top && !wasReturningFromPull)) {
|
|
3453
4106
|
[scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, targetOffset) animated:NO];
|
|
3454
4107
|
}
|
|
4108
|
+
if (_applyingTopOverlayInsetChange && !restore && wasReturningFromPull &&
|
|
4109
|
+
previousTopInset != next.top && scrollView == _observedScrollView) {
|
|
4110
|
+
_pendingTopInsetReboundScrollView = scrollView;
|
|
4111
|
+
_pendingTopInsetReboundPageKey = [pageKey copy];
|
|
4112
|
+
_pendingTopInsetReboundGeneration = _generation;
|
|
4113
|
+
[self scheduleTopInsetReboundCheck];
|
|
4114
|
+
}
|
|
3455
4115
|
if (_nativeSmoothHeaderScrollEnabled && restore && targetOffset + next.top + 0.5 < intendedLogicalOffset) {
|
|
3456
4116
|
[_pendingPageRestoreKeys addObject:pageKey];
|
|
3457
4117
|
}
|
|
@@ -3461,7 +4121,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3461
4121
|
if (!_nativeSmoothHeaderScrollEnabled) [self updateHeaderForScrollView:scrollView];
|
|
3462
4122
|
else [self applyHeaderOffset];
|
|
3463
4123
|
}
|
|
3464
|
-
[self offsetDiag:@"restore-post" page:pageIndex scroll:scrollView];
|
|
3465
4124
|
}
|
|
3466
4125
|
|
|
3467
4126
|
- (void)updateNativeScrollerViewport:(UIScrollView *)scrollView stickyHeight:(CGFloat)height
|
|
@@ -3478,6 +4137,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3478
4137
|
_observedPageKey == nil || ![_observedPageKey isEqualToString:self.currentPageKey] ||
|
|
3479
4138
|
_observedPageIndex != _currentIndex || _observedPageGeneration != _generation ||
|
|
3480
4139
|
_transitioning || _isPagerDragging || _transitionPageOffsets != nil || _syntheticPageRestore) return;
|
|
4140
|
+
[self cancelTopInsetReboundCheck];
|
|
3481
4141
|
NSInteger phase = [notification.userInfo[@"phase"] integerValue];
|
|
3482
4142
|
[self updateHeaderSettledStateForceBusy:NO];
|
|
3483
4143
|
if (phase == 1) _headerCommandOwner = scrollView;
|
|
@@ -3510,7 +4170,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3510
4170
|
_headerOffset = 0;
|
|
3511
4171
|
[self applyHeaderOffset];
|
|
3512
4172
|
}
|
|
3513
|
-
[self offsetDiag:@"list-command-body-takeover" page:_currentIndex scroll:scrollView];
|
|
3514
4173
|
}
|
|
3515
4174
|
|
|
3516
4175
|
- (void)nativeScrollerWillReceiveScrollCommand:(RNCNativeScrollerComponentView *)scroller
|
|
@@ -3522,12 +4181,12 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3522
4181
|
_observedPageIndex != _currentIndex || _currentIndex < 0 || _currentIndex >= _pageControllers.count ||
|
|
3523
4182
|
_observedPageGeneration != _generation || _transitioning || _isPagerDragging ||
|
|
3524
4183
|
_transitionPageOffsets != nil || _syntheticPageRestore) return;
|
|
4184
|
+
[self cancelTopInsetReboundCheck];
|
|
3525
4185
|
if (!_dispatchingPagerTop) {
|
|
3526
4186
|
[self cancelPagerTop];
|
|
3527
4187
|
[_alignedBodyKeys removeObject:ownerKey];
|
|
3528
4188
|
}
|
|
3529
4189
|
if (!_nativeSmoothHeaderScrollEnabled) return;
|
|
3530
|
-
[self offsetDiag:@"command-pending-cancel-before" page:_observedPageIndex scroll:scrollView];
|
|
3531
4190
|
[_pendingPageRestoreKeys removeObject:ownerKey];
|
|
3532
4191
|
_currentLogicalOffset = scrollView.contentOffset.y + scrollView.contentInset.top;
|
|
3533
4192
|
[_commandBodyKeys removeObject:ownerKey];
|
|
@@ -3536,7 +4195,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3536
4195
|
[_displayBodyOverrides removeObjectForKey:ownerKey];
|
|
3537
4196
|
_pageOffsets[ownerKey] = @(MAX(_currentLogicalOffset - _headerHeight, 0));
|
|
3538
4197
|
[self updateHeaderForScrollView:scrollView];
|
|
3539
|
-
[self offsetDiag:@"command-pending-cancel-after" page:_observedPageIndex scroll:scrollView];
|
|
3540
4198
|
}
|
|
3541
4199
|
|
|
3542
4200
|
|
|
@@ -3598,6 +4256,14 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3598
4256
|
return;
|
|
3599
4257
|
}
|
|
3600
4258
|
if (context == RNCCollapsiblePagerContentOffsetContext && object == _observedScrollView) {
|
|
4259
|
+
if (_pendingTopInsetReboundScrollView == object &&
|
|
4260
|
+
([keyPath isEqualToString:@"contentOffset"] || [keyPath isEqualToString:@"contentInset"])) {
|
|
4261
|
+
if (((UIScrollView *)object).panGestureRecognizer.state == UIGestureRecognizerStateChanged) {
|
|
4262
|
+
[self cancelTopInsetReboundCheck];
|
|
4263
|
+
} else {
|
|
4264
|
+
[self scheduleTopInsetReboundCheck];
|
|
4265
|
+
}
|
|
4266
|
+
}
|
|
3601
4267
|
if (![change[NSKeyValueChangeOldKey] isEqual:change[NSKeyValueChangeNewKey]]) {
|
|
3602
4268
|
[self updateHeaderSettledStateForceBusy:YES];
|
|
3603
4269
|
}
|
|
@@ -3662,18 +4328,12 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3662
4328
|
}
|
|
3663
4329
|
UIScrollView *scrollView = (UIScrollView *)object;
|
|
3664
4330
|
_currentLogicalOffset = scrollView.contentOffset.y + scrollView.contentInset.top;
|
|
3665
|
-
|
|
3666
|
-
BOOL offsetDiagChanged = _pageOffsets[self.currentPageKey] == nil || _pageOffsets[self.currentPageKey].doubleValue != (_nativeSmoothHeaderScrollEnabled ? MAX(_currentLogicalOffset - _headerHeight, 0) : _currentLogicalOffset);
|
|
3667
|
-
if (offsetDiagChanged) [self offsetDiag:@"kvo-cache-before" page:_currentIndex scroll:scrollView];
|
|
3668
|
-
#endif
|
|
4331
|
+
if (_correctingTopInsetRebound) return;
|
|
3669
4332
|
if ([_alignedBodyKeys containsObject:_observedPageKey] ||
|
|
3670
4333
|
(_nativeSmoothHeaderScrollEnabled && scrollView.panGestureRecognizer.state != UIGestureRecognizerStateChanged &&
|
|
3671
4334
|
!scrollView.isDecelerating && ![_commandBodyKeys containsObject:_observedPageKey])) return;
|
|
3672
4335
|
_pageOffsets[_nativeSmoothHeaderScrollEnabled ? _observedPageKey : self.currentPageKey] =
|
|
3673
4336
|
@(_nativeSmoothHeaderScrollEnabled ? MAX(_currentLogicalOffset - _headerHeight, 0) : _currentLogicalOffset);
|
|
3674
|
-
#if DEBUG
|
|
3675
|
-
if (offsetDiagChanged) [self offsetDiag:@"kvo-cache-after" page:_currentIndex scroll:scrollView];
|
|
3676
|
-
#endif
|
|
3677
4337
|
[self updateHeaderForScrollView:scrollView];
|
|
3678
4338
|
return;
|
|
3679
4339
|
}
|
|
@@ -3714,17 +4374,22 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3714
4374
|
? MAX(0, scrollView.contentInset.top - appliedTop.doubleValue) : 0;
|
|
3715
4375
|
CGFloat pinnedTranslation = MAX(0, _currentLogicalOffset - _headerHeight - refreshInset);
|
|
3716
4376
|
CGAffineTransform stickyTransform = CGAffineTransformMakeTranslation(0, pinnedTranslation);
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
4377
|
+
[self updatePinnedStickyHeader];
|
|
4378
|
+
_stickyHeaderView.transform = _stickyHeaderIsPinned ? CGAffineTransformIdentity : stickyTransform;
|
|
4379
|
+
_nativeTabBarView.transform = _stickyHeaderIsPinned ? CGAffineTransformIdentity : stickyTransform;
|
|
4380
|
+
_nativeSubHeaderView.transform = _stickyHeaderIsPinned ? CGAffineTransformIdentity : stickyTransform;
|
|
4381
|
+
[self updateScrollEdgeContentMasks];
|
|
3720
4382
|
return;
|
|
3721
4383
|
}
|
|
4384
|
+
[self updatePinnedStickyHeader];
|
|
3722
4385
|
CGAffineTransform collapseTransform = CGAffineTransformMakeTranslation(0, -_headerOffset);
|
|
3723
4386
|
_sharedHeaderHostView.transform = collapseTransform;
|
|
3724
4387
|
_headerView.transform = CGAffineTransformIdentity;
|
|
3725
4388
|
_stickyHeaderView.transform = CGAffineTransformIdentity;
|
|
3726
4389
|
_nativeTabBarView.transform = CGAffineTransformIdentity;
|
|
3727
4390
|
_nativeSubHeaderView.transform = CGAffineTransformIdentity;
|
|
4391
|
+
[self updateTopScrollEdgeForScrollView:_observedScrollView];
|
|
4392
|
+
[self updateScrollEdgeContentMasks];
|
|
3728
4393
|
}
|
|
3729
4394
|
|
|
3730
4395
|
#pragma mark - Pager navigation
|
|
@@ -3736,7 +4401,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3736
4401
|
|
|
3737
4402
|
- (void)goTo:(NSInteger)index animated:(BOOL)animated
|
|
3738
4403
|
{
|
|
3739
|
-
[self
|
|
4404
|
+
[self cancelTopInsetReboundCheck];
|
|
3740
4405
|
if (_isBeingRecycled || index < 0 || index >= _pageControllers.count) return;
|
|
3741
4406
|
_hasReceivedPageCommand = YES;
|
|
3742
4407
|
_hasAppliedInitialPage = YES;
|
|
@@ -3867,6 +4532,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3867
4532
|
if (!self->_transitioning && !self->_needsSlotRebuild) {
|
|
3868
4533
|
[self attachScrollObserverForCurrentPage];
|
|
3869
4534
|
}
|
|
4535
|
+
[self updateScrollEdgeContentMasks];
|
|
3870
4536
|
});
|
|
3871
4537
|
}
|
|
3872
4538
|
|
|
@@ -3918,7 +4584,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3918
4584
|
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
|
|
3919
4585
|
{
|
|
3920
4586
|
[self updateHeaderSettledStateForceBusy:YES];
|
|
3921
|
-
[self offsetDiag:@"pager-drag-begin" page:_currentIndex scroll:_observedScrollView];
|
|
3922
4587
|
if (_nativeSmoothHeaderScrollEnabled && scrollView == _pagerScrollView) {
|
|
3923
4588
|
[_contentPressCancellationGesture cancelPressForPan:scrollView.panGestureRecognizer];
|
|
3924
4589
|
}
|
|
@@ -4075,6 +4740,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
4075
4740
|
CGFloat offset = progress - position;
|
|
4076
4741
|
position = MAX(0, MIN(position, (NSInteger)_pageControllers.count - 1));
|
|
4077
4742
|
[self updatePageStateTransitionProgress:progress];
|
|
4743
|
+
[self updateScrollEdgeContentMasks];
|
|
4078
4744
|
[_nativeTabBarView setProgress:progress];
|
|
4079
4745
|
const auto emitter = [self eventEmitter];
|
|
4080
4746
|
if (emitter) {
|
|
@@ -4093,6 +4759,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
4093
4759
|
tabProgress = _currentIndex + (_destinationIndex - _currentIndex) * transitionProgress;
|
|
4094
4760
|
}
|
|
4095
4761
|
[self updatePageStateTransitionProgress:tabProgress];
|
|
4762
|
+
[self updateScrollEdgeContentMasks];
|
|
4096
4763
|
[_nativeTabBarView setProgress:tabProgress];
|
|
4097
4764
|
const auto emitter = [self eventEmitter];
|
|
4098
4765
|
if (emitter) {
|
|
@@ -4110,7 +4777,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
4110
4777
|
|
|
4111
4778
|
- (void)emitPageSelected:(NSInteger)position
|
|
4112
4779
|
{
|
|
4113
|
-
[self offsetDiag:@"selection-complete" page:position scroll:_observedScrollView];
|
|
4114
4780
|
const auto emitter = [self eventEmitter];
|
|
4115
4781
|
if (emitter) emitter->onPageSelected({.position = (double)position});
|
|
4116
4782
|
}
|
|
@@ -4255,12 +4921,12 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
4255
4921
|
_currentLogicalOffset = logical;
|
|
4256
4922
|
_headerOffset = _pagerTopLogicalTarget;
|
|
4257
4923
|
[self applyHeaderOffset];
|
|
4258
|
-
[self offsetDiag:[NSString stringWithFormat:@"pager-top-complete-token=%lu-target=%.4f", (unsigned long)_pagerTopToken, _pagerTopLogicalTarget] page:_currentIndex scroll:scrollView];
|
|
4259
4924
|
[self cancelPagerTop];
|
|
4260
4925
|
}
|
|
4261
4926
|
|
|
4262
4927
|
- (void)scrollToTop:(NSInteger)target animated:(BOOL)animated
|
|
4263
4928
|
{
|
|
4929
|
+
[self cancelTopInsetReboundCheck];
|
|
4264
4930
|
UIScrollView *scrollView = _observedScrollView;
|
|
4265
4931
|
NSString *key = _observedPageKey;
|
|
4266
4932
|
if ((target != 0 && target != 1) || _isBeingRecycled || _transitioning || _isPagerDragging ||
|
|
@@ -4292,7 +4958,6 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
4292
4958
|
[self applyHeaderOffset];
|
|
4293
4959
|
CGFloat physicalTarget = RNCLimitRestoredScrollOffset(logicalTarget - scrollView.contentInset.top,
|
|
4294
4960
|
scrollView.contentSize.height, scrollView.bounds.size.height, scrollView.contentInset.top, scrollView.contentInset.bottom);
|
|
4295
|
-
[self offsetDiag:[NSString stringWithFormat:@"pager-top-begin-token=%lu-target=%ld-animated=%d", (unsigned long)_pagerTopToken, (long)target, animated] page:_currentIndex scroll:scrollView];
|
|
4296
4961
|
_dispatchingPagerTop = YES;
|
|
4297
4962
|
[scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, physicalTarget) animated:animated];
|
|
4298
4963
|
_dispatchingPagerTop = NO;
|
|
@@ -4357,6 +5022,22 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
4357
5022
|
: velocity;
|
|
4358
5023
|
BOOL hasMotion = MAX(fabs(intent.x), fabs(intent.y)) >= 1;
|
|
4359
5024
|
BOOL horizontal = hasMotion && fabs(intent.x) > fabs(intent.y);
|
|
5025
|
+
if (_sharedHeaderOuterPagerGesture.protectsNestedBody) {
|
|
5026
|
+
NSInteger pageCount = _pageControllers.count;
|
|
5027
|
+
BOOL validPage = _currentIndex >= 0 && _currentIndex < pageCount;
|
|
5028
|
+
NSInteger firstPageIndex = self.isLtrLayout ? 0 : pageCount - 1;
|
|
5029
|
+
NSInteger lastPageIndex = self.isLtrLayout ? pageCount - 1 : 0;
|
|
5030
|
+
BOOL outward = validPage &&
|
|
5031
|
+
((_currentIndex == firstPageIndex && intent.x > 0) ||
|
|
5032
|
+
(_currentIndex == lastPageIndex && intent.x < 0));
|
|
5033
|
+
UIScrollView *outer = RNCNestedOuterReactPagerScrollView(self);
|
|
5034
|
+
BOOL protectInnerPage = horizontal && validPage && pageCount > 1 &&
|
|
5035
|
+
_nestedScrollEnabled && _scrollEnabled && _pagerScrollView.scrollEnabled &&
|
|
5036
|
+
_pagerScrollView.panGestureRecognizer.enabled && outer != nil && outer.scrollEnabled &&
|
|
5037
|
+
[_sharedHeaderOuterPagerGesture.blockedPagerGestures
|
|
5038
|
+
containsObject:outer.panGestureRecognizer] && !outward;
|
|
5039
|
+
return protectInnerPage;
|
|
5040
|
+
}
|
|
4360
5041
|
RNCCollapsiblePagerLog([NSString stringWithFormat:
|
|
4361
5042
|
@"header-axis role=outer-pager-block dx=%.2f dy=%.2f velocity=(%.2f,%.2f) result=%d outer-pagers=%lu",
|
|
4362
5043
|
intent.x,
|
|
@@ -4396,24 +5077,21 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
4396
5077
|
return vertical;
|
|
4397
5078
|
}
|
|
4398
5079
|
if (gestureRecognizer != _blockerGesture) return YES;
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
UIPanGestureRecognizer *pan = (UIPanGestureRecognizer *)gestureRecognizer;
|
|
4403
|
-
CGPoint velocity = [pan velocityInView:self];
|
|
5080
|
+
UIPanGestureRecognizer *pan = [gestureRecognizer isKindOfClass:UIPanGestureRecognizer.class]
|
|
5081
|
+
? (UIPanGestureRecognizer *)gestureRecognizer : nil;
|
|
5082
|
+
CGPoint velocity = pan != nil ? [pan velocityInView:self] : CGPointZero;
|
|
4404
5083
|
NSInteger pageCount = _pageControllers.count;
|
|
4405
|
-
if (pageCount == 0 || _currentIndex < 0 || _currentIndex >= pageCount) return NO;
|
|
4406
|
-
|
|
4407
5084
|
CGFloat absVelocityX = fabs(velocity.x);
|
|
4408
5085
|
CGFloat absVelocityY = fabs(velocity.y);
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
BOOL
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
5086
|
+
NSInteger firstPageIndex = self.isLtrLayout ? 0 : pageCount - 1;
|
|
5087
|
+
NSInteger lastPageIndex = self.isLtrLayout ? pageCount - 1 : 0;
|
|
5088
|
+
BOOL validPage = pageCount > 0 && _currentIndex >= 0 && _currentIndex < pageCount;
|
|
5089
|
+
BOOL horizontal = absVelocityX > absVelocityY && absVelocityX >= 1.0f;
|
|
5090
|
+
BOOL outward = validPage &&
|
|
5091
|
+
((_currentIndex == firstPageIndex && velocity.x > 0) ||
|
|
5092
|
+
(_currentIndex == lastPageIndex && velocity.x < 0));
|
|
5093
|
+
BOOL shouldBegin = _nestedScrollEnabled && pan != nil && horizontal && outward;
|
|
5094
|
+
return shouldBegin;
|
|
4417
5095
|
}
|
|
4418
5096
|
|
|
4419
5097
|
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
|
|
@@ -4423,13 +5101,29 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
4423
5101
|
_sharedHeaderPressCancellationGestureHost == nil) return NO;
|
|
4424
5102
|
UIView *touchView = touch.view;
|
|
4425
5103
|
BOOL beginsInSharedHeader = touchView == _sharedHeaderHostView ||
|
|
4426
|
-
[touchView isDescendantOfView:_sharedHeaderHostView]
|
|
4427
|
-
|
|
5104
|
+
[touchView isDescendantOfView:_sharedHeaderHostView] ||
|
|
5105
|
+
touchView == _pinnedStickyHostView ||
|
|
5106
|
+
[touchView isDescendantOfView:_pinnedStickyHostView];
|
|
5107
|
+
if (_sharedHeaderOuterPagerGesture.trackedTouch != nil &&
|
|
5108
|
+
_sharedHeaderOuterPagerGesture.trackedTouch != touch) return NO;
|
|
5109
|
+
UIScrollView *outer = RNCNestedOuterReactPagerScrollView(self);
|
|
5110
|
+
BOOL beginsInNestedBody = !beginsInSharedHeader && _nestedScrollEnabled &&
|
|
5111
|
+
_scrollEnabled && _pagerScrollView.scrollEnabled &&
|
|
5112
|
+
_pagerScrollView.panGestureRecognizer.enabled && _pageControllers.count > 1 &&
|
|
5113
|
+
outer != nil && outer.scrollEnabled &&
|
|
5114
|
+
[_sharedHeaderOuterPagerGesture.blockedPagerGestures containsObject:outer.panGestureRecognizer] &&
|
|
5115
|
+
(touchView == _pagerScrollView || [touchView isDescendantOfView:_pagerScrollView]);
|
|
5116
|
+
if (!beginsInSharedHeader && !beginsInNestedBody) return NO;
|
|
5117
|
+
|
|
5118
|
+
_sharedHeaderOuterPagerGesture.trackedTouch = touch;
|
|
5119
|
+
_sharedHeaderOuterPagerGesture.protectsNestedBody = beginsInNestedBody;
|
|
5120
|
+
_sharedHeaderOuterPagerGesture.cancelsTouchesInView = !beginsInNestedBody;
|
|
4428
5121
|
|
|
4429
5122
|
RNCCollapsiblePagerLog([NSString stringWithFormat:
|
|
4430
|
-
@"outer-pager-touch target=%@ outer-pagers=%lu",
|
|
5123
|
+
@"outer-pager-touch target=%@ outer-pagers=%lu nested-body=%d",
|
|
4431
5124
|
touchView == nil ? @"none" : NSStringFromClass(touchView.class),
|
|
4432
|
-
(unsigned long)_sharedHeaderOuterPagerGesture.blockedPagerGestures.count
|
|
5125
|
+
(unsigned long)_sharedHeaderOuterPagerGesture.blockedPagerGestures.count,
|
|
5126
|
+
beginsInNestedBody]);
|
|
4433
5127
|
return YES;
|
|
4434
5128
|
}
|
|
4435
5129
|
if (gestureRecognizer == _contentPressCancellationGesture) {
|
|
@@ -4438,14 +5132,17 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
4438
5132
|
return _nativeSmoothHeaderScrollEnabled && !_isBeingRecycled &&
|
|
4439
5133
|
_contentPressCancellationGesture.view != nil &&
|
|
4440
5134
|
[touchView isDescendantOfView:_pagerScrollView] &&
|
|
4441
|
-
![touchView isDescendantOfView:_sharedHeaderHostView]
|
|
5135
|
+
![touchView isDescendantOfView:_sharedHeaderHostView] &&
|
|
5136
|
+
![touchView isDescendantOfView:_pinnedStickyHostView];
|
|
4442
5137
|
}
|
|
4443
5138
|
if (gestureRecognizer == _sharedHeaderPressCancellationGesture) {
|
|
4444
5139
|
if (!_nativeSmoothHeaderScrollEnabled || _isBeingRecycled ||
|
|
4445
5140
|
_sharedHeaderPressCancellationGestureHost == nil) return NO;
|
|
4446
5141
|
UIView *touchView = touch.view;
|
|
4447
5142
|
BOOL beginsInSharedHeader = touchView == _sharedHeaderHostView ||
|
|
4448
|
-
[touchView isDescendantOfView:_sharedHeaderHostView]
|
|
5143
|
+
[touchView isDescendantOfView:_sharedHeaderHostView] ||
|
|
5144
|
+
touchView == _pinnedStickyHostView ||
|
|
5145
|
+
[touchView isDescendantOfView:_pinnedStickyHostView];
|
|
4449
5146
|
if (!beginsInSharedHeader) return NO;
|
|
4450
5147
|
|
|
4451
5148
|
RNCCollapsiblePagerLog([NSString stringWithFormat:
|
|
@@ -4456,7 +5153,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
4456
5153
|
if (gestureRecognizer == _verticalPagerGesture) {
|
|
4457
5154
|
UIView *touchView = touch.view;
|
|
4458
5155
|
return touchView != _sharedHeaderHostView &&
|
|
4459
|
-
![touchView isDescendantOfView:_sharedHeaderHostView]
|
|
5156
|
+
![touchView isDescendantOfView:_sharedHeaderHostView] &&
|
|
5157
|
+
touchView != _pinnedStickyHostView &&
|
|
5158
|
+
![touchView isDescendantOfView:_pinnedStickyHostView];
|
|
4460
5159
|
}
|
|
4461
5160
|
// A new touch can cancel UIKit's animation before its first frame. Queue a
|
|
4462
5161
|
// non-animated settle for the next safe transition boundary instead of
|