@onekeyfe/react-native-pager-view 3.0.118 → 3.0.120
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/build.gradle +1 -0
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerHost.kt +368 -1
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerNativeHeaders.kt +503 -0
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerViewManager.kt +133 -2
- package/android/src/main/java/com/reactnativepagerview/event/NativeHeaderPressEvent.kt +31 -0
- package/ios/RNCCollapsiblePagerViewComponentView.mm +1886 -64
- package/lib/module/CollapsiblePagerView.js +83 -6
- package/lib/module/CollapsiblePagerViewNativeComponent.ts +23 -0
- package/lib/typescript/src/CollapsiblePagerView.d.ts +67 -2
- package/lib/typescript/src/CollapsiblePagerViewNativeComponent.d.ts +22 -1
- package/package.json +3 -2
- package/src/CollapsiblePagerView.tsx +263 -13
- package/src/CollapsiblePagerViewNativeComponent.ts +23 -0
|
@@ -6,11 +6,890 @@
|
|
|
6
6
|
#import <react/renderer/components/pagerview/RCTComponentViewHelpers.h>
|
|
7
7
|
|
|
8
8
|
#import "React/RCTConversions.h"
|
|
9
|
+
#import "React/RCTSurfaceTouchHandler.h"
|
|
10
|
+
#import "React/RCTTouchHandler.h"
|
|
9
11
|
|
|
10
12
|
using namespace facebook::react;
|
|
11
13
|
|
|
12
14
|
static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerContentOffsetContext;
|
|
13
15
|
|
|
16
|
+
typedef void (^RNCCollapsiblePagerNativeTabPressHandler)(NSInteger index, NSString *key);
|
|
17
|
+
|
|
18
|
+
static CGFloat RNCClamp(CGFloat value, CGFloat minimum, CGFloat maximum)
|
|
19
|
+
{
|
|
20
|
+
return MIN(MAX(value, minimum), maximum);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static void RNCCollapsiblePagerLog(NSString *message)
|
|
24
|
+
{
|
|
25
|
+
Class logClass = NSClassFromString(@"ReactNativeNativeLogger.OneKeyLog");
|
|
26
|
+
if (logClass == nil) {
|
|
27
|
+
logClass = NSClassFromString(@"OneKeyLog");
|
|
28
|
+
}
|
|
29
|
+
SEL selector = NSSelectorFromString(@"debug::");
|
|
30
|
+
if (logClass == nil || ![logClass respondsToSelector:selector]) return;
|
|
31
|
+
|
|
32
|
+
typedef void (*LogFunction)(id, SEL, NSString *, NSString *);
|
|
33
|
+
LogFunction logFunction = (LogFunction)[logClass methodForSelector:selector];
|
|
34
|
+
logFunction(logClass, selector, @"CollapsiblePager", message);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static BOOL RNCGetColorComponents(UIColor *color, UITraitCollection *traits,
|
|
38
|
+
CGFloat *red, CGFloat *green, CGFloat *blue, CGFloat *alpha)
|
|
39
|
+
{
|
|
40
|
+
UIColor *resolved = [color resolvedColorWithTraitCollection:traits];
|
|
41
|
+
if ([resolved getRed:red green:green blue:blue alpha:alpha]) return YES;
|
|
42
|
+
CGFloat white = 0;
|
|
43
|
+
if ([resolved getWhite:&white alpha:alpha]) {
|
|
44
|
+
*red = white;
|
|
45
|
+
*green = white;
|
|
46
|
+
*blue = white;
|
|
47
|
+
return YES;
|
|
48
|
+
}
|
|
49
|
+
return NO;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static UIColor *RNCInterpolateColor(UIColor *from, UIColor *to, CGFloat progress,
|
|
53
|
+
UITraitCollection *traits)
|
|
54
|
+
{
|
|
55
|
+
CGFloat fromRed = 0, fromGreen = 0, fromBlue = 0, fromAlpha = 1;
|
|
56
|
+
CGFloat toRed = 0, toGreen = 0, toBlue = 0, toAlpha = 1;
|
|
57
|
+
if (!RNCGetColorComponents(from, traits, &fromRed, &fromGreen, &fromBlue, &fromAlpha) ||
|
|
58
|
+
!RNCGetColorComponents(to, traits, &toRed, &toGreen, &toBlue, &toAlpha)) {
|
|
59
|
+
return progress >= 0.5 ? to : from;
|
|
60
|
+
}
|
|
61
|
+
CGFloat clamped = RNCClamp(progress, 0, 1);
|
|
62
|
+
return [UIColor colorWithRed:fromRed + (toRed - fromRed) * clamped
|
|
63
|
+
green:fromGreen + (toGreen - fromGreen) * clamped
|
|
64
|
+
blue:fromBlue + (toBlue - fromBlue) * clamped
|
|
65
|
+
alpha:fromAlpha + (toAlpha - fromAlpha) * clamped];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@interface RNCCollapsiblePagerHorizontalScrollView : UIScrollView
|
|
69
|
+
@property (nonatomic, copy) NSString *axisOwner;
|
|
70
|
+
@end
|
|
71
|
+
|
|
72
|
+
@implementation RNCCollapsiblePagerHorizontalScrollView
|
|
73
|
+
|
|
74
|
+
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
|
|
75
|
+
{
|
|
76
|
+
if (gestureRecognizer != self.panGestureRecognizer) {
|
|
77
|
+
return [super gestureRecognizerShouldBegin:gestureRecognizer];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
CGPoint translation = [self.panGestureRecognizer translationInView:self];
|
|
81
|
+
CGPoint velocity = [self.panGestureRecognizer velocityInView:self];
|
|
82
|
+
CGPoint intent = fabs(translation.x) + fabs(translation.y) >= 1
|
|
83
|
+
? translation
|
|
84
|
+
: velocity;
|
|
85
|
+
CGFloat maximumOffset = MAX(0, self.contentSize.width - CGRectGetWidth(self.bounds));
|
|
86
|
+
BOOL hasHorizontalRange = maximumOffset > 0;
|
|
87
|
+
BOOL horizontal = MAX(fabs(intent.x), fabs(intent.y)) >= 1 &&
|
|
88
|
+
fabs(intent.x) > fabs(intent.y);
|
|
89
|
+
BOOL shouldBegin = hasHorizontalRange && horizontal &&
|
|
90
|
+
[super gestureRecognizerShouldBegin:gestureRecognizer];
|
|
91
|
+
RNCCollapsiblePagerLog([NSString stringWithFormat:
|
|
92
|
+
@"native-tab-axis owner=%@ dx=%.2f dy=%.2f max=%.2f result=%d",
|
|
93
|
+
_axisOwner ?: @"unknown",
|
|
94
|
+
intent.x,
|
|
95
|
+
intent.y,
|
|
96
|
+
maximumOffset,
|
|
97
|
+
shouldBegin]);
|
|
98
|
+
return shouldBegin;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@end
|
|
102
|
+
|
|
103
|
+
static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
104
|
+
NSString *phase,
|
|
105
|
+
UIScrollView *scrollView,
|
|
106
|
+
CGFloat startOffsetX)
|
|
107
|
+
{
|
|
108
|
+
NSInteger listState = -1;
|
|
109
|
+
NSInteger pagerState = -1;
|
|
110
|
+
for (UIView *ancestor = scrollView.superview;
|
|
111
|
+
ancestor != nil;
|
|
112
|
+
ancestor = ancestor.superview) {
|
|
113
|
+
if (![ancestor isKindOfClass:UIScrollView.class]) continue;
|
|
114
|
+
UIScrollView *ancestorScrollView = (UIScrollView *)ancestor;
|
|
115
|
+
if (ancestorScrollView.pagingEnabled) {
|
|
116
|
+
pagerState = ancestorScrollView.panGestureRecognizer.state;
|
|
117
|
+
} else if (listState < 0 &&
|
|
118
|
+
(ancestorScrollView.alwaysBounceVertical ||
|
|
119
|
+
ancestorScrollView.contentSize.height >
|
|
120
|
+
CGRectGetHeight(ancestorScrollView.bounds))) {
|
|
121
|
+
listState = ancestorScrollView.panGestureRecognizer.state;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
RNCCollapsiblePagerLog([NSString stringWithFormat:
|
|
125
|
+
@"native-tab-scroll owner=%@ phase=%@ x-start=%.2f x-current=%.2f state=%ld list-state=%ld pager-state=%ld",
|
|
126
|
+
owner,
|
|
127
|
+
phase,
|
|
128
|
+
startOffsetX,
|
|
129
|
+
scrollView.contentOffset.x,
|
|
130
|
+
(long)scrollView.panGestureRecognizer.state,
|
|
131
|
+
(long)listState,
|
|
132
|
+
(long)pagerState]);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@interface RNCCollapsiblePagerNativeTabBarView : UIView <UIScrollViewDelegate>
|
|
136
|
+
@property (nonatomic, copy) RNCCollapsiblePagerNativeTabPressHandler onTabPress;
|
|
137
|
+
- (void)updateItemsJSON:(NSString *)itemsJSON;
|
|
138
|
+
- (void)updateStyleWithHeight:(CGFloat)height
|
|
139
|
+
contentPaddingHorizontal:(CGFloat)contentPaddingHorizontal
|
|
140
|
+
itemSpacing:(CGFloat)itemSpacing
|
|
141
|
+
fontSize:(CGFloat)fontSize
|
|
142
|
+
fontFamily:(NSString *)fontFamily
|
|
143
|
+
backgroundColor:(UIColor *)backgroundColor
|
|
144
|
+
activeTextColor:(UIColor *)activeTextColor
|
|
145
|
+
inactiveTextColor:(UIColor *)inactiveTextColor
|
|
146
|
+
indicatorColor:(UIColor *)indicatorColor
|
|
147
|
+
indicatorHeight:(CGFloat)indicatorHeight
|
|
148
|
+
indicatorBottom:(CGFloat)indicatorBottom;
|
|
149
|
+
- (void)setProgress:(CGFloat)progress;
|
|
150
|
+
- (void)setLeftToRight:(BOOL)leftToRight;
|
|
151
|
+
- (void)updateAncestorGesturePrecedence;
|
|
152
|
+
@end
|
|
153
|
+
|
|
154
|
+
@implementation RNCCollapsiblePagerNativeTabBarView {
|
|
155
|
+
RNCCollapsiblePagerHorizontalScrollView *_scrollView;
|
|
156
|
+
UIView *_indicatorView;
|
|
157
|
+
NSArray<NSDictionary *> *_items;
|
|
158
|
+
NSMutableArray<UIButton *> *_buttons;
|
|
159
|
+
NSMutableArray<NSValue *> *_indicatorFrames;
|
|
160
|
+
CGFloat _barHeight;
|
|
161
|
+
CGFloat _contentPaddingHorizontal;
|
|
162
|
+
CGFloat _itemSpacing;
|
|
163
|
+
CGFloat _fontSize;
|
|
164
|
+
NSString *_fontFamily;
|
|
165
|
+
UIColor *_activeTextColor;
|
|
166
|
+
UIColor *_inactiveTextColor;
|
|
167
|
+
UIColor *_indicatorColor;
|
|
168
|
+
CGFloat _indicatorHeight;
|
|
169
|
+
CGFloat _indicatorBottom;
|
|
170
|
+
CGFloat _progress;
|
|
171
|
+
BOOL _leftToRight;
|
|
172
|
+
BOOL _shouldCenterSelectedItem;
|
|
173
|
+
CGFloat _scrollStartOffsetX;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
- (instancetype)initWithFrame:(CGRect)frame
|
|
177
|
+
{
|
|
178
|
+
if (self = [super initWithFrame:frame]) {
|
|
179
|
+
_items = @[];
|
|
180
|
+
_buttons = [NSMutableArray new];
|
|
181
|
+
_indicatorFrames = [NSMutableArray new];
|
|
182
|
+
_barHeight = 44;
|
|
183
|
+
_contentPaddingHorizontal = 20;
|
|
184
|
+
_itemSpacing = 8;
|
|
185
|
+
_fontSize = 16;
|
|
186
|
+
_activeTextColor = UIColor.labelColor;
|
|
187
|
+
_inactiveTextColor = UIColor.secondaryLabelColor;
|
|
188
|
+
_indicatorColor = UIColor.labelColor;
|
|
189
|
+
_indicatorHeight = 2;
|
|
190
|
+
_indicatorBottom = 0;
|
|
191
|
+
_leftToRight = YES;
|
|
192
|
+
_shouldCenterSelectedItem = YES;
|
|
193
|
+
|
|
194
|
+
_scrollView = [RNCCollapsiblePagerHorizontalScrollView new];
|
|
195
|
+
_scrollView.axisOwner = @"primary";
|
|
196
|
+
_scrollView.showsHorizontalScrollIndicator = NO;
|
|
197
|
+
_scrollView.showsVerticalScrollIndicator = NO;
|
|
198
|
+
_scrollView.alwaysBounceHorizontal = YES;
|
|
199
|
+
_scrollView.directionalLockEnabled = YES;
|
|
200
|
+
_scrollView.delaysContentTouches = NO;
|
|
201
|
+
_scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
|
202
|
+
_scrollView.delegate = self;
|
|
203
|
+
[self addSubview:_scrollView];
|
|
204
|
+
|
|
205
|
+
_indicatorView = [UIView new];
|
|
206
|
+
_indicatorView.userInteractionEnabled = NO;
|
|
207
|
+
[_scrollView addSubview:_indicatorView];
|
|
208
|
+
}
|
|
209
|
+
return self;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
- (UIFont *)tabFont
|
|
213
|
+
{
|
|
214
|
+
if (_fontFamily.length > 0) {
|
|
215
|
+
UIFont *font = [UIFont fontWithName:_fontFamily size:_fontSize];
|
|
216
|
+
if (font != nil) return font;
|
|
217
|
+
}
|
|
218
|
+
return [UIFont systemFontOfSize:_fontSize weight:UIFontWeightMedium];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
- (void)updateItemsJSON:(NSString *)itemsJSON
|
|
222
|
+
{
|
|
223
|
+
NSData *data = [itemsJSON dataUsingEncoding:NSUTF8StringEncoding];
|
|
224
|
+
id parsed = data == nil ? nil : [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
|
|
225
|
+
NSArray *items = [parsed isKindOfClass:NSArray.class] ? parsed : @[];
|
|
226
|
+
if ([_items isEqualToArray:items]) return;
|
|
227
|
+
_items = items;
|
|
228
|
+
|
|
229
|
+
for (UIButton *button in _buttons) [button removeFromSuperview];
|
|
230
|
+
[_buttons removeAllObjects];
|
|
231
|
+
[_indicatorFrames removeAllObjects];
|
|
232
|
+
|
|
233
|
+
[_items enumerateObjectsUsingBlock:^(NSDictionary *item, NSUInteger index, BOOL *stop) {
|
|
234
|
+
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
|
|
235
|
+
button.tag = (NSInteger)index;
|
|
236
|
+
button.titleLabel.font = self.tabFont;
|
|
237
|
+
button.titleLabel.lineBreakMode = NSLineBreakByTruncatingTail;
|
|
238
|
+
NSString *title = [item[@"title"] isKindOfClass:NSString.class] ? item[@"title"] : @"";
|
|
239
|
+
[button setTitle:title forState:UIControlStateNormal];
|
|
240
|
+
[button addTarget:self action:@selector(handleTabPress:) forControlEvents:UIControlEventTouchUpInside];
|
|
241
|
+
NSString *accessibilityLabel = [item[@"accessibilityLabel"] isKindOfClass:NSString.class]
|
|
242
|
+
? item[@"accessibilityLabel"]
|
|
243
|
+
: title;
|
|
244
|
+
button.accessibilityLabel = accessibilityLabel;
|
|
245
|
+
NSString *testID = [item[@"testID"] isKindOfClass:NSString.class] ? item[@"testID"] : nil;
|
|
246
|
+
button.accessibilityIdentifier = testID;
|
|
247
|
+
[self->_scrollView addSubview:button];
|
|
248
|
+
[self->_buttons addObject:button];
|
|
249
|
+
}];
|
|
250
|
+
|
|
251
|
+
[_scrollView bringSubviewToFront:_indicatorView];
|
|
252
|
+
for (UIButton *button in _buttons) [_scrollView bringSubviewToFront:button];
|
|
253
|
+
_shouldCenterSelectedItem = YES;
|
|
254
|
+
self.hidden = _items.count == 0;
|
|
255
|
+
[self setNeedsLayout];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
- (void)updateStyleWithHeight:(CGFloat)height
|
|
259
|
+
contentPaddingHorizontal:(CGFloat)contentPaddingHorizontal
|
|
260
|
+
itemSpacing:(CGFloat)itemSpacing
|
|
261
|
+
fontSize:(CGFloat)fontSize
|
|
262
|
+
fontFamily:(NSString *)fontFamily
|
|
263
|
+
backgroundColor:(UIColor *)backgroundColor
|
|
264
|
+
activeTextColor:(UIColor *)activeTextColor
|
|
265
|
+
inactiveTextColor:(UIColor *)inactiveTextColor
|
|
266
|
+
indicatorColor:(UIColor *)indicatorColor
|
|
267
|
+
indicatorHeight:(CGFloat)indicatorHeight
|
|
268
|
+
indicatorBottom:(CGFloat)indicatorBottom
|
|
269
|
+
{
|
|
270
|
+
_barHeight = MAX(1, height);
|
|
271
|
+
_contentPaddingHorizontal = MAX(0, contentPaddingHorizontal);
|
|
272
|
+
_itemSpacing = MAX(0, itemSpacing);
|
|
273
|
+
_fontSize = MAX(1, fontSize);
|
|
274
|
+
_fontFamily = [fontFamily copy];
|
|
275
|
+
self.backgroundColor = backgroundColor ?: UIColor.clearColor;
|
|
276
|
+
_scrollView.backgroundColor = self.backgroundColor;
|
|
277
|
+
_activeTextColor = activeTextColor ?: UIColor.labelColor;
|
|
278
|
+
_inactiveTextColor = inactiveTextColor ?: UIColor.secondaryLabelColor;
|
|
279
|
+
_indicatorColor = indicatorColor ?: _activeTextColor;
|
|
280
|
+
_indicatorHeight = MAX(0, indicatorHeight);
|
|
281
|
+
_indicatorBottom = MAX(0, indicatorBottom);
|
|
282
|
+
_indicatorView.backgroundColor = _indicatorColor;
|
|
283
|
+
_indicatorView.layer.cornerRadius = _indicatorHeight / 2;
|
|
284
|
+
for (UIButton *button in _buttons) button.titleLabel.font = self.tabFont;
|
|
285
|
+
[self setNeedsLayout];
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
- (void)setLeftToRight:(BOOL)leftToRight
|
|
289
|
+
{
|
|
290
|
+
if (_leftToRight == leftToRight) return;
|
|
291
|
+
_leftToRight = leftToRight;
|
|
292
|
+
_shouldCenterSelectedItem = YES;
|
|
293
|
+
[self setNeedsLayout];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
- (void)updateAncestorGesturePrecedence
|
|
297
|
+
{
|
|
298
|
+
for (UIView *ancestor = self.superview; ancestor != nil; ancestor = ancestor.superview) {
|
|
299
|
+
if ([ancestor isKindOfClass:UIScrollView.class]) {
|
|
300
|
+
UIScrollView *scrollView = (UIScrollView *)ancestor;
|
|
301
|
+
if (scrollView.pagingEnabled || scrollView.alwaysBounceHorizontal) {
|
|
302
|
+
[scrollView.panGestureRecognizer
|
|
303
|
+
requireGestureRecognizerToFail:_scrollView.panGestureRecognizer];
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
- (void)handleTabPress:(UIButton *)button
|
|
310
|
+
{
|
|
311
|
+
NSInteger index = button.tag;
|
|
312
|
+
if (index < 0 || index >= (NSInteger)_items.count) return;
|
|
313
|
+
NSDictionary *item = _items[index];
|
|
314
|
+
NSString *key = [item[@"key"] isKindOfClass:NSString.class] ? item[@"key"] : @"";
|
|
315
|
+
_shouldCenterSelectedItem = YES;
|
|
316
|
+
if (self.onTabPress) self.onTabPress(index, key);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
|
|
320
|
+
{
|
|
321
|
+
_shouldCenterSelectedItem = NO;
|
|
322
|
+
_scrollStartOffsetX = scrollView.contentOffset.x;
|
|
323
|
+
RNCLogNativeTabScrollBoundary(@"primary", @"begin", scrollView, _scrollStartOffsetX);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView
|
|
327
|
+
willDecelerate:(BOOL)decelerate
|
|
328
|
+
{
|
|
329
|
+
if (decelerate) return;
|
|
330
|
+
RNCLogNativeTabScrollBoundary(@"primary", @"end", scrollView, _scrollStartOffsetX);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
|
|
334
|
+
{
|
|
335
|
+
RNCLogNativeTabScrollBoundary(@"primary", @"end", scrollView, _scrollStartOffsetX);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
- (void)layoutSubviews
|
|
339
|
+
{
|
|
340
|
+
[super layoutSubviews];
|
|
341
|
+
_scrollView.frame = self.bounds;
|
|
342
|
+
UIFont *font = self.tabFont;
|
|
343
|
+
NSMutableArray<NSNumber *> *widths = [NSMutableArray arrayWithCapacity:_buttons.count];
|
|
344
|
+
CGFloat itemsWidth = 0;
|
|
345
|
+
for (UIButton *button in _buttons) {
|
|
346
|
+
NSString *title = [button titleForState:UIControlStateNormal] ?: @"";
|
|
347
|
+
CGFloat textWidth = ceil([title sizeWithAttributes:@{NSFontAttributeName: font}].width);
|
|
348
|
+
CGFloat buttonWidth = MAX(44, textWidth + 16);
|
|
349
|
+
[widths addObject:@(buttonWidth)];
|
|
350
|
+
itemsWidth += buttonWidth;
|
|
351
|
+
}
|
|
352
|
+
if (_buttons.count > 1) itemsWidth += (_buttons.count - 1) * _itemSpacing;
|
|
353
|
+
CGFloat contentWidth = MAX(CGRectGetWidth(self.bounds),
|
|
354
|
+
itemsWidth + _contentPaddingHorizontal * 2);
|
|
355
|
+
_scrollView.contentSize = CGSizeMake(contentWidth, MAX(_barHeight, CGRectGetHeight(self.bounds)));
|
|
356
|
+
[_indicatorFrames removeAllObjects];
|
|
357
|
+
|
|
358
|
+
__block CGFloat x = _leftToRight
|
|
359
|
+
? _contentPaddingHorizontal
|
|
360
|
+
: contentWidth - _contentPaddingHorizontal;
|
|
361
|
+
[_buttons enumerateObjectsUsingBlock:^(UIButton *button, NSUInteger index, BOOL *stop) {
|
|
362
|
+
CGFloat buttonWidth = widths[index].doubleValue;
|
|
363
|
+
if (!self->_leftToRight) x -= buttonWidth;
|
|
364
|
+
button.frame = CGRectMake(x, 0, buttonWidth, self->_barHeight);
|
|
365
|
+
NSString *title = [button titleForState:UIControlStateNormal] ?: @"";
|
|
366
|
+
CGFloat textWidth = ceil([title sizeWithAttributes:@{NSFontAttributeName: font}].width);
|
|
367
|
+
CGRect indicatorFrame = CGRectMake(
|
|
368
|
+
CGRectGetMidX(button.frame) - textWidth / 2,
|
|
369
|
+
self->_barHeight - self->_indicatorBottom - self->_indicatorHeight,
|
|
370
|
+
textWidth,
|
|
371
|
+
self->_indicatorHeight
|
|
372
|
+
);
|
|
373
|
+
[self->_indicatorFrames addObject:[NSValue valueWithCGRect:indicatorFrame]];
|
|
374
|
+
if (self->_leftToRight) {
|
|
375
|
+
x += buttonWidth + self->_itemSpacing;
|
|
376
|
+
} else {
|
|
377
|
+
x -= self->_itemSpacing;
|
|
378
|
+
}
|
|
379
|
+
}];
|
|
380
|
+
[self updatePresentation];
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
- (void)setProgress:(CGFloat)progress
|
|
384
|
+
{
|
|
385
|
+
if (_items.count == 0) return;
|
|
386
|
+
if (fabs(_progress - progress) >= 0.001) _shouldCenterSelectedItem = YES;
|
|
387
|
+
_progress = RNCClamp(progress, 0, _items.count - 1);
|
|
388
|
+
if (_indicatorFrames.count == _items.count) [self updatePresentation];
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
- (void)updatePresentation
|
|
392
|
+
{
|
|
393
|
+
NSInteger count = _buttons.count;
|
|
394
|
+
if (count == 0 || _indicatorFrames.count != count) return;
|
|
395
|
+
CGFloat progress = RNCClamp(_progress, 0, count - 1);
|
|
396
|
+
NSInteger lower = (NSInteger)floor(progress);
|
|
397
|
+
NSInteger upper = MIN(lower + 1, count - 1);
|
|
398
|
+
CGFloat fraction = progress - lower;
|
|
399
|
+
CGRect fromFrame = _indicatorFrames[lower].CGRectValue;
|
|
400
|
+
CGRect toFrame = _indicatorFrames[upper].CGRectValue;
|
|
401
|
+
CGRect indicatorFrame = CGRectMake(
|
|
402
|
+
CGRectGetMinX(fromFrame) + (CGRectGetMinX(toFrame) - CGRectGetMinX(fromFrame)) * fraction,
|
|
403
|
+
CGRectGetMinY(fromFrame),
|
|
404
|
+
CGRectGetWidth(fromFrame) + (CGRectGetWidth(toFrame) - CGRectGetWidth(fromFrame)) * fraction,
|
|
405
|
+
_indicatorHeight
|
|
406
|
+
);
|
|
407
|
+
_indicatorView.frame = indicatorFrame;
|
|
408
|
+
_indicatorView.hidden = _indicatorHeight <= 0;
|
|
409
|
+
|
|
410
|
+
NSInteger selectedIndex = (NSInteger)round(progress);
|
|
411
|
+
[_buttons enumerateObjectsUsingBlock:^(UIButton *button, NSUInteger index, BOOL *stop) {
|
|
412
|
+
CGFloat emphasis = 1 - MIN(1, fabs(progress - index));
|
|
413
|
+
UIColor *color = RNCInterpolateColor(
|
|
414
|
+
self->_inactiveTextColor,
|
|
415
|
+
self->_activeTextColor,
|
|
416
|
+
emphasis,
|
|
417
|
+
self.traitCollection
|
|
418
|
+
);
|
|
419
|
+
[button setTitleColor:color forState:UIControlStateNormal];
|
|
420
|
+
button.accessibilityTraits = index == selectedIndex
|
|
421
|
+
? UIAccessibilityTraitButton | UIAccessibilityTraitSelected
|
|
422
|
+
: UIAccessibilityTraitButton;
|
|
423
|
+
}];
|
|
424
|
+
|
|
425
|
+
if (_shouldCenterSelectedItem && !_scrollView.tracking && !_scrollView.dragging &&
|
|
426
|
+
!_scrollView.decelerating && CGRectGetWidth(_scrollView.bounds) > 0) {
|
|
427
|
+
CGFloat maximumOffset = MAX(0, _scrollView.contentSize.width - CGRectGetWidth(_scrollView.bounds));
|
|
428
|
+
CGFloat desiredOffset = RNCClamp(
|
|
429
|
+
CGRectGetMidX(indicatorFrame) - CGRectGetWidth(_scrollView.bounds) / 2,
|
|
430
|
+
0,
|
|
431
|
+
maximumOffset
|
|
432
|
+
);
|
|
433
|
+
[_scrollView setContentOffset:CGPointMake(desiredOffset, 0) animated:NO];
|
|
434
|
+
_shouldCenterSelectedItem = NO;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
@end
|
|
439
|
+
|
|
440
|
+
@interface RNCCollapsiblePagerNativeSubHeaderView : UIView <UIScrollViewDelegate>
|
|
441
|
+
@property (nonatomic, copy) RNCCollapsiblePagerNativeTabPressHandler onItemPress;
|
|
442
|
+
@property (nonatomic, readonly) CGFloat preferredHeight;
|
|
443
|
+
- (void)updateConfigJSON:(NSString *)configJSON;
|
|
444
|
+
- (void)updateColorsWithBackgroundColor:(UIColor *)backgroundColor
|
|
445
|
+
activeTextColor:(UIColor *)activeTextColor
|
|
446
|
+
inactiveTextColor:(UIColor *)inactiveTextColor
|
|
447
|
+
selectedBackgroundColor:(UIColor *)selectedBackgroundColor
|
|
448
|
+
fontFamily:(NSString *)fontFamily;
|
|
449
|
+
- (void)setLeftToRight:(BOOL)leftToRight;
|
|
450
|
+
- (void)updateAncestorGesturePrecedence;
|
|
451
|
+
@end
|
|
452
|
+
|
|
453
|
+
@implementation RNCCollapsiblePagerNativeSubHeaderView {
|
|
454
|
+
RNCCollapsiblePagerHorizontalScrollView *_tabsScrollView;
|
|
455
|
+
UIView *_columnsView;
|
|
456
|
+
UILabel *_leadingColumnLabel;
|
|
457
|
+
UILabel *_middleColumnLabel;
|
|
458
|
+
UILabel *_trailingColumnLabel;
|
|
459
|
+
NSArray<NSDictionary *> *_items;
|
|
460
|
+
NSMutableArray<UIButton *> *_buttons;
|
|
461
|
+
NSString *_selectedKey;
|
|
462
|
+
CGFloat _configuredHeight;
|
|
463
|
+
CGFloat _tabsHeight;
|
|
464
|
+
CGFloat _contentPaddingHorizontal;
|
|
465
|
+
CGFloat _itemSpacing;
|
|
466
|
+
CGFloat _fontSize;
|
|
467
|
+
CGFloat _columnFontSize;
|
|
468
|
+
CGFloat _trailingColumnWidth;
|
|
469
|
+
CGFloat _columnGap;
|
|
470
|
+
NSString *_fontFamily;
|
|
471
|
+
UIColor *_activeTextColor;
|
|
472
|
+
UIColor *_inactiveTextColor;
|
|
473
|
+
UIColor *_selectedBackgroundColor;
|
|
474
|
+
BOOL _leftToRight;
|
|
475
|
+
BOOL _shouldCenterSelectedItem;
|
|
476
|
+
CGFloat _scrollStartOffsetX;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
- (instancetype)initWithFrame:(CGRect)frame
|
|
480
|
+
{
|
|
481
|
+
if (self = [super initWithFrame:frame]) {
|
|
482
|
+
_items = @[];
|
|
483
|
+
_buttons = [NSMutableArray new];
|
|
484
|
+
_selectedKey = @"";
|
|
485
|
+
_configuredHeight = 74;
|
|
486
|
+
_tabsHeight = 42;
|
|
487
|
+
_contentPaddingHorizontal = 20;
|
|
488
|
+
_itemSpacing = 8;
|
|
489
|
+
_fontSize = 14;
|
|
490
|
+
_columnFontSize = 12;
|
|
491
|
+
_trailingColumnWidth = 80;
|
|
492
|
+
_columnGap = 8;
|
|
493
|
+
_activeTextColor = UIColor.labelColor;
|
|
494
|
+
_inactiveTextColor = UIColor.secondaryLabelColor;
|
|
495
|
+
_selectedBackgroundColor = UIColor.secondarySystemFillColor;
|
|
496
|
+
_leftToRight = YES;
|
|
497
|
+
_shouldCenterSelectedItem = YES;
|
|
498
|
+
|
|
499
|
+
_tabsScrollView = [RNCCollapsiblePagerHorizontalScrollView new];
|
|
500
|
+
_tabsScrollView.axisOwner = @"secondary";
|
|
501
|
+
_tabsScrollView.showsHorizontalScrollIndicator = NO;
|
|
502
|
+
_tabsScrollView.showsVerticalScrollIndicator = NO;
|
|
503
|
+
_tabsScrollView.alwaysBounceHorizontal = YES;
|
|
504
|
+
_tabsScrollView.directionalLockEnabled = YES;
|
|
505
|
+
_tabsScrollView.delaysContentTouches = NO;
|
|
506
|
+
_tabsScrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
|
507
|
+
_tabsScrollView.delegate = self;
|
|
508
|
+
[self addSubview:_tabsScrollView];
|
|
509
|
+
|
|
510
|
+
_columnsView = [UIView new];
|
|
511
|
+
[self addSubview:_columnsView];
|
|
512
|
+
_leadingColumnLabel = [UILabel new];
|
|
513
|
+
_middleColumnLabel = [UILabel new];
|
|
514
|
+
_trailingColumnLabel = [UILabel new];
|
|
515
|
+
for (UILabel *label in @[_leadingColumnLabel, _middleColumnLabel, _trailingColumnLabel]) {
|
|
516
|
+
label.numberOfLines = 1;
|
|
517
|
+
label.lineBreakMode = NSLineBreakByTruncatingTail;
|
|
518
|
+
[_columnsView addSubview:label];
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return self;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
- (CGFloat)preferredHeight
|
|
525
|
+
{
|
|
526
|
+
return _configuredHeight;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
- (void)didMoveToWindow
|
|
530
|
+
{
|
|
531
|
+
[super didMoveToWindow];
|
|
532
|
+
if (self.window == nil) return;
|
|
533
|
+
|
|
534
|
+
[self updateAncestorGesturePrecedence];
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
- (void)updateAncestorGesturePrecedence
|
|
538
|
+
{
|
|
539
|
+
|
|
540
|
+
// Keep this nested horizontal scroller ahead of any ancestor pager. Without
|
|
541
|
+
// this precedence, the Discovery pager can consume the category drag.
|
|
542
|
+
for (UIView *ancestor = self.superview; ancestor != nil; ancestor = ancestor.superview) {
|
|
543
|
+
if ([ancestor isKindOfClass:UIScrollView.class]) {
|
|
544
|
+
UIScrollView *scrollView = (UIScrollView *)ancestor;
|
|
545
|
+
if (scrollView.pagingEnabled || scrollView.alwaysBounceHorizontal) {
|
|
546
|
+
[scrollView.panGestureRecognizer
|
|
547
|
+
requireGestureRecognizerToFail:_tabsScrollView.panGestureRecognizer];
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
|
|
554
|
+
{
|
|
555
|
+
_shouldCenterSelectedItem = NO;
|
|
556
|
+
_scrollStartOffsetX = scrollView.contentOffset.x;
|
|
557
|
+
RNCLogNativeTabScrollBoundary(@"secondary", @"begin", scrollView, _scrollStartOffsetX);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView
|
|
561
|
+
willDecelerate:(BOOL)decelerate
|
|
562
|
+
{
|
|
563
|
+
if (decelerate) return;
|
|
564
|
+
RNCLogNativeTabScrollBoundary(@"secondary", @"end", scrollView, _scrollStartOffsetX);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
|
|
568
|
+
{
|
|
569
|
+
RNCLogNativeTabScrollBoundary(@"secondary", @"end", scrollView, _scrollStartOffsetX);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
- (UIFont *)fontWithSize:(CGFloat)size
|
|
573
|
+
{
|
|
574
|
+
if (_fontFamily.length > 0) {
|
|
575
|
+
UIFont *font = [UIFont fontWithName:_fontFamily size:size];
|
|
576
|
+
if (font != nil) return font;
|
|
577
|
+
}
|
|
578
|
+
return [UIFont systemFontOfSize:size weight:UIFontWeightMedium];
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
- (void)updateConfigJSON:(NSString *)configJSON
|
|
582
|
+
{
|
|
583
|
+
NSData *data = [configJSON dataUsingEncoding:NSUTF8StringEncoding];
|
|
584
|
+
id parsed = data == nil ? nil : [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
|
|
585
|
+
NSDictionary *config = [parsed isKindOfClass:NSDictionary.class] ? parsed : @{};
|
|
586
|
+
NSArray *items = [config[@"items"] isKindOfClass:NSArray.class] ? config[@"items"] : @[];
|
|
587
|
+
NSString *selectedKey = [config[@"selectedKey"] isKindOfClass:NSString.class]
|
|
588
|
+
? config[@"selectedKey"]
|
|
589
|
+
: @"";
|
|
590
|
+
NSDictionary *columns = [config[@"columns"] isKindOfClass:NSDictionary.class]
|
|
591
|
+
? config[@"columns"]
|
|
592
|
+
: @{};
|
|
593
|
+
NSDictionary *style = [config[@"style"] isKindOfClass:NSDictionary.class]
|
|
594
|
+
? config[@"style"]
|
|
595
|
+
: @{};
|
|
596
|
+
|
|
597
|
+
BOOL itemsChanged = ![_items isEqualToArray:items];
|
|
598
|
+
BOOL selectedKeyChanged = ![_selectedKey isEqualToString:selectedKey];
|
|
599
|
+
_items = items;
|
|
600
|
+
_selectedKey = [selectedKey copy];
|
|
601
|
+
if (itemsChanged || selectedKeyChanged) _shouldCenterSelectedItem = YES;
|
|
602
|
+
_configuredHeight = MAX(1, [style[@"height"] doubleValue] ?: 74);
|
|
603
|
+
_tabsHeight = MAX(0, [style[@"tabsHeight"] doubleValue] ?: 42);
|
|
604
|
+
_contentPaddingHorizontal = MAX(
|
|
605
|
+
0,
|
|
606
|
+
[style[@"contentPaddingHorizontal"] doubleValue] ?: 20
|
|
607
|
+
);
|
|
608
|
+
_itemSpacing = MAX(0, [style[@"itemSpacing"] doubleValue] ?: 8);
|
|
609
|
+
_fontSize = MAX(1, [style[@"fontSize"] doubleValue] ?: 14);
|
|
610
|
+
_columnFontSize = MAX(1, [style[@"columnFontSize"] doubleValue] ?: 12);
|
|
611
|
+
_trailingColumnWidth = MAX(0, [style[@"trailingColumnWidth"] doubleValue] ?: 80);
|
|
612
|
+
_columnGap = MAX(0, [style[@"columnGap"] doubleValue] ?: 8);
|
|
613
|
+
|
|
614
|
+
_leadingColumnLabel.text = [columns[@"leading"] isKindOfClass:NSString.class]
|
|
615
|
+
? columns[@"leading"]
|
|
616
|
+
: @"";
|
|
617
|
+
_middleColumnLabel.text = [columns[@"middle"] isKindOfClass:NSString.class]
|
|
618
|
+
? columns[@"middle"]
|
|
619
|
+
: @"";
|
|
620
|
+
_trailingColumnLabel.text = [columns[@"trailing"] isKindOfClass:NSString.class]
|
|
621
|
+
? columns[@"trailing"]
|
|
622
|
+
: @"";
|
|
623
|
+
|
|
624
|
+
if (itemsChanged) {
|
|
625
|
+
for (UIButton *button in _buttons) [button removeFromSuperview];
|
|
626
|
+
[_buttons removeAllObjects];
|
|
627
|
+
[_items enumerateObjectsUsingBlock:^(NSDictionary *item, NSUInteger index, BOOL *stop) {
|
|
628
|
+
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
|
|
629
|
+
button.tag = (NSInteger)index;
|
|
630
|
+
button.titleLabel.lineBreakMode = NSLineBreakByTruncatingTail;
|
|
631
|
+
NSString *title = [item[@"title"] isKindOfClass:NSString.class] ? item[@"title"] : @"";
|
|
632
|
+
[button setTitle:title forState:UIControlStateNormal];
|
|
633
|
+
[button addTarget:self action:@selector(handleItemPress:) forControlEvents:UIControlEventTouchUpInside];
|
|
634
|
+
button.accessibilityLabel = [item[@"accessibilityLabel"] isKindOfClass:NSString.class]
|
|
635
|
+
? item[@"accessibilityLabel"]
|
|
636
|
+
: title;
|
|
637
|
+
button.accessibilityIdentifier = [item[@"testID"] isKindOfClass:NSString.class]
|
|
638
|
+
? item[@"testID"]
|
|
639
|
+
: nil;
|
|
640
|
+
[self->_tabsScrollView addSubview:button];
|
|
641
|
+
[self->_buttons addObject:button];
|
|
642
|
+
}];
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
self.hidden = _items.count == 0;
|
|
646
|
+
[self setNeedsLayout];
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
- (void)updateColorsWithBackgroundColor:(UIColor *)backgroundColor
|
|
650
|
+
activeTextColor:(UIColor *)activeTextColor
|
|
651
|
+
inactiveTextColor:(UIColor *)inactiveTextColor
|
|
652
|
+
selectedBackgroundColor:(UIColor *)selectedBackgroundColor
|
|
653
|
+
fontFamily:(NSString *)fontFamily
|
|
654
|
+
{
|
|
655
|
+
self.backgroundColor = backgroundColor ?: UIColor.clearColor;
|
|
656
|
+
_tabsScrollView.backgroundColor = self.backgroundColor;
|
|
657
|
+
_columnsView.backgroundColor = self.backgroundColor;
|
|
658
|
+
_activeTextColor = activeTextColor ?: UIColor.labelColor;
|
|
659
|
+
_inactiveTextColor = inactiveTextColor ?: UIColor.secondaryLabelColor;
|
|
660
|
+
_selectedBackgroundColor = selectedBackgroundColor ?: UIColor.secondarySystemFillColor;
|
|
661
|
+
_fontFamily = [fontFamily copy];
|
|
662
|
+
[self setNeedsLayout];
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
- (void)setLeftToRight:(BOOL)leftToRight
|
|
666
|
+
{
|
|
667
|
+
if (_leftToRight == leftToRight) return;
|
|
668
|
+
_leftToRight = leftToRight;
|
|
669
|
+
[self setNeedsLayout];
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
- (void)handleItemPress:(UIButton *)button
|
|
673
|
+
{
|
|
674
|
+
NSInteger index = button.tag;
|
|
675
|
+
if (index < 0 || index >= (NSInteger)_items.count) return;
|
|
676
|
+
NSDictionary *item = _items[index];
|
|
677
|
+
NSString *key = [item[@"key"] isKindOfClass:NSString.class] ? item[@"key"] : @"";
|
|
678
|
+
if (self.onItemPress) self.onItemPress(index, key);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
- (void)layoutSubviews
|
|
682
|
+
{
|
|
683
|
+
[super layoutSubviews];
|
|
684
|
+
CGFloat width = CGRectGetWidth(self.bounds);
|
|
685
|
+
CGFloat height = CGRectGetHeight(self.bounds);
|
|
686
|
+
CGFloat tabsHeight = MIN(height, _tabsHeight);
|
|
687
|
+
_tabsScrollView.frame = CGRectMake(0, 0, width, tabsHeight);
|
|
688
|
+
_columnsView.frame = CGRectMake(0, tabsHeight, width, MAX(0, height - tabsHeight));
|
|
689
|
+
|
|
690
|
+
UIFont *itemFont = [self fontWithSize:_fontSize];
|
|
691
|
+
CGFloat itemsWidth = 0;
|
|
692
|
+
NSMutableArray<NSNumber *> *widths = [NSMutableArray arrayWithCapacity:_buttons.count];
|
|
693
|
+
for (UIButton *button in _buttons) {
|
|
694
|
+
button.titleLabel.font = itemFont;
|
|
695
|
+
NSString *title = [button titleForState:UIControlStateNormal] ?: @"";
|
|
696
|
+
CGFloat itemWidth = MAX(44, ceil([title sizeWithAttributes:@{NSFontAttributeName: itemFont}].width) + 20);
|
|
697
|
+
[widths addObject:@(itemWidth)];
|
|
698
|
+
itemsWidth += itemWidth;
|
|
699
|
+
}
|
|
700
|
+
if (_buttons.count > 1) itemsWidth += (_buttons.count - 1) * _itemSpacing;
|
|
701
|
+
CGFloat contentWidth = MAX(width, itemsWidth + _contentPaddingHorizontal * 2);
|
|
702
|
+
_tabsScrollView.contentSize = CGSizeMake(contentWidth, tabsHeight);
|
|
703
|
+
__block CGFloat x = _leftToRight
|
|
704
|
+
? _contentPaddingHorizontal
|
|
705
|
+
: contentWidth - _contentPaddingHorizontal;
|
|
706
|
+
__block UIButton *selectedButton = nil;
|
|
707
|
+
[_buttons enumerateObjectsUsingBlock:^(UIButton *button, NSUInteger index, BOOL *stop) {
|
|
708
|
+
CGFloat itemWidth = widths[index].doubleValue;
|
|
709
|
+
if (!self->_leftToRight) x -= itemWidth;
|
|
710
|
+
button.frame = CGRectMake(x, MAX(0, (tabsHeight - 32) / 2), itemWidth, MIN(32, tabsHeight));
|
|
711
|
+
NSDictionary *item = self->_items[index];
|
|
712
|
+
NSString *key = [item[@"key"] isKindOfClass:NSString.class] ? item[@"key"] : @"";
|
|
713
|
+
BOOL selected = [key isEqualToString:self->_selectedKey];
|
|
714
|
+
button.backgroundColor = selected ? self->_selectedBackgroundColor : UIColor.clearColor;
|
|
715
|
+
button.layer.cornerRadius = 10;
|
|
716
|
+
[button setTitleColor:selected ? self->_activeTextColor : self->_inactiveTextColor
|
|
717
|
+
forState:UIControlStateNormal];
|
|
718
|
+
button.accessibilityTraits = selected
|
|
719
|
+
? UIAccessibilityTraitButton | UIAccessibilityTraitSelected
|
|
720
|
+
: UIAccessibilityTraitButton;
|
|
721
|
+
if (selected) selectedButton = button;
|
|
722
|
+
if (self->_leftToRight) {
|
|
723
|
+
x += itemWidth + self->_itemSpacing;
|
|
724
|
+
} else {
|
|
725
|
+
x -= self->_itemSpacing;
|
|
726
|
+
}
|
|
727
|
+
}];
|
|
728
|
+
|
|
729
|
+
UIFont *columnFont = [self fontWithSize:_columnFontSize];
|
|
730
|
+
for (UILabel *label in @[_leadingColumnLabel, _middleColumnLabel, _trailingColumnLabel]) {
|
|
731
|
+
label.font = columnFont;
|
|
732
|
+
label.textColor = _inactiveTextColor;
|
|
733
|
+
}
|
|
734
|
+
CGFloat columnHeight = CGRectGetHeight(_columnsView.bounds);
|
|
735
|
+
CGFloat half = width / 2;
|
|
736
|
+
CGFloat trailingX = width - _contentPaddingHorizontal - _trailingColumnWidth;
|
|
737
|
+
if (_leftToRight) {
|
|
738
|
+
_leadingColumnLabel.textAlignment = NSTextAlignmentLeft;
|
|
739
|
+
_middleColumnLabel.textAlignment = NSTextAlignmentRight;
|
|
740
|
+
_trailingColumnLabel.textAlignment = NSTextAlignmentRight;
|
|
741
|
+
_leadingColumnLabel.frame = CGRectMake(
|
|
742
|
+
_contentPaddingHorizontal,
|
|
743
|
+
0,
|
|
744
|
+
MAX(0, half - _contentPaddingHorizontal),
|
|
745
|
+
columnHeight
|
|
746
|
+
);
|
|
747
|
+
_middleColumnLabel.frame = CGRectMake(
|
|
748
|
+
half,
|
|
749
|
+
0,
|
|
750
|
+
MAX(0, trailingX - _columnGap - half),
|
|
751
|
+
columnHeight
|
|
752
|
+
);
|
|
753
|
+
_trailingColumnLabel.frame = CGRectMake(
|
|
754
|
+
trailingX,
|
|
755
|
+
0,
|
|
756
|
+
_trailingColumnWidth,
|
|
757
|
+
columnHeight
|
|
758
|
+
);
|
|
759
|
+
} else {
|
|
760
|
+
_leadingColumnLabel.textAlignment = NSTextAlignmentRight;
|
|
761
|
+
_middleColumnLabel.textAlignment = NSTextAlignmentLeft;
|
|
762
|
+
_trailingColumnLabel.textAlignment = NSTextAlignmentLeft;
|
|
763
|
+
_leadingColumnLabel.frame = CGRectMake(
|
|
764
|
+
half,
|
|
765
|
+
0,
|
|
766
|
+
MAX(0, width - _contentPaddingHorizontal - half),
|
|
767
|
+
columnHeight
|
|
768
|
+
);
|
|
769
|
+
_middleColumnLabel.frame = CGRectMake(
|
|
770
|
+
_contentPaddingHorizontal + _trailingColumnWidth + _columnGap,
|
|
771
|
+
0,
|
|
772
|
+
MAX(0, half - _contentPaddingHorizontal - _trailingColumnWidth - _columnGap),
|
|
773
|
+
columnHeight
|
|
774
|
+
);
|
|
775
|
+
_trailingColumnLabel.frame = CGRectMake(
|
|
776
|
+
_contentPaddingHorizontal,
|
|
777
|
+
0,
|
|
778
|
+
_trailingColumnWidth,
|
|
779
|
+
columnHeight
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
if (_shouldCenterSelectedItem && selectedButton != nil &&
|
|
784
|
+
!_tabsScrollView.tracking && !_tabsScrollView.dragging &&
|
|
785
|
+
!_tabsScrollView.decelerating) {
|
|
786
|
+
CGFloat maximumOffset = MAX(0, contentWidth - width);
|
|
787
|
+
CGFloat desiredOffset = RNCClamp(
|
|
788
|
+
CGRectGetMidX(selectedButton.frame) - width / 2,
|
|
789
|
+
0,
|
|
790
|
+
maximumOffset
|
|
791
|
+
);
|
|
792
|
+
[_tabsScrollView setContentOffset:CGPointMake(desiredOffset, 0) animated:NO];
|
|
793
|
+
_shouldCenterSelectedItem = NO;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
@end
|
|
798
|
+
|
|
799
|
+
@interface RNCCollapsiblePagerSharedHeaderView : UIView
|
|
800
|
+
@end
|
|
801
|
+
|
|
802
|
+
@implementation RNCCollapsiblePagerSharedHeaderView
|
|
803
|
+
|
|
804
|
+
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
|
|
805
|
+
{
|
|
806
|
+
if (self.hidden || !self.userInteractionEnabled || self.alpha <= 0.01) return nil;
|
|
807
|
+
// A pinned sticky child can be translated below this host's original bounds.
|
|
808
|
+
// Keep it interactive while the host itself scrolls above the viewport.
|
|
809
|
+
for (UIView *subview in self.subviews.reverseObjectEnumerator) {
|
|
810
|
+
if (subview.hidden || subview.alpha <= 0.01 || !subview.userInteractionEnabled) continue;
|
|
811
|
+
CGPoint childPoint = [subview convertPoint:point fromView:self];
|
|
812
|
+
UIView *hitView = [subview hitTest:childPoint withEvent:event];
|
|
813
|
+
if (hitView != nil) return hitView;
|
|
814
|
+
}
|
|
815
|
+
return nil;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
@end
|
|
819
|
+
|
|
820
|
+
@interface RNCCollapsiblePagerDirectScrollView : UIScrollView
|
|
821
|
+
@property (nonatomic, weak) UIView *excludedHeaderView;
|
|
822
|
+
@end
|
|
823
|
+
|
|
824
|
+
@implementation RNCCollapsiblePagerDirectScrollView
|
|
825
|
+
|
|
826
|
+
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
|
|
827
|
+
{
|
|
828
|
+
if (gestureRecognizer == self.panGestureRecognizer && self.excludedHeaderView != nil) {
|
|
829
|
+
CGPoint point = [gestureRecognizer locationInView:self.excludedHeaderView];
|
|
830
|
+
if ([self.excludedHeaderView hitTest:point withEvent:nil] != nil) {
|
|
831
|
+
RNCCollapsiblePagerLog(@"pager-header-excluded result=1");
|
|
832
|
+
return NO;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
return [super gestureRecognizerShouldBegin:gestureRecognizer];
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
@end
|
|
839
|
+
|
|
840
|
+
@interface RNCCollapsiblePagerPressCancellationPanGestureRecognizer : UIPanGestureRecognizer
|
|
841
|
+
@end
|
|
842
|
+
|
|
843
|
+
@implementation RNCCollapsiblePagerPressCancellationPanGestureRecognizer
|
|
844
|
+
|
|
845
|
+
- (BOOL)canPreventGestureRecognizer:(UIGestureRecognizer *)preventedGestureRecognizer
|
|
846
|
+
{
|
|
847
|
+
return [preventedGestureRecognizer isKindOfClass:RCTSurfaceTouchHandler.class] ||
|
|
848
|
+
[preventedGestureRecognizer isKindOfClass:RCTTouchHandler.class];
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
@end
|
|
852
|
+
|
|
853
|
+
@interface RNCCollapsiblePagerOuterPagerPanGestureRecognizer : UIPanGestureRecognizer
|
|
854
|
+
@property (nonatomic, strong) NSHashTable<UIGestureRecognizer *> *blockedPagerGestures;
|
|
855
|
+
@end
|
|
856
|
+
|
|
857
|
+
@implementation RNCCollapsiblePagerOuterPagerPanGestureRecognizer
|
|
858
|
+
|
|
859
|
+
- (BOOL)canPreventGestureRecognizer:(UIGestureRecognizer *)preventedGestureRecognizer
|
|
860
|
+
{
|
|
861
|
+
return [_blockedPagerGestures containsObject:preventedGestureRecognizer];
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
- (BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer *)preventingGestureRecognizer
|
|
865
|
+
{
|
|
866
|
+
UIView *view = preventingGestureRecognizer.view;
|
|
867
|
+
if ([view isKindOfClass:UIScrollView.class] &&
|
|
868
|
+
[view isDescendantOfView:self.view]) {
|
|
869
|
+
UIScrollView *scrollView = (UIScrollView *)view;
|
|
870
|
+
BOOL horizontal = scrollView.alwaysBounceHorizontal ||
|
|
871
|
+
scrollView.contentSize.width > CGRectGetWidth(scrollView.bounds);
|
|
872
|
+
if (horizontal && preventingGestureRecognizer == scrollView.panGestureRecognizer) {
|
|
873
|
+
return YES;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
return NO;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
@end
|
|
880
|
+
|
|
881
|
+
@interface RNCCollapsiblePagerVerticalPagerGuardGestureRecognizer : UIPanGestureRecognizer
|
|
882
|
+
@end
|
|
883
|
+
|
|
884
|
+
@implementation RNCCollapsiblePagerVerticalPagerGuardGestureRecognizer
|
|
885
|
+
|
|
886
|
+
- (BOOL)canPreventGestureRecognizer:(UIGestureRecognizer *)preventedGestureRecognizer
|
|
887
|
+
{
|
|
888
|
+
return NO;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
@end
|
|
892
|
+
|
|
14
893
|
@interface RNCCollapsiblePagerViewComponentView () <
|
|
15
894
|
RCTRNCCollapsiblePagerViewViewProtocol,
|
|
16
895
|
UIPageViewControllerDataSource,
|
|
@@ -19,32 +898,59 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
19
898
|
UIGestureRecognizerDelegate
|
|
20
899
|
>
|
|
21
900
|
- (void)finishPagerScrollEmittingSelection:(BOOL)emitSelection;
|
|
901
|
+
- (void)completeTransitionOnNextRunLoopForGeneration:(NSUInteger)generation
|
|
902
|
+
transition:(NSUInteger)transition;
|
|
903
|
+
- (void)setDirectNativePagerEnabled:(BOOL)enabled;
|
|
904
|
+
- (void)attachSharedHeadersToScrollView:(UIScrollView *)scrollView;
|
|
905
|
+
- (void)liftSharedHeadersForPagerTransition:(NSString *)reason;
|
|
906
|
+
- (void)restoreSharedHeadersToContainer;
|
|
907
|
+
- (void)layoutSharedHeaders;
|
|
908
|
+
- (void)preparePageForHorizontalTransitionAtIndex:(NSInteger)pageIndex;
|
|
909
|
+
- (void)updateSharedHeaderPressCancellationGesture;
|
|
910
|
+
- (void)detachSharedHeaderPressCancellationGesture;
|
|
911
|
+
- (void)sharedHeaderPressCancellationGestureChanged:(UIPanGestureRecognizer *)recognizer;
|
|
912
|
+
- (void)outerPagerGestureChanged:(UIPanGestureRecognizer *)recognizer;
|
|
913
|
+
- (void)verticalPagerGuardGestureChanged:(UIPanGestureRecognizer *)recognizer;
|
|
914
|
+
- (void)connectVerticalPagerGuard;
|
|
915
|
+
- (void)observedListPanChanged:(UIPanGestureRecognizer *)recognizer;
|
|
22
916
|
@end
|
|
23
917
|
|
|
24
918
|
@implementation RNCCollapsiblePagerViewComponentView {
|
|
25
919
|
UIView *_containerView;
|
|
920
|
+
RNCCollapsiblePagerSharedHeaderView *_sharedHeaderHostView;
|
|
26
921
|
UIPageViewController *_pageViewController;
|
|
922
|
+
UIScrollView *_pageViewControllerScrollView;
|
|
27
923
|
UIScrollView *_pagerScrollView;
|
|
28
924
|
NSMutableArray<UIView<RCTComponentViewProtocol> *> *_logicalChildren;
|
|
29
925
|
NSMutableArray<UIViewController *> *_pageControllers;
|
|
30
926
|
UIView *_headerView;
|
|
31
927
|
UIView *_stickyHeaderView;
|
|
928
|
+
RNCCollapsiblePagerNativeTabBarView *_nativeTabBarView;
|
|
929
|
+
RNCCollapsiblePagerNativeSubHeaderView *_nativeSubHeaderView;
|
|
32
930
|
NSInteger _currentIndex;
|
|
33
931
|
NSInteger _destinationIndex;
|
|
34
932
|
NSInteger _pendingInitialPage;
|
|
35
933
|
NSInteger _headerHeight;
|
|
36
934
|
NSInteger _stickyHeaderHeight;
|
|
935
|
+
CGFloat _nativeTabBarHeight;
|
|
936
|
+
CGFloat _nativeSubHeaderHeight;
|
|
37
937
|
CGFloat _headerOffset;
|
|
38
938
|
BOOL _scrollEnabled;
|
|
39
939
|
// OneKey patch: Coordinate this inner pager with an outer horizontal pager.
|
|
40
940
|
BOOL _nestedScrollEnabled;
|
|
941
|
+
BOOL _nativeSmoothHeaderScrollEnabled;
|
|
41
942
|
UIPanGestureRecognizer *_blockerGesture;
|
|
42
943
|
BOOL _transitioning;
|
|
43
944
|
BOOL _isPagerDragging;
|
|
945
|
+
BOOL _directNativePagerEnabled;
|
|
946
|
+
BOOL _pendingDirectNativePagerEnabled;
|
|
947
|
+
BOOL _hasPendingDirectNativePagerChange;
|
|
44
948
|
BOOL _hasReceivedPageCommand;
|
|
45
949
|
NSUInteger _transitionId;
|
|
46
950
|
// OneKey patch: Retain the latest tab tap while an animation is in flight.
|
|
47
951
|
NSInteger _pendingGoToIndex;
|
|
952
|
+
BOOL _pendingGoToAnimated;
|
|
953
|
+
BOOL _needsSlotRebuild;
|
|
48
954
|
BOOL _hasAppliedInitialPage;
|
|
49
955
|
BOOL _needsPropsReapply;
|
|
50
956
|
NSString *_layoutDirection;
|
|
@@ -57,7 +963,16 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
57
963
|
NSMapTable<UIScrollView *, NSNumber *> *_originalAlwaysBounceVertical;
|
|
58
964
|
NSMapTable<UIScrollView *, NSNumber *> *_originalInsetAdjustmentBehavior;
|
|
59
965
|
__weak UIScrollView *_observedScrollView;
|
|
966
|
+
__weak UIScrollView *_sharedHeaderScrollView;
|
|
967
|
+
__weak UIView *_sharedHeaderPressCancellationGestureHost;
|
|
968
|
+
__weak UIGestureRecognizer *_reactTouchHandler;
|
|
969
|
+
UIPanGestureRecognizer *_sharedHeaderPressCancellationGesture;
|
|
970
|
+
RNCCollapsiblePagerOuterPagerPanGestureRecognizer *_sharedHeaderOuterPagerGesture;
|
|
971
|
+
UIPanGestureRecognizer *_verticalPagerGesture;
|
|
60
972
|
BOOL _observingContentOffset;
|
|
973
|
+
CGFloat _currentLogicalOffset;
|
|
974
|
+
CGFloat _observedListPanStartLogicalOffset;
|
|
975
|
+
BOOL _sharedHeadersLiftedForPagerTransition;
|
|
61
976
|
BOOL _isBeingRecycled;
|
|
62
977
|
NSUInteger _generation;
|
|
63
978
|
}
|
|
@@ -86,8 +1001,15 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
86
1001
|
_destinationIndex = 0;
|
|
87
1002
|
_pendingInitialPage = 0;
|
|
88
1003
|
_scrollEnabled = YES;
|
|
1004
|
+
_nativeTabBarHeight = 44;
|
|
1005
|
+
_nativeSubHeaderHeight = 74;
|
|
89
1006
|
_nestedScrollEnabled = NO;
|
|
1007
|
+
_nativeSmoothHeaderScrollEnabled = NO;
|
|
1008
|
+
_directNativePagerEnabled = NO;
|
|
1009
|
+
_pendingDirectNativePagerEnabled = NO;
|
|
1010
|
+
_hasPendingDirectNativePagerChange = NO;
|
|
90
1011
|
_pendingGoToIndex = -1;
|
|
1012
|
+
_pendingGoToAnimated = YES;
|
|
91
1013
|
_hasAppliedInitialPage = NO;
|
|
92
1014
|
_needsPropsReapply = YES;
|
|
93
1015
|
_layoutDirection = @"ltr";
|
|
@@ -96,6 +1018,50 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
96
1018
|
_containerView.clipsToBounds = YES;
|
|
97
1019
|
self.contentView = _containerView;
|
|
98
1020
|
|
|
1021
|
+
_sharedHeaderHostView = [RNCCollapsiblePagerSharedHeaderView new];
|
|
1022
|
+
_sharedHeaderHostView.clipsToBounds = NO;
|
|
1023
|
+
[_containerView addSubview:_sharedHeaderHostView];
|
|
1024
|
+
|
|
1025
|
+
_verticalPagerGesture =
|
|
1026
|
+
[[RNCCollapsiblePagerVerticalPagerGuardGestureRecognizer alloc]
|
|
1027
|
+
initWithTarget:self
|
|
1028
|
+
action:@selector(verticalPagerGuardGestureChanged:)];
|
|
1029
|
+
_verticalPagerGesture.delegate = self;
|
|
1030
|
+
_verticalPagerGesture.cancelsTouchesInView = NO;
|
|
1031
|
+
_verticalPagerGesture.enabled = NO;
|
|
1032
|
+
[self addGestureRecognizer:_verticalPagerGesture];
|
|
1033
|
+
|
|
1034
|
+
_nativeTabBarView = [RNCCollapsiblePagerNativeTabBarView new];
|
|
1035
|
+
_nativeTabBarView.hidden = YES;
|
|
1036
|
+
__weak __typeof__(self) weakSelf = self;
|
|
1037
|
+
_nativeTabBarView.onTabPress = ^(NSInteger index, NSString *key) {
|
|
1038
|
+
__strong __typeof__(weakSelf) self = weakSelf;
|
|
1039
|
+
if (self == nil || self->_isBeingRecycled) return;
|
|
1040
|
+
const auto emitter = [self eventEmitter];
|
|
1041
|
+
if (emitter) {
|
|
1042
|
+
emitter->onNativeTabPress({
|
|
1043
|
+
.position = (int)index,
|
|
1044
|
+
.key = std::string(key.UTF8String ?: "")
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
};
|
|
1048
|
+
[_sharedHeaderHostView addSubview:_nativeTabBarView];
|
|
1049
|
+
|
|
1050
|
+
_nativeSubHeaderView = [RNCCollapsiblePagerNativeSubHeaderView new];
|
|
1051
|
+
_nativeSubHeaderView.hidden = YES;
|
|
1052
|
+
_nativeSubHeaderView.onItemPress = ^(NSInteger index, NSString *key) {
|
|
1053
|
+
__strong __typeof__(weakSelf) self = weakSelf;
|
|
1054
|
+
if (self == nil || self->_isBeingRecycled) return;
|
|
1055
|
+
const auto emitter = [self eventEmitter];
|
|
1056
|
+
if (emitter) {
|
|
1057
|
+
emitter->onNativeSubHeaderPress({
|
|
1058
|
+
.position = (int)index,
|
|
1059
|
+
.key = std::string(key.UTF8String ?: "")
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
};
|
|
1063
|
+
[_sharedHeaderHostView addSubview:_nativeSubHeaderView];
|
|
1064
|
+
|
|
99
1065
|
[self initializePageViewController];
|
|
100
1066
|
}
|
|
101
1067
|
return self;
|
|
@@ -104,7 +1070,7 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
104
1070
|
- (void)willMoveToSuperview:(UIView *)newSuperview
|
|
105
1071
|
{
|
|
106
1072
|
[super willMoveToSuperview:newSuperview];
|
|
107
|
-
if (newSuperview != nil && _pageViewController == nil) {
|
|
1073
|
+
if (newSuperview != nil && !_directNativePagerEnabled && _pageViewController == nil) {
|
|
108
1074
|
self.contentView = _containerView;
|
|
109
1075
|
[self initializePageViewController];
|
|
110
1076
|
}
|
|
@@ -113,6 +1079,7 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
113
1079
|
- (void)didMoveToWindow
|
|
114
1080
|
{
|
|
115
1081
|
[super didMoveToWindow];
|
|
1082
|
+
[self updateSharedHeaderPressCancellationGesture];
|
|
116
1083
|
// OneKey patch: removing a decelerating page from its window can suppress
|
|
117
1084
|
// UIKit's final scroll callback. Restore the last acknowledged page on reattach.
|
|
118
1085
|
if (self.window != nil && _isPagerDragging &&
|
|
@@ -121,8 +1088,14 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
121
1088
|
}
|
|
122
1089
|
}
|
|
123
1090
|
|
|
1091
|
+
- (void)dealloc
|
|
1092
|
+
{
|
|
1093
|
+
[self detachSharedHeaderPressCancellationGesture];
|
|
1094
|
+
}
|
|
1095
|
+
|
|
124
1096
|
- (void)initializePageViewController
|
|
125
1097
|
{
|
|
1098
|
+
if (_directNativePagerEnabled) return;
|
|
126
1099
|
_pageViewController = [[UIPageViewController alloc]
|
|
127
1100
|
initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll
|
|
128
1101
|
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
|
|
@@ -136,6 +1109,7 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
136
1109
|
for (UIView *subview in _pageViewController.view.subviews) {
|
|
137
1110
|
if ([subview isKindOfClass:UIScrollView.class]) {
|
|
138
1111
|
_pagerScrollView = (UIScrollView *)subview;
|
|
1112
|
+
_pageViewControllerScrollView = _pagerScrollView;
|
|
139
1113
|
_pagerScrollView.delegate = self;
|
|
140
1114
|
_pagerScrollView.delaysContentTouches = NO;
|
|
141
1115
|
_pagerScrollView.scrollEnabled = _scrollEnabled;
|
|
@@ -143,6 +1117,200 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
143
1117
|
}
|
|
144
1118
|
}
|
|
145
1119
|
[self applyNestedScrollBlocker];
|
|
1120
|
+
[self connectVerticalPagerGuard];
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
- (void)setDirectNativePagerEnabled:(BOOL)enabled
|
|
1124
|
+
{
|
|
1125
|
+
if ([_pagerScrollView isKindOfClass:RNCCollapsiblePagerDirectScrollView.class]) {
|
|
1126
|
+
((RNCCollapsiblePagerDirectScrollView *)_pagerScrollView).excludedHeaderView =
|
|
1127
|
+
_nativeSmoothHeaderScrollEnabled ? _sharedHeaderHostView : nil;
|
|
1128
|
+
}
|
|
1129
|
+
if (_transitioning || _isPagerDragging) {
|
|
1130
|
+
_pendingDirectNativePagerEnabled = enabled;
|
|
1131
|
+
_hasPendingDirectNativePagerChange = YES;
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
_hasPendingDirectNativePagerChange = NO;
|
|
1135
|
+
if (_directNativePagerEnabled == enabled) return;
|
|
1136
|
+
|
|
1137
|
+
[self detachScrollObserver];
|
|
1138
|
+
[self restoreSharedHeadersToContainer];
|
|
1139
|
+
if (_blockerGesture != nil) {
|
|
1140
|
+
[self removeGestureRecognizer:_blockerGesture];
|
|
1141
|
+
_blockerGesture = nil;
|
|
1142
|
+
}
|
|
1143
|
+
for (UIViewController *controller in _pageControllers) {
|
|
1144
|
+
[controller.view removeFromSuperview];
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
if (enabled) {
|
|
1148
|
+
_pageViewController.dataSource = nil;
|
|
1149
|
+
_pageViewController.delegate = nil;
|
|
1150
|
+
_pageViewControllerScrollView.delegate = nil;
|
|
1151
|
+
[_pageViewController.view removeFromSuperview];
|
|
1152
|
+
_pageViewController = nil;
|
|
1153
|
+
_pageViewControllerScrollView = nil;
|
|
1154
|
+
|
|
1155
|
+
RNCCollapsiblePagerDirectScrollView *directPager =
|
|
1156
|
+
[RNCCollapsiblePagerDirectScrollView new];
|
|
1157
|
+
directPager.excludedHeaderView = _nativeSmoothHeaderScrollEnabled
|
|
1158
|
+
? _sharedHeaderHostView
|
|
1159
|
+
: nil;
|
|
1160
|
+
directPager.pagingEnabled = YES;
|
|
1161
|
+
directPager.directionalLockEnabled = YES;
|
|
1162
|
+
directPager.delaysContentTouches = NO;
|
|
1163
|
+
directPager.showsHorizontalScrollIndicator = NO;
|
|
1164
|
+
directPager.showsVerticalScrollIndicator = NO;
|
|
1165
|
+
directPager.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
|
|
1166
|
+
directPager.scrollEnabled = _scrollEnabled;
|
|
1167
|
+
directPager.delegate = self;
|
|
1168
|
+
[_containerView insertSubview:directPager atIndex:0];
|
|
1169
|
+
_pagerScrollView = directPager;
|
|
1170
|
+
_directNativePagerEnabled = YES;
|
|
1171
|
+
} else {
|
|
1172
|
+
_pagerScrollView.delegate = nil;
|
|
1173
|
+
[_pagerScrollView removeFromSuperview];
|
|
1174
|
+
_pagerScrollView = nil;
|
|
1175
|
+
_directNativePagerEnabled = NO;
|
|
1176
|
+
[self initializePageViewController];
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
[self applyNestedScrollBlocker];
|
|
1180
|
+
[self connectVerticalPagerGuard];
|
|
1181
|
+
[self rebuildSlots];
|
|
1182
|
+
[self updateSharedHeaderPressCancellationGesture];
|
|
1183
|
+
[self setNeedsLayout];
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
- (CGFloat)directPagerOffsetForIndex:(NSInteger)index
|
|
1187
|
+
{
|
|
1188
|
+
CGFloat width = CGRectGetWidth(_pagerScrollView.bounds);
|
|
1189
|
+
if (width <= 0 || _pageControllers.count == 0) return 0;
|
|
1190
|
+
NSInteger physicalIndex = self.isLtrLayout
|
|
1191
|
+
? index
|
|
1192
|
+
: (NSInteger)_pageControllers.count - 1 - index;
|
|
1193
|
+
return width * MAX(0, physicalIndex);
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
- (CGFloat)directPagerProgress
|
|
1197
|
+
{
|
|
1198
|
+
CGFloat width = CGRectGetWidth(_pagerScrollView.bounds);
|
|
1199
|
+
if (width <= 0 || _pageControllers.count == 0) return _currentIndex;
|
|
1200
|
+
CGFloat physicalProgress = RNCClamp(
|
|
1201
|
+
_pagerScrollView.contentOffset.x / width,
|
|
1202
|
+
0,
|
|
1203
|
+
_pageControllers.count - 1
|
|
1204
|
+
);
|
|
1205
|
+
return self.isLtrLayout
|
|
1206
|
+
? physicalProgress
|
|
1207
|
+
: _pageControllers.count - 1 - physicalProgress;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
#pragma mark - Smooth shared header ownership
|
|
1211
|
+
|
|
1212
|
+
- (void)restoreSharedHeadersToContainer
|
|
1213
|
+
{
|
|
1214
|
+
_sharedHeaderScrollView = nil;
|
|
1215
|
+
_sharedHeadersLiftedForPagerTransition = NO;
|
|
1216
|
+
_sharedHeaderHostView.layer.zPosition = 0;
|
|
1217
|
+
if (_sharedHeaderHostView.superview != _containerView) {
|
|
1218
|
+
[_containerView addSubview:_sharedHeaderHostView];
|
|
1219
|
+
}
|
|
1220
|
+
[self layoutSharedHeaders];
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
- (void)attachSharedHeadersToScrollView:(UIScrollView *)scrollView
|
|
1224
|
+
{
|
|
1225
|
+
if (!_nativeSmoothHeaderScrollEnabled || _transitioning || _isPagerDragging ||
|
|
1226
|
+
scrollView == nil || _isBeingRecycled) return;
|
|
1227
|
+
if (_sharedHeaderScrollView == scrollView &&
|
|
1228
|
+
_sharedHeaderHostView.superview == scrollView) {
|
|
1229
|
+
[self layoutSharedHeaders];
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
_sharedHeaderScrollView = scrollView;
|
|
1234
|
+
_sharedHeadersLiftedForPagerTransition = NO;
|
|
1235
|
+
_sharedHeaderHostView.layer.zPosition = 1000;
|
|
1236
|
+
if (_sharedHeaderHostView.superview != scrollView) {
|
|
1237
|
+
[scrollView addSubview:_sharedHeaderHostView];
|
|
1238
|
+
}
|
|
1239
|
+
[_nativeTabBarView updateAncestorGesturePrecedence];
|
|
1240
|
+
[_nativeSubHeaderView updateAncestorGesturePrecedence];
|
|
1241
|
+
_currentLogicalOffset = scrollView.contentOffset.y + scrollView.contentInset.top;
|
|
1242
|
+
[self layoutSharedHeaders];
|
|
1243
|
+
RNCCollapsiblePagerLog([NSString stringWithFormat:
|
|
1244
|
+
@"header-owner=list page=%ld offset=%.2f",
|
|
1245
|
+
(long)_currentIndex,
|
|
1246
|
+
_currentLogicalOffset]);
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
- (void)liftSharedHeadersForPagerTransition:(NSString *)reason
|
|
1250
|
+
{
|
|
1251
|
+
if (!_nativeSmoothHeaderScrollEnabled || _sharedHeadersLiftedForPagerTransition ||
|
|
1252
|
+
_isBeingRecycled) return;
|
|
1253
|
+
[self restoreSharedHeadersToContainer];
|
|
1254
|
+
_sharedHeadersLiftedForPagerTransition = YES;
|
|
1255
|
+
[self layoutSharedHeaders];
|
|
1256
|
+
RNCCollapsiblePagerLog([NSString stringWithFormat:
|
|
1257
|
+
@"header-owner=pager page=%ld destination=%ld reason=%@",
|
|
1258
|
+
(long)_currentIndex,
|
|
1259
|
+
(long)_destinationIndex,
|
|
1260
|
+
reason ?: @"unknown"]);
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
- (void)layoutSharedHeaders
|
|
1264
|
+
{
|
|
1265
|
+
CGFloat width = CGRectGetWidth(self.bounds);
|
|
1266
|
+
CGFloat totalHeight = _headerHeight + _stickyHeaderHeight;
|
|
1267
|
+
BOOL attachedToList = _nativeSmoothHeaderScrollEnabled &&
|
|
1268
|
+
_sharedHeaderScrollView != nil && !_sharedHeadersLiftedForPagerTransition;
|
|
1269
|
+
|
|
1270
|
+
_sharedHeaderHostView.transform = CGAffineTransformIdentity;
|
|
1271
|
+
_headerView.transform = CGAffineTransformIdentity;
|
|
1272
|
+
_stickyHeaderView.transform = CGAffineTransformIdentity;
|
|
1273
|
+
_nativeTabBarView.transform = CGAffineTransformIdentity;
|
|
1274
|
+
_nativeSubHeaderView.transform = CGAffineTransformIdentity;
|
|
1275
|
+
|
|
1276
|
+
_sharedHeaderHostView.frame = CGRectMake(
|
|
1277
|
+
0,
|
|
1278
|
+
attachedToList ? -totalHeight : 0,
|
|
1279
|
+
width,
|
|
1280
|
+
totalHeight
|
|
1281
|
+
);
|
|
1282
|
+
CGFloat stickyY = _headerHeight;
|
|
1283
|
+
_headerView.frame = CGRectMake(0, 0, width, _headerHeight);
|
|
1284
|
+
_stickyHeaderView.frame = CGRectMake(0, stickyY, width, _stickyHeaderHeight);
|
|
1285
|
+
_nativeTabBarView.frame = CGRectMake(
|
|
1286
|
+
0,
|
|
1287
|
+
stickyY,
|
|
1288
|
+
width,
|
|
1289
|
+
MIN(_stickyHeaderHeight, _nativeTabBarHeight)
|
|
1290
|
+
);
|
|
1291
|
+
CGFloat nativeTabBarVisibleHeight = CGRectGetHeight(_nativeTabBarView.frame);
|
|
1292
|
+
_nativeSubHeaderView.frame = CGRectMake(
|
|
1293
|
+
0,
|
|
1294
|
+
stickyY + nativeTabBarVisibleHeight,
|
|
1295
|
+
width,
|
|
1296
|
+
MIN(
|
|
1297
|
+
MAX(0, _stickyHeaderHeight - nativeTabBarVisibleHeight),
|
|
1298
|
+
_nativeSubHeaderHeight
|
|
1299
|
+
)
|
|
1300
|
+
);
|
|
1301
|
+
[self applyHeaderOffset];
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
- (void)preparePageForHorizontalTransitionAtIndex:(NSInteger)pageIndex
|
|
1305
|
+
{
|
|
1306
|
+
if (!_nativeSmoothHeaderScrollEnabled || _headerOffset >= _headerHeight ||
|
|
1307
|
+
pageIndex < 0 || pageIndex >= _pageControllers.count) return;
|
|
1308
|
+
|
|
1309
|
+
_pageOffsets[[self pageKeyForIndex:pageIndex]] = @(_headerOffset);
|
|
1310
|
+
UIScrollView *scrollView = [self findVerticalScrollViewInView:_pageControllers[pageIndex].view];
|
|
1311
|
+
if (scrollView != nil && scrollView != _observedScrollView) {
|
|
1312
|
+
[self applyInsetsToScrollView:scrollView pageIndex:pageIndex restore:YES];
|
|
1313
|
+
}
|
|
146
1314
|
}
|
|
147
1315
|
|
|
148
1316
|
#pragma mark - React mounting
|
|
@@ -169,24 +1337,36 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
169
1337
|
|
|
170
1338
|
- (void)rebuildSlots
|
|
171
1339
|
{
|
|
1340
|
+
if (_transitioning || _isPagerDragging) {
|
|
1341
|
+
_needsSlotRebuild = YES;
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
_needsSlotRebuild = NO;
|
|
172
1345
|
[self detachScrollObserver];
|
|
1346
|
+
[self restoreSharedHeadersToContainer];
|
|
173
1347
|
// OneKey patch: Fabric reuses these views without resetting transforms
|
|
174
1348
|
// written by a native parent. Release our collapse translation with the slot.
|
|
175
1349
|
_headerView.transform = CGAffineTransformIdentity;
|
|
176
1350
|
_stickyHeaderView.transform = CGAffineTransformIdentity;
|
|
1351
|
+
_sharedHeaderHostView.transform = CGAffineTransformIdentity;
|
|
1352
|
+
_nativeTabBarView.transform = CGAffineTransformIdentity;
|
|
1353
|
+
_nativeSubHeaderView.transform = CGAffineTransformIdentity;
|
|
177
1354
|
[_headerView removeFromSuperview];
|
|
178
1355
|
[_stickyHeaderView removeFromSuperview];
|
|
1356
|
+
for (UIViewController *controller in _pageControllers) {
|
|
1357
|
+
[controller.view removeFromSuperview];
|
|
1358
|
+
}
|
|
179
1359
|
_headerView = nil;
|
|
180
1360
|
_stickyHeaderView = nil;
|
|
181
1361
|
[_pageControllers removeAllObjects];
|
|
182
1362
|
|
|
183
1363
|
if (_logicalChildren.count > 0) {
|
|
184
1364
|
_headerView = _logicalChildren[0];
|
|
185
|
-
[
|
|
1365
|
+
[_sharedHeaderHostView addSubview:_headerView];
|
|
186
1366
|
}
|
|
187
1367
|
if (_logicalChildren.count > 1) {
|
|
188
1368
|
_stickyHeaderView = _logicalChildren[1];
|
|
189
|
-
[
|
|
1369
|
+
[_sharedHeaderHostView addSubview:_stickyHeaderView];
|
|
190
1370
|
}
|
|
191
1371
|
for (NSInteger index = 2; index < _logicalChildren.count; index++) {
|
|
192
1372
|
UIView *page = _logicalChildren[index];
|
|
@@ -201,18 +1381,32 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
201
1381
|
_pendingInitialPage < _pageControllers.count) {
|
|
202
1382
|
_currentIndex = _pendingInitialPage;
|
|
203
1383
|
// A recycled host may receive slots before its page controller exists.
|
|
204
|
-
_hasAppliedInitialPage = _pageViewController != nil;
|
|
1384
|
+
_hasAppliedInitialPage = _directNativePagerEnabled || _pageViewController != nil;
|
|
205
1385
|
} else {
|
|
206
1386
|
_currentIndex = MIN(MAX(_currentIndex, 0), _pageControllers.count - 1);
|
|
207
1387
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
1388
|
+
_destinationIndex = _currentIndex;
|
|
1389
|
+
if (_directNativePagerEnabled) {
|
|
1390
|
+
for (UIViewController *controller in _pageControllers) {
|
|
1391
|
+
[_pagerScrollView addSubview:controller.view];
|
|
1392
|
+
}
|
|
1393
|
+
[_pagerScrollView setContentOffset:CGPointMake(
|
|
1394
|
+
[self directPagerOffsetForIndex:_currentIndex],
|
|
1395
|
+
0
|
|
1396
|
+
) animated:NO];
|
|
1397
|
+
} else {
|
|
1398
|
+
UIViewController *controller = _pageControllers[_currentIndex];
|
|
1399
|
+
[_pageViewController setViewControllers:@[controller]
|
|
1400
|
+
direction:UIPageViewControllerNavigationDirectionForward
|
|
1401
|
+
animated:NO
|
|
1402
|
+
completion:nil];
|
|
1403
|
+
}
|
|
1404
|
+
[_nativeTabBarView setProgress:_currentIndex];
|
|
213
1405
|
}
|
|
214
|
-
if (
|
|
215
|
-
if (
|
|
1406
|
+
if (_stickyHeaderView != nil) [_sharedHeaderHostView bringSubviewToFront:_stickyHeaderView];
|
|
1407
|
+
if (!_nativeTabBarView.hidden) [_sharedHeaderHostView bringSubviewToFront:_nativeTabBarView];
|
|
1408
|
+
if (!_nativeSubHeaderView.hidden) [_sharedHeaderHostView bringSubviewToFront:_nativeSubHeaderView];
|
|
1409
|
+
[_containerView bringSubviewToFront:_sharedHeaderHostView];
|
|
216
1410
|
[self setNeedsLayout];
|
|
217
1411
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
218
1412
|
if (!self->_isBeingRecycled) {
|
|
@@ -226,38 +1420,68 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
226
1420
|
{
|
|
227
1421
|
[super layoutSubviews];
|
|
228
1422
|
_containerView.frame = self.bounds;
|
|
229
|
-
|
|
1423
|
+
if (_directNativePagerEnabled) {
|
|
1424
|
+
_pagerScrollView.frame = _containerView.bounds;
|
|
1425
|
+
} else {
|
|
1426
|
+
_pageViewController.view.frame = _containerView.bounds;
|
|
1427
|
+
}
|
|
230
1428
|
// OneKey patch: Fabric may mount page slots before applying the host props.
|
|
231
1429
|
// Commit the pending initial page once both props and page controllers exist.
|
|
232
|
-
if (!
|
|
1430
|
+
if (!_transitioning && !_isPagerDragging && !_hasAppliedInitialPage &&
|
|
1431
|
+
(_directNativePagerEnabled ? _pagerScrollView != nil : _pageViewController != nil) &&
|
|
233
1432
|
_pendingInitialPage >= 0 && _pendingInitialPage < _pageControllers.count) {
|
|
234
1433
|
[self detachScrollObserver];
|
|
235
1434
|
_currentIndex = _pendingInitialPage;
|
|
236
1435
|
_destinationIndex = _currentIndex;
|
|
237
1436
|
_hasAppliedInitialPage = YES;
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
1437
|
+
if (_directNativePagerEnabled) {
|
|
1438
|
+
[_pagerScrollView setContentOffset:CGPointMake(
|
|
1439
|
+
[self directPagerOffsetForIndex:_currentIndex],
|
|
1440
|
+
0
|
|
1441
|
+
) animated:NO];
|
|
1442
|
+
} else {
|
|
1443
|
+
[_pageViewController setViewControllers:@[_pageControllers[_currentIndex]]
|
|
1444
|
+
direction:UIPageViewControllerNavigationDirectionForward
|
|
1445
|
+
animated:NO
|
|
1446
|
+
completion:nil];
|
|
1447
|
+
}
|
|
1448
|
+
[_nativeTabBarView setProgress:_currentIndex];
|
|
242
1449
|
}
|
|
243
1450
|
// UIKit frame assignment is undefined while a view has a non-identity transform.
|
|
244
|
-
//
|
|
1451
|
+
// Restore transforms before settling page and shared-header frames.
|
|
245
1452
|
_headerView.transform = CGAffineTransformIdentity;
|
|
246
1453
|
_stickyHeaderView.transform = CGAffineTransformIdentity;
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
1454
|
+
_sharedHeaderHostView.transform = CGAffineTransformIdentity;
|
|
1455
|
+
_nativeTabBarView.transform = CGAffineTransformIdentity;
|
|
1456
|
+
_nativeSubHeaderView.transform = CGAffineTransformIdentity;
|
|
1457
|
+
// Settle native page containers before sizing their React child views.
|
|
1458
|
+
if (_directNativePagerEnabled) {
|
|
1459
|
+
CGFloat width = CGRectGetWidth(_pagerScrollView.bounds);
|
|
1460
|
+
CGFloat height = CGRectGetHeight(_pagerScrollView.bounds);
|
|
1461
|
+
_pagerScrollView.contentSize = CGSizeMake(width * _pageControllers.count, height);
|
|
1462
|
+
[_pageControllers enumerateObjectsUsingBlock:^(UIViewController *controller,
|
|
1463
|
+
NSUInteger index,
|
|
1464
|
+
BOOL *stop) {
|
|
1465
|
+
NSInteger physicalIndex = self.isLtrLayout
|
|
1466
|
+
? (NSInteger)index
|
|
1467
|
+
: (NSInteger)self->_pageControllers.count - 1 - (NSInteger)index;
|
|
1468
|
+
controller.view.frame = CGRectMake(width * physicalIndex, 0, width, height);
|
|
1469
|
+
controller.view.subviews.firstObject.frame = controller.view.bounds;
|
|
1470
|
+
}];
|
|
1471
|
+
if (!_transitioning && !_isPagerDragging) {
|
|
1472
|
+
[_pagerScrollView setContentOffset:CGPointMake(
|
|
1473
|
+
[self directPagerOffsetForIndex:_currentIndex],
|
|
1474
|
+
0
|
|
1475
|
+
) animated:NO];
|
|
1476
|
+
}
|
|
1477
|
+
} else {
|
|
1478
|
+
[_pageViewController.view layoutIfNeeded];
|
|
1479
|
+
for (UIViewController *controller in _pageControllers) {
|
|
1480
|
+
controller.view.frame = _pageViewController.view.bounds;
|
|
1481
|
+
controller.view.subviews.firstObject.frame = controller.view.bounds;
|
|
1482
|
+
}
|
|
259
1483
|
}
|
|
260
|
-
[self
|
|
1484
|
+
[self layoutSharedHeaders];
|
|
261
1485
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
262
1486
|
if (!self->_isBeingRecycled) {
|
|
263
1487
|
[self prepareAdjacentPageInsets];
|
|
@@ -270,7 +1494,9 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
270
1494
|
{
|
|
271
1495
|
_isBeingRecycled = YES;
|
|
272
1496
|
_generation++;
|
|
1497
|
+
[self detachSharedHeaderPressCancellationGesture];
|
|
273
1498
|
[self detachScrollObserver];
|
|
1499
|
+
[self restoreSharedHeadersToContainer];
|
|
274
1500
|
for (UIScrollView *scrollView in _originalInsets.keyEnumerator) {
|
|
275
1501
|
NSValue *value = [_originalInsets objectForKey:scrollView];
|
|
276
1502
|
if (value != nil) {
|
|
@@ -301,8 +1527,13 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
301
1527
|
// written by a native parent. Release our collapse translation with the slot.
|
|
302
1528
|
_headerView.transform = CGAffineTransformIdentity;
|
|
303
1529
|
_stickyHeaderView.transform = CGAffineTransformIdentity;
|
|
1530
|
+
_nativeTabBarView.transform = CGAffineTransformIdentity;
|
|
1531
|
+
_nativeSubHeaderView.transform = CGAffineTransformIdentity;
|
|
304
1532
|
[_headerView removeFromSuperview];
|
|
305
1533
|
[_stickyHeaderView removeFromSuperview];
|
|
1534
|
+
for (UIViewController *controller in _pageControllers) {
|
|
1535
|
+
[controller.view removeFromSuperview];
|
|
1536
|
+
}
|
|
306
1537
|
[_logicalChildren removeAllObjects];
|
|
307
1538
|
[_pageControllers removeAllObjects];
|
|
308
1539
|
_headerView = nil;
|
|
@@ -315,7 +1546,11 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
315
1546
|
_blockerGesture = nil;
|
|
316
1547
|
}
|
|
317
1548
|
[_pageViewController.view removeFromSuperview];
|
|
1549
|
+
if (_directNativePagerEnabled) {
|
|
1550
|
+
[_pagerScrollView removeFromSuperview];
|
|
1551
|
+
}
|
|
318
1552
|
_pageViewController = nil;
|
|
1553
|
+
_pageViewControllerScrollView = nil;
|
|
319
1554
|
_pagerScrollView = nil;
|
|
320
1555
|
[super prepareForRecycle];
|
|
321
1556
|
[_pageOffsets removeAllObjects];
|
|
@@ -326,17 +1561,36 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
326
1561
|
_pendingInitialPage = 0;
|
|
327
1562
|
_headerHeight = 0;
|
|
328
1563
|
_stickyHeaderHeight = 0;
|
|
1564
|
+
_nativeTabBarHeight = 44;
|
|
1565
|
+
_nativeSubHeaderHeight = 74;
|
|
329
1566
|
_headerOffset = 0;
|
|
1567
|
+
_currentLogicalOffset = 0;
|
|
1568
|
+
_observedListPanStartLogicalOffset = 0;
|
|
1569
|
+
_sharedHeaderHostView.transform = CGAffineTransformIdentity;
|
|
1570
|
+
_sharedHeaderHostView.frame = CGRectZero;
|
|
1571
|
+
_sharedHeaderHostView.layer.zPosition = 0;
|
|
330
1572
|
_scrollEnabled = YES;
|
|
331
1573
|
_nestedScrollEnabled = NO;
|
|
1574
|
+
_nativeSmoothHeaderScrollEnabled = NO;
|
|
1575
|
+
_directNativePagerEnabled = NO;
|
|
1576
|
+
_pendingDirectNativePagerEnabled = NO;
|
|
1577
|
+
_hasPendingDirectNativePagerChange = NO;
|
|
332
1578
|
_transitioning = NO;
|
|
333
1579
|
_isPagerDragging = NO;
|
|
1580
|
+
_sharedHeaderScrollView = nil;
|
|
1581
|
+
_sharedHeadersLiftedForPagerTransition = NO;
|
|
1582
|
+
_verticalPagerGesture.enabled = NO;
|
|
334
1583
|
_hasReceivedPageCommand = NO;
|
|
335
1584
|
_pendingGoToIndex = -1;
|
|
1585
|
+
_pendingGoToAnimated = YES;
|
|
1586
|
+
_needsSlotRebuild = NO;
|
|
336
1587
|
_hasAppliedInitialPage = NO;
|
|
337
1588
|
// OneKey patch: Fabric retains old props when recycling this view.
|
|
338
1589
|
_needsPropsReapply = YES;
|
|
339
1590
|
_layoutDirection = @"ltr";
|
|
1591
|
+
[_nativeTabBarView updateItemsJSON:@"[]"];
|
|
1592
|
+
[_nativeTabBarView setProgress:0];
|
|
1593
|
+
[_nativeSubHeaderView updateConfigJSON:@"{}"];
|
|
340
1594
|
_isBeingRecycled = NO;
|
|
341
1595
|
}
|
|
342
1596
|
|
|
@@ -362,8 +1616,28 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
362
1616
|
_nestedScrollEnabled = newViewProps.nestedScrollEnabled;
|
|
363
1617
|
[self applyNestedScrollBlocker];
|
|
364
1618
|
}
|
|
1619
|
+
if (_needsPropsReapply ||
|
|
1620
|
+
oldViewProps.nativeSmoothHeaderScrollEnabled !=
|
|
1621
|
+
newViewProps.nativeSmoothHeaderScrollEnabled) {
|
|
1622
|
+
_nativeSmoothHeaderScrollEnabled = newViewProps.nativeSmoothHeaderScrollEnabled;
|
|
1623
|
+
_verticalPagerGesture.enabled = _nativeSmoothHeaderScrollEnabled;
|
|
1624
|
+
[self connectVerticalPagerGuard];
|
|
1625
|
+
[self setDirectNativePagerEnabled:
|
|
1626
|
+
_nativeSmoothHeaderScrollEnabled || !_nativeTabBarView.hidden];
|
|
1627
|
+
if (_nativeSmoothHeaderScrollEnabled) {
|
|
1628
|
+
[self attachSharedHeadersToScrollView:_observedScrollView];
|
|
1629
|
+
} else {
|
|
1630
|
+
[self restoreSharedHeadersToContainer];
|
|
1631
|
+
}
|
|
1632
|
+
[self updateSharedHeaderPressCancellationGesture];
|
|
1633
|
+
RNCCollapsiblePagerLog([NSString stringWithFormat:
|
|
1634
|
+
@"smooth-header enabled=%d",
|
|
1635
|
+
_nativeSmoothHeaderScrollEnabled]);
|
|
1636
|
+
}
|
|
365
1637
|
if (_needsPropsReapply || oldViewProps.layoutDirection != newViewProps.layoutDirection) {
|
|
366
1638
|
_layoutDirection = RCTNSStringFromString(toString(newViewProps.layoutDirection));
|
|
1639
|
+
[_nativeTabBarView setLeftToRight:self.isLtrLayout];
|
|
1640
|
+
[_nativeSubHeaderView setLeftToRight:self.isLtrLayout];
|
|
367
1641
|
}
|
|
368
1642
|
BOOL insetsChanged = NO;
|
|
369
1643
|
if (_needsPropsReapply || oldViewProps.headerHeight != newViewProps.headerHeight) {
|
|
@@ -385,6 +1659,56 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
385
1659
|
_retainedPages = RCTNSStringFromString(newViewProps.retainedPages);
|
|
386
1660
|
[self setNeedsLayout];
|
|
387
1661
|
}
|
|
1662
|
+
if (_needsPropsReapply || oldViewProps.nativeTabBarItems != newViewProps.nativeTabBarItems) {
|
|
1663
|
+
NSString *itemsJSON = RCTNSStringFromString(newViewProps.nativeTabBarItems);
|
|
1664
|
+
[_nativeTabBarView updateItemsJSON:itemsJSON];
|
|
1665
|
+
[self setDirectNativePagerEnabled:
|
|
1666
|
+
_nativeSmoothHeaderScrollEnabled || !_nativeTabBarView.hidden];
|
|
1667
|
+
[self setNeedsLayout];
|
|
1668
|
+
}
|
|
1669
|
+
if (_needsPropsReapply || oldViewProps.nativeSubHeaderConfig != newViewProps.nativeSubHeaderConfig) {
|
|
1670
|
+
NSString *configJSON = RCTNSStringFromString(newViewProps.nativeSubHeaderConfig);
|
|
1671
|
+
[_nativeSubHeaderView updateConfigJSON:configJSON];
|
|
1672
|
+
_nativeSubHeaderHeight = _nativeSubHeaderView.preferredHeight;
|
|
1673
|
+
[self setNeedsLayout];
|
|
1674
|
+
}
|
|
1675
|
+
if (_needsPropsReapply ||
|
|
1676
|
+
oldViewProps.nativeTabBarHeight != newViewProps.nativeTabBarHeight ||
|
|
1677
|
+
oldViewProps.nativeTabBarContentPaddingHorizontal != newViewProps.nativeTabBarContentPaddingHorizontal ||
|
|
1678
|
+
oldViewProps.nativeTabBarItemSpacing != newViewProps.nativeTabBarItemSpacing ||
|
|
1679
|
+
oldViewProps.nativeTabBarFontSize != newViewProps.nativeTabBarFontSize ||
|
|
1680
|
+
oldViewProps.nativeTabBarFontFamily != newViewProps.nativeTabBarFontFamily ||
|
|
1681
|
+
oldViewProps.nativeTabBarBackgroundColor != newViewProps.nativeTabBarBackgroundColor ||
|
|
1682
|
+
oldViewProps.nativeTabBarActiveTextColor != newViewProps.nativeTabBarActiveTextColor ||
|
|
1683
|
+
oldViewProps.nativeTabBarInactiveTextColor != newViewProps.nativeTabBarInactiveTextColor ||
|
|
1684
|
+
oldViewProps.nativeTabBarIndicatorColor != newViewProps.nativeTabBarIndicatorColor ||
|
|
1685
|
+
oldViewProps.nativeTabBarIndicatorHeight != newViewProps.nativeTabBarIndicatorHeight ||
|
|
1686
|
+
oldViewProps.nativeTabBarIndicatorBottom != newViewProps.nativeTabBarIndicatorBottom ||
|
|
1687
|
+
oldViewProps.nativeSubHeaderSelectedBackgroundColor != newViewProps.nativeSubHeaderSelectedBackgroundColor) {
|
|
1688
|
+
_nativeTabBarHeight = newViewProps.nativeTabBarHeight > 0
|
|
1689
|
+
? newViewProps.nativeTabBarHeight
|
|
1690
|
+
: 44;
|
|
1691
|
+
NSString *fontFamily = RCTNSStringFromString(newViewProps.nativeTabBarFontFamily);
|
|
1692
|
+
[_nativeTabBarView
|
|
1693
|
+
updateStyleWithHeight:_nativeTabBarHeight
|
|
1694
|
+
contentPaddingHorizontal:MAX(0, newViewProps.nativeTabBarContentPaddingHorizontal)
|
|
1695
|
+
itemSpacing:MAX(0, newViewProps.nativeTabBarItemSpacing)
|
|
1696
|
+
fontSize:newViewProps.nativeTabBarFontSize > 0 ? newViewProps.nativeTabBarFontSize : 16
|
|
1697
|
+
fontFamily:fontFamily
|
|
1698
|
+
backgroundColor:RCTUIColorFromSharedColor(newViewProps.nativeTabBarBackgroundColor)
|
|
1699
|
+
activeTextColor:RCTUIColorFromSharedColor(newViewProps.nativeTabBarActiveTextColor)
|
|
1700
|
+
inactiveTextColor:RCTUIColorFromSharedColor(newViewProps.nativeTabBarInactiveTextColor)
|
|
1701
|
+
indicatorColor:RCTUIColorFromSharedColor(newViewProps.nativeTabBarIndicatorColor)
|
|
1702
|
+
indicatorHeight:MAX(0, newViewProps.nativeTabBarIndicatorHeight)
|
|
1703
|
+
indicatorBottom:MAX(0, newViewProps.nativeTabBarIndicatorBottom)];
|
|
1704
|
+
[_nativeSubHeaderView
|
|
1705
|
+
updateColorsWithBackgroundColor:RCTUIColorFromSharedColor(newViewProps.nativeTabBarBackgroundColor)
|
|
1706
|
+
activeTextColor:RCTUIColorFromSharedColor(newViewProps.nativeTabBarActiveTextColor)
|
|
1707
|
+
inactiveTextColor:RCTUIColorFromSharedColor(newViewProps.nativeTabBarInactiveTextColor)
|
|
1708
|
+
selectedBackgroundColor:RCTUIColorFromSharedColor(newViewProps.nativeSubHeaderSelectedBackgroundColor)
|
|
1709
|
+
fontFamily:fontFamily];
|
|
1710
|
+
[self setNeedsLayout];
|
|
1711
|
+
}
|
|
388
1712
|
|
|
389
1713
|
_needsPropsReapply = NO;
|
|
390
1714
|
[super updateProps:props oldProps:oldProps];
|
|
@@ -392,6 +1716,13 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
392
1716
|
[self reapplyInsetsToObservedScrollView];
|
|
393
1717
|
[self setNeedsLayout];
|
|
394
1718
|
}
|
|
1719
|
+
if (!_nativeTabBarView.hidden) {
|
|
1720
|
+
[_sharedHeaderHostView bringSubviewToFront:_nativeTabBarView];
|
|
1721
|
+
}
|
|
1722
|
+
if (!_nativeSubHeaderView.hidden) {
|
|
1723
|
+
[_sharedHeaderHostView bringSubviewToFront:_nativeSubHeaderView];
|
|
1724
|
+
}
|
|
1725
|
+
[_sharedHeaderHostView.superview bringSubviewToFront:_sharedHeaderHostView];
|
|
395
1726
|
}
|
|
396
1727
|
|
|
397
1728
|
#pragma mark - Collapsible scroll coordination
|
|
@@ -439,11 +1770,16 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
439
1770
|
if (_currentIndex < 0 || _currentIndex >= _pageControllers.count) return;
|
|
440
1771
|
UIScrollView *candidate = [self findVerticalScrollViewInView:_pageControllers[_currentIndex].view];
|
|
441
1772
|
if (candidate == nil) return;
|
|
442
|
-
//
|
|
443
|
-
|
|
1773
|
+
// Legacy mode keeps the historical pager-first dependency. Smooth mode
|
|
1774
|
+
// arbitrates header touches by axis so the vertical list can begin directly.
|
|
1775
|
+
if (_pagerScrollView != nil && !_nativeSmoothHeaderScrollEnabled && !_needsPropsReapply) {
|
|
444
1776
|
[candidate.panGestureRecognizer requireGestureRecognizerToFail:_pagerScrollView.panGestureRecognizer];
|
|
445
1777
|
}
|
|
446
|
-
if (candidate == _observedScrollView)
|
|
1778
|
+
if (candidate == _observedScrollView) {
|
|
1779
|
+
[self attachSharedHeadersToScrollView:candidate];
|
|
1780
|
+
[self updateSharedHeaderPressCancellationGesture];
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
447
1783
|
|
|
448
1784
|
BOOL replacingCurrentScrollView = _observingContentOffset;
|
|
449
1785
|
[self detachScrollObserver];
|
|
@@ -461,13 +1797,18 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
461
1797
|
forKeyPath:@"contentSize"
|
|
462
1798
|
options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
|
|
463
1799
|
context:RNCCollapsiblePagerContentOffsetContext];
|
|
1800
|
+
[candidate.panGestureRecognizer addTarget:self action:@selector(observedListPanChanged:)];
|
|
464
1801
|
_observingContentOffset = YES;
|
|
1802
|
+
_currentLogicalOffset = candidate.contentOffset.y + candidate.contentInset.top;
|
|
1803
|
+
[self attachSharedHeadersToScrollView:candidate];
|
|
1804
|
+
[self updateSharedHeaderPressCancellationGesture];
|
|
465
1805
|
}
|
|
466
1806
|
|
|
467
1807
|
- (void)detachScrollObserver
|
|
468
1808
|
{
|
|
469
1809
|
UIScrollView *scrollView = _observedScrollView;
|
|
470
1810
|
if (scrollView != nil) {
|
|
1811
|
+
[scrollView.panGestureRecognizer removeTarget:self action:@selector(observedListPanChanged:)];
|
|
471
1812
|
_pageOffsets[self.currentPageKey] = @(scrollView.contentOffset.y + scrollView.contentInset.top);
|
|
472
1813
|
if (_observingContentOffset) {
|
|
473
1814
|
[scrollView removeObserver:self
|
|
@@ -482,6 +1823,211 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
482
1823
|
_observedScrollView = nil;
|
|
483
1824
|
}
|
|
484
1825
|
|
|
1826
|
+
- (void)detachSharedHeaderPressCancellationGesture
|
|
1827
|
+
{
|
|
1828
|
+
if (_sharedHeaderPressCancellationGesture.view != nil) {
|
|
1829
|
+
[_sharedHeaderPressCancellationGesture.view
|
|
1830
|
+
removeGestureRecognizer:_sharedHeaderPressCancellationGesture];
|
|
1831
|
+
}
|
|
1832
|
+
if (_sharedHeaderOuterPagerGesture.view != nil) {
|
|
1833
|
+
[_sharedHeaderOuterPagerGesture.view
|
|
1834
|
+
removeGestureRecognizer:_sharedHeaderOuterPagerGesture];
|
|
1835
|
+
}
|
|
1836
|
+
_sharedHeaderOuterPagerGesture.blockedPagerGestures = nil;
|
|
1837
|
+
_sharedHeaderPressCancellationGestureHost = nil;
|
|
1838
|
+
_reactTouchHandler = nil;
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
- (void)connectVerticalPagerGuard
|
|
1842
|
+
{
|
|
1843
|
+
if (_pagerScrollView != nil && _verticalPagerGesture != nil) {
|
|
1844
|
+
[_pagerScrollView.panGestureRecognizer
|
|
1845
|
+
requireGestureRecognizerToFail:_verticalPagerGesture];
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
- (void)updateSharedHeaderPressCancellationGesture
|
|
1850
|
+
{
|
|
1851
|
+
if (!_nativeSmoothHeaderScrollEnabled || self.window == nil || _isBeingRecycled) {
|
|
1852
|
+
[self detachSharedHeaderPressCancellationGesture];
|
|
1853
|
+
return;
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
UIGestureRecognizer *reactTouchHandler = nil;
|
|
1857
|
+
for (UIView *ancestor = self; ancestor != nil; ancestor = ancestor.superview) {
|
|
1858
|
+
for (UIGestureRecognizer *gestureRecognizer in ancestor.gestureRecognizers) {
|
|
1859
|
+
if ([gestureRecognizer isKindOfClass:RCTSurfaceTouchHandler.class] ||
|
|
1860
|
+
[gestureRecognizer isKindOfClass:RCTTouchHandler.class]) {
|
|
1861
|
+
reactTouchHandler = gestureRecognizer;
|
|
1862
|
+
break;
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
if (reactTouchHandler != nil) break;
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
UIView *gestureHost = reactTouchHandler.view.superview;
|
|
1869
|
+
if (reactTouchHandler == nil || gestureHost == nil) {
|
|
1870
|
+
[self detachSharedHeaderPressCancellationGesture];
|
|
1871
|
+
RNCCollapsiblePagerLog(@"press-cancel-bridge attached=0 reason=touch-handler-missing");
|
|
1872
|
+
return;
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
NSHashTable<UIGestureRecognizer *> *outerPagerGestures = [NSHashTable weakObjectsHashTable];
|
|
1876
|
+
for (UIView *ancestor = self.superview;
|
|
1877
|
+
ancestor != nil;
|
|
1878
|
+
ancestor = ancestor.superview) {
|
|
1879
|
+
if (![ancestor isKindOfClass:UIScrollView.class]) continue;
|
|
1880
|
+
UIScrollView *scrollView = (UIScrollView *)ancestor;
|
|
1881
|
+
if (scrollView.pagingEnabled) {
|
|
1882
|
+
[outerPagerGestures addObject:scrollView.panGestureRecognizer];
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
if (_sharedHeaderPressCancellationGesture.view == gestureHost &&
|
|
1886
|
+
_sharedHeaderOuterPagerGesture.view == _sharedHeaderHostView &&
|
|
1887
|
+
_reactTouchHandler == reactTouchHandler) {
|
|
1888
|
+
_sharedHeaderOuterPagerGesture.blockedPagerGestures = outerPagerGestures;
|
|
1889
|
+
return;
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
[self detachSharedHeaderPressCancellationGesture];
|
|
1893
|
+
if (_sharedHeaderPressCancellationGesture == nil) {
|
|
1894
|
+
_sharedHeaderPressCancellationGesture =
|
|
1895
|
+
[[RNCCollapsiblePagerPressCancellationPanGestureRecognizer alloc]
|
|
1896
|
+
initWithTarget:self
|
|
1897
|
+
action:@selector(sharedHeaderPressCancellationGestureChanged:)];
|
|
1898
|
+
_sharedHeaderPressCancellationGesture.delegate = self;
|
|
1899
|
+
_sharedHeaderPressCancellationGesture.cancelsTouchesInView = NO;
|
|
1900
|
+
_sharedHeaderPressCancellationGesture.maximumNumberOfTouches = 1;
|
|
1901
|
+
}
|
|
1902
|
+
if (_sharedHeaderOuterPagerGesture == nil) {
|
|
1903
|
+
_sharedHeaderOuterPagerGesture =
|
|
1904
|
+
[[RNCCollapsiblePagerOuterPagerPanGestureRecognizer alloc]
|
|
1905
|
+
initWithTarget:self
|
|
1906
|
+
action:@selector(outerPagerGestureChanged:)];
|
|
1907
|
+
_sharedHeaderOuterPagerGesture.delegate = self;
|
|
1908
|
+
_sharedHeaderOuterPagerGesture.cancelsTouchesInView = YES;
|
|
1909
|
+
_sharedHeaderOuterPagerGesture.maximumNumberOfTouches = 1;
|
|
1910
|
+
}
|
|
1911
|
+
_sharedHeaderOuterPagerGesture.blockedPagerGestures = outerPagerGestures;
|
|
1912
|
+
_reactTouchHandler = reactTouchHandler;
|
|
1913
|
+
_sharedHeaderPressCancellationGestureHost = gestureHost;
|
|
1914
|
+
[gestureHost addGestureRecognizer:_sharedHeaderPressCancellationGesture];
|
|
1915
|
+
[_sharedHeaderHostView addGestureRecognizer:_sharedHeaderOuterPagerGesture];
|
|
1916
|
+
RNCCollapsiblePagerLog([NSString stringWithFormat:
|
|
1917
|
+
@"press-cancel-bridge attached=1 outer-pagers=%lu",
|
|
1918
|
+
(unsigned long)outerPagerGestures.count]);
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
- (void)sharedHeaderPressCancellationGestureChanged:(UIPanGestureRecognizer *)recognizer
|
|
1922
|
+
{
|
|
1923
|
+
if (recognizer.state == UIGestureRecognizerStateBegan) {
|
|
1924
|
+
RNCCollapsiblePagerLog([NSString stringWithFormat:
|
|
1925
|
+
@"press-cancel-owner=header page=%ld touch-handler-state=%ld",
|
|
1926
|
+
(long)_currentIndex,
|
|
1927
|
+
(long)_reactTouchHandler.state]);
|
|
1928
|
+
} else if (recognizer.state == UIGestureRecognizerStateEnded ||
|
|
1929
|
+
recognizer.state == UIGestureRecognizerStateCancelled ||
|
|
1930
|
+
recognizer.state == UIGestureRecognizerStateFailed) {
|
|
1931
|
+
RNCCollapsiblePagerLog([NSString stringWithFormat:
|
|
1932
|
+
@"press-cancel-end page=%ld state=%ld touch-handler-state=%ld",
|
|
1933
|
+
(long)_currentIndex,
|
|
1934
|
+
(long)recognizer.state,
|
|
1935
|
+
(long)_reactTouchHandler.state]);
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
- (void)outerPagerGestureChanged:(UIPanGestureRecognizer *)recognizer
|
|
1940
|
+
{
|
|
1941
|
+
if (recognizer.state == UIGestureRecognizerStateBegan) {
|
|
1942
|
+
NSMutableArray<NSString *> *blockedStates = [NSMutableArray new];
|
|
1943
|
+
for (UIGestureRecognizer *pagerGesture in
|
|
1944
|
+
_sharedHeaderOuterPagerGesture.blockedPagerGestures) {
|
|
1945
|
+
[blockedStates addObject:[NSString stringWithFormat:
|
|
1946
|
+
@"%@:%ld",
|
|
1947
|
+
NSStringFromClass(pagerGesture.view.class),
|
|
1948
|
+
(long)pagerGesture.state]];
|
|
1949
|
+
}
|
|
1950
|
+
RNCCollapsiblePagerLog([NSString stringWithFormat:
|
|
1951
|
+
@"gesture-owner=header-outer-pager-block page=%ld blocked=[%@] rct=%ld list=%ld inner-pager=%ld",
|
|
1952
|
+
(long)_currentIndex,
|
|
1953
|
+
[blockedStates componentsJoinedByString:@","],
|
|
1954
|
+
(long)_reactTouchHandler.state,
|
|
1955
|
+
(long)_observedScrollView.panGestureRecognizer.state,
|
|
1956
|
+
(long)_pagerScrollView.panGestureRecognizer.state]);
|
|
1957
|
+
} else if (recognizer.state == UIGestureRecognizerStateEnded ||
|
|
1958
|
+
recognizer.state == UIGestureRecognizerStateCancelled ||
|
|
1959
|
+
recognizer.state == UIGestureRecognizerStateFailed) {
|
|
1960
|
+
RNCCollapsiblePagerLog([NSString stringWithFormat:
|
|
1961
|
+
@"gesture-end=header-outer-pager-block page=%ld state=%ld",
|
|
1962
|
+
(long)_currentIndex,
|
|
1963
|
+
(long)recognizer.state]);
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
- (void)verticalPagerGuardGestureChanged:(UIPanGestureRecognizer *)recognizer
|
|
1968
|
+
{
|
|
1969
|
+
if (recognizer.state == UIGestureRecognizerStateBegan) {
|
|
1970
|
+
RNCCollapsiblePagerLog([NSString stringWithFormat:
|
|
1971
|
+
@"gesture-owner=vertical-pager-guard page=%ld list=%ld pager=%ld",
|
|
1972
|
+
(long)_currentIndex,
|
|
1973
|
+
(long)_observedScrollView.panGestureRecognizer.state,
|
|
1974
|
+
(long)_pagerScrollView.panGestureRecognizer.state]);
|
|
1975
|
+
} else if (recognizer.state == UIGestureRecognizerStateEnded ||
|
|
1976
|
+
recognizer.state == UIGestureRecognizerStateCancelled ||
|
|
1977
|
+
recognizer.state == UIGestureRecognizerStateFailed) {
|
|
1978
|
+
RNCCollapsiblePagerLog([NSString stringWithFormat:
|
|
1979
|
+
@"gesture-end=vertical-pager-guard page=%ld state=%ld",
|
|
1980
|
+
(long)_currentIndex,
|
|
1981
|
+
(long)recognizer.state]);
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
- (void)observedListPanChanged:(UIPanGestureRecognizer *)recognizer
|
|
1986
|
+
{
|
|
1987
|
+
if (!_nativeSmoothHeaderScrollEnabled || recognizer != _observedScrollView.panGestureRecognizer) {
|
|
1988
|
+
return;
|
|
1989
|
+
}
|
|
1990
|
+
if (recognizer.state == UIGestureRecognizerStateBegan) {
|
|
1991
|
+
_observedListPanStartLogicalOffset =
|
|
1992
|
+
_observedScrollView.contentOffset.y + _observedScrollView.contentInset.top;
|
|
1993
|
+
RNCCollapsiblePagerLog([NSString stringWithFormat:
|
|
1994
|
+
@"gesture-owner=list page=%ld logical=%.2f content-height=%.2f "
|
|
1995
|
+
"bounds-height=%.2f inset=(%.2f,%.2f) enabled=%d dragging=%d",
|
|
1996
|
+
(long)_currentIndex,
|
|
1997
|
+
_observedListPanStartLogicalOffset,
|
|
1998
|
+
_observedScrollView.contentSize.height,
|
|
1999
|
+
_observedScrollView.bounds.size.height,
|
|
2000
|
+
_observedScrollView.contentInset.top,
|
|
2001
|
+
_observedScrollView.contentInset.bottom,
|
|
2002
|
+
_observedScrollView.isScrollEnabled,
|
|
2003
|
+
_observedScrollView.isDragging]);
|
|
2004
|
+
} else if (recognizer.state == UIGestureRecognizerStateEnded ||
|
|
2005
|
+
recognizer.state == UIGestureRecognizerStateCancelled ||
|
|
2006
|
+
recognizer.state == UIGestureRecognizerStateFailed) {
|
|
2007
|
+
CGFloat minimumOffset = -_observedScrollView.contentInset.top;
|
|
2008
|
+
CGFloat maximumOffset = MAX(
|
|
2009
|
+
minimumOffset,
|
|
2010
|
+
_observedScrollView.contentSize.height - _observedScrollView.bounds.size.height +
|
|
2011
|
+
_observedScrollView.contentInset.bottom
|
|
2012
|
+
);
|
|
2013
|
+
RNCCollapsiblePagerLog([NSString stringWithFormat:
|
|
2014
|
+
@"gesture-end=list page=%ld state=%ld logical-start=%.2f "
|
|
2015
|
+
"logical-end=%.2f header=%.2f range=(%.2f,%.2f) "
|
|
2016
|
+
"content-height=%.2f bounds-height=%.2f inset=(%.2f,%.2f)",
|
|
2017
|
+
(long)_currentIndex,
|
|
2018
|
+
(long)recognizer.state,
|
|
2019
|
+
_observedListPanStartLogicalOffset,
|
|
2020
|
+
_observedScrollView.contentOffset.y + _observedScrollView.contentInset.top,
|
|
2021
|
+
_headerOffset,
|
|
2022
|
+
minimumOffset,
|
|
2023
|
+
maximumOffset,
|
|
2024
|
+
_observedScrollView.contentSize.height,
|
|
2025
|
+
_observedScrollView.bounds.size.height,
|
|
2026
|
+
_observedScrollView.contentInset.top,
|
|
2027
|
+
_observedScrollView.contentInset.bottom]);
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
|
|
485
2031
|
- (void)restoreDetachedScrollInsets
|
|
486
2032
|
{
|
|
487
2033
|
for (UIScrollView *scrollView in _originalInsets.keyEnumerator.allObjects) {
|
|
@@ -574,7 +2120,13 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
574
2120
|
NSNumber *saved = _pageOffsets[[self pageKeyForIndex:pageIndex]];
|
|
575
2121
|
// The sticky slot can change height between pages. Cache offsets relative
|
|
576
2122
|
// to content start so a round trip does not add that height difference.
|
|
577
|
-
|
|
2123
|
+
CGFloat restoredLogicalOffset = saved == nil ? _headerOffset : saved.doubleValue;
|
|
2124
|
+
// While the shared header is visible, retained pages must agree on its
|
|
2125
|
+
// collapse position. Deep offsets remain independent after full collapse.
|
|
2126
|
+
if (_nativeSmoothHeaderScrollEnabled && _headerOffset < _headerHeight) {
|
|
2127
|
+
restoredLogicalOffset = _headerOffset;
|
|
2128
|
+
}
|
|
2129
|
+
targetOffset = restoredLogicalOffset - next.top;
|
|
578
2130
|
targetOffset = MAX(targetOffset, -next.top + _headerOffset);
|
|
579
2131
|
}
|
|
580
2132
|
// A bottom-inset change must not cancel UIKit's refresh or bounce settlement.
|
|
@@ -623,7 +2175,8 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
623
2175
|
return;
|
|
624
2176
|
}
|
|
625
2177
|
UIScrollView *scrollView = (UIScrollView *)object;
|
|
626
|
-
|
|
2178
|
+
_currentLogicalOffset = scrollView.contentOffset.y + scrollView.contentInset.top;
|
|
2179
|
+
_pageOffsets[self.currentPageKey] = @(_currentLogicalOffset);
|
|
627
2180
|
[self updateHeaderForScrollView:scrollView];
|
|
628
2181
|
return;
|
|
629
2182
|
}
|
|
@@ -633,15 +2186,31 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
633
2186
|
- (void)updateHeaderForScrollView:(UIScrollView *)scrollView
|
|
634
2187
|
{
|
|
635
2188
|
CGFloat logicalOffset = scrollView.contentOffset.y + scrollView.contentInset.top;
|
|
2189
|
+
_currentLogicalOffset = logicalOffset;
|
|
636
2190
|
_headerOffset = MIN(MAX(logicalOffset, 0), _headerHeight);
|
|
637
2191
|
[self applyHeaderOffset];
|
|
638
2192
|
}
|
|
639
2193
|
|
|
640
2194
|
- (void)applyHeaderOffset
|
|
641
2195
|
{
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
2196
|
+
BOOL attachedToList = _nativeSmoothHeaderScrollEnabled &&
|
|
2197
|
+
_sharedHeaderScrollView != nil && !_sharedHeadersLiftedForPagerTransition;
|
|
2198
|
+
if (attachedToList) {
|
|
2199
|
+
_sharedHeaderHostView.transform = CGAffineTransformIdentity;
|
|
2200
|
+
_headerView.transform = CGAffineTransformIdentity;
|
|
2201
|
+
CGFloat pinnedTranslation = MAX(0, _currentLogicalOffset - _headerHeight);
|
|
2202
|
+
CGAffineTransform stickyTransform = CGAffineTransformMakeTranslation(0, pinnedTranslation);
|
|
2203
|
+
_stickyHeaderView.transform = stickyTransform;
|
|
2204
|
+
_nativeTabBarView.transform = stickyTransform;
|
|
2205
|
+
_nativeSubHeaderView.transform = stickyTransform;
|
|
2206
|
+
return;
|
|
2207
|
+
}
|
|
2208
|
+
CGAffineTransform collapseTransform = CGAffineTransformMakeTranslation(0, -_headerOffset);
|
|
2209
|
+
_sharedHeaderHostView.transform = collapseTransform;
|
|
2210
|
+
_headerView.transform = CGAffineTransformIdentity;
|
|
2211
|
+
_stickyHeaderView.transform = CGAffineTransformIdentity;
|
|
2212
|
+
_nativeTabBarView.transform = CGAffineTransformIdentity;
|
|
2213
|
+
_nativeSubHeaderView.transform = CGAffineTransformIdentity;
|
|
645
2214
|
}
|
|
646
2215
|
|
|
647
2216
|
#pragma mark - Pager navigation
|
|
@@ -656,25 +2225,32 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
656
2225
|
if (_isBeingRecycled || index < 0 || index >= _pageControllers.count) return;
|
|
657
2226
|
_hasReceivedPageCommand = YES;
|
|
658
2227
|
_hasAppliedInitialPage = YES;
|
|
659
|
-
if (_transitioning
|
|
2228
|
+
if (_transitioning || _isPagerDragging) {
|
|
660
2229
|
_pendingGoToIndex = index;
|
|
2230
|
+
_pendingGoToAnimated = animated;
|
|
661
2231
|
return;
|
|
662
2232
|
}
|
|
663
2233
|
|
|
664
2234
|
_pendingGoToIndex = -1;
|
|
2235
|
+
_pendingGoToAnimated = YES;
|
|
665
2236
|
|
|
666
2237
|
// Re-selecting the displayed controller removes its view briefly and cancels
|
|
667
2238
|
// an in-flight list touch. Focus synchronization must be idempotent.
|
|
2239
|
+
BOOL currentControllerDisplayed = _directNativePagerEnabled
|
|
2240
|
+
? fabs(_pagerScrollView.contentOffset.x - [self directPagerOffsetForIndex:index]) < 0.5
|
|
2241
|
+
: _pageViewController.viewControllers.firstObject == _pageControllers[index];
|
|
668
2242
|
if (!_transitioning && index == _currentIndex &&
|
|
669
|
-
|
|
2243
|
+
currentControllerDisplayed) {
|
|
670
2244
|
[self attachScrollObserverForCurrentPage];
|
|
671
2245
|
[self emitPageSelected:index];
|
|
672
2246
|
[self emitDiagnostics:@"page-selected"];
|
|
673
2247
|
return;
|
|
674
2248
|
}
|
|
675
2249
|
|
|
676
|
-
[self
|
|
2250
|
+
[self preparePageForHorizontalTransitionAtIndex:index];
|
|
677
2251
|
_destinationIndex = index;
|
|
2252
|
+
[self liftSharedHeadersForPagerTransition:@"programmatic"];
|
|
2253
|
+
[self detachScrollObserver];
|
|
678
2254
|
BOOL forward = (index > _currentIndex && self.isLtrLayout) ||
|
|
679
2255
|
(index < _currentIndex && !self.isLtrLayout);
|
|
680
2256
|
UIPageViewControllerNavigationDirection direction = forward
|
|
@@ -685,6 +2261,47 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
685
2261
|
NSUInteger capturedTransition = ++_transitionId;
|
|
686
2262
|
NSUInteger capturedGeneration = _generation;
|
|
687
2263
|
__weak __typeof__(self) weakSelf = self;
|
|
2264
|
+
if (_directNativePagerEnabled) {
|
|
2265
|
+
CGFloat targetOffset = [self directPagerOffsetForIndex:index];
|
|
2266
|
+
void (^animations)(void) = ^{
|
|
2267
|
+
[self->_pagerScrollView setContentOffset:CGPointMake(targetOffset, 0)];
|
|
2268
|
+
[self->_nativeTabBarView setProgress:index];
|
|
2269
|
+
};
|
|
2270
|
+
void (^completion)(BOOL) = ^(BOOL finished) {
|
|
2271
|
+
__strong __typeof__(weakSelf) self = weakSelf;
|
|
2272
|
+
if (self == nil || self->_generation != capturedGeneration ||
|
|
2273
|
+
self->_transitionId != capturedTransition || self->_isBeingRecycled) return;
|
|
2274
|
+
self->_pagerScrollView.scrollEnabled = self->_scrollEnabled;
|
|
2275
|
+
if (finished) {
|
|
2276
|
+
self->_currentIndex = index;
|
|
2277
|
+
self->_destinationIndex = index;
|
|
2278
|
+
[self->_nativeTabBarView setProgress:index];
|
|
2279
|
+
[self attachScrollObserverForCurrentPage];
|
|
2280
|
+
[self emitPageSelected:index];
|
|
2281
|
+
[self emitDiagnostics:@"page-selected"];
|
|
2282
|
+
} else if (self->_pendingGoToIndex < 0) {
|
|
2283
|
+
self->_pendingGoToIndex = index;
|
|
2284
|
+
self->_pendingGoToAnimated = animated;
|
|
2285
|
+
}
|
|
2286
|
+
[self completeTransitionOnNextRunLoopForGeneration:capturedGeneration
|
|
2287
|
+
transition:capturedTransition];
|
|
2288
|
+
};
|
|
2289
|
+
|
|
2290
|
+
if (animated && index != _currentIndex) {
|
|
2291
|
+
_pagerScrollView.scrollEnabled = NO;
|
|
2292
|
+
[UIView animateWithDuration:0.28
|
|
2293
|
+
delay:0
|
|
2294
|
+
options:UIViewAnimationOptionCurveEaseInOut |
|
|
2295
|
+
UIViewAnimationOptionAllowUserInteraction
|
|
2296
|
+
animations:animations
|
|
2297
|
+
completion:completion];
|
|
2298
|
+
} else {
|
|
2299
|
+
[UIView performWithoutAnimation:animations];
|
|
2300
|
+
completion(YES);
|
|
2301
|
+
}
|
|
2302
|
+
return;
|
|
2303
|
+
}
|
|
2304
|
+
|
|
688
2305
|
[_pageViewController setViewControllers:@[_pageControllers[index]]
|
|
689
2306
|
direction:direction
|
|
690
2307
|
animated:animated && index != _currentIndex
|
|
@@ -694,21 +2311,45 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
694
2311
|
self->_transitionId != capturedTransition || self->_isBeingRecycled) return;
|
|
695
2312
|
if (finished) {
|
|
696
2313
|
self->_currentIndex = index;
|
|
2314
|
+
self->_destinationIndex = index;
|
|
2315
|
+
[self->_nativeTabBarView setProgress:index];
|
|
697
2316
|
[self attachScrollObserverForCurrentPage];
|
|
698
2317
|
[self emitPageSelected:index];
|
|
699
2318
|
[self emitDiagnostics:@"page-selected"];
|
|
2319
|
+
} else {
|
|
2320
|
+
[self->_nativeTabBarView setProgress:self->_currentIndex];
|
|
2321
|
+
if (self->_pendingGoToIndex < 0) {
|
|
2322
|
+
self->_pendingGoToIndex = self->_currentIndex;
|
|
2323
|
+
self->_pendingGoToAnimated = NO;
|
|
2324
|
+
}
|
|
700
2325
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
704
|
-
if (self->_generation != capturedGeneration ||
|
|
705
|
-
self->_transitionId != capturedTransition || self->_isBeingRecycled) return;
|
|
706
|
-
self->_transitioning = NO;
|
|
707
|
-
[self drainPendingGoTo];
|
|
708
|
-
});
|
|
2326
|
+
[self completeTransitionOnNextRunLoopForGeneration:capturedGeneration
|
|
2327
|
+
transition:capturedTransition];
|
|
709
2328
|
}];
|
|
710
2329
|
}
|
|
711
2330
|
|
|
2331
|
+
- (void)completeTransitionOnNextRunLoopForGeneration:(NSUInteger)generation
|
|
2332
|
+
transition:(NSUInteger)transition
|
|
2333
|
+
{
|
|
2334
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
2335
|
+
if (self->_generation != generation || self->_transitionId != transition ||
|
|
2336
|
+
self->_isBeingRecycled) return;
|
|
2337
|
+
self->_transitioning = NO;
|
|
2338
|
+
if (self->_hasPendingDirectNativePagerChange) {
|
|
2339
|
+
BOOL enabled = self->_pendingDirectNativePagerEnabled;
|
|
2340
|
+
self->_hasPendingDirectNativePagerChange = NO;
|
|
2341
|
+
[self setDirectNativePagerEnabled:enabled];
|
|
2342
|
+
} else if (self->_needsSlotRebuild) {
|
|
2343
|
+
[self rebuildSlots];
|
|
2344
|
+
}
|
|
2345
|
+
if (!self->_hasAppliedInitialPage) [self setNeedsLayout];
|
|
2346
|
+
[self drainPendingGoTo];
|
|
2347
|
+
if (!self->_transitioning && !self->_needsSlotRebuild) {
|
|
2348
|
+
[self attachScrollObserverForCurrentPage];
|
|
2349
|
+
}
|
|
2350
|
+
});
|
|
2351
|
+
}
|
|
2352
|
+
|
|
712
2353
|
- (UIViewController *)adjacentController:(UIViewController *)controller delta:(NSInteger)delta
|
|
713
2354
|
{
|
|
714
2355
|
NSInteger index = [_pageControllers indexOfObjectIdenticalTo:controller];
|
|
@@ -737,9 +2378,18 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
737
2378
|
- (void)drainPendingGoTo
|
|
738
2379
|
{
|
|
739
2380
|
NSInteger pending = _pendingGoToIndex;
|
|
2381
|
+
BOOL animated = _pendingGoToAnimated;
|
|
740
2382
|
_pendingGoToIndex = -1;
|
|
741
|
-
|
|
742
|
-
|
|
2383
|
+
_pendingGoToAnimated = YES;
|
|
2384
|
+
BOOL currentControllerNeedsRestore = NO;
|
|
2385
|
+
if (_currentIndex >= 0 && _currentIndex < _pageControllers.count) {
|
|
2386
|
+
currentControllerNeedsRestore = _directNativePagerEnabled
|
|
2387
|
+
? fabs(_pagerScrollView.contentOffset.x -
|
|
2388
|
+
[self directPagerOffsetForIndex:_currentIndex]) >= 0.5
|
|
2389
|
+
: _pageViewController.viewControllers.firstObject != _pageControllers[_currentIndex];
|
|
2390
|
+
}
|
|
2391
|
+
if (pending >= 0 && (pending != _currentIndex || currentControllerNeedsRestore)) {
|
|
2392
|
+
[self goTo:pending animated:animated];
|
|
743
2393
|
}
|
|
744
2394
|
}
|
|
745
2395
|
|
|
@@ -747,10 +2397,21 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
747
2397
|
|
|
748
2398
|
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
|
|
749
2399
|
{
|
|
2400
|
+
if (_directNativePagerEnabled) {
|
|
2401
|
+
[self preparePageForHorizontalTransitionAtIndex:_currentIndex - 1];
|
|
2402
|
+
[self preparePageForHorizontalTransitionAtIndex:_currentIndex + 1];
|
|
2403
|
+
}
|
|
750
2404
|
// A user drag owns the result even if UIKit later completes the old animation.
|
|
751
2405
|
++_transitionId;
|
|
2406
|
+
[self liftSharedHeadersForPagerTransition:@"interactive"];
|
|
2407
|
+
[self detachScrollObserver];
|
|
752
2408
|
_isPagerDragging = YES;
|
|
753
2409
|
_transitioning = YES;
|
|
2410
|
+
if (_nativeSmoothHeaderScrollEnabled) {
|
|
2411
|
+
RNCCollapsiblePagerLog([NSString stringWithFormat:
|
|
2412
|
+
@"gesture-owner=pager page=%ld",
|
|
2413
|
+
(long)_currentIndex]);
|
|
2414
|
+
}
|
|
754
2415
|
const auto emitter = [self eventEmitter];
|
|
755
2416
|
if (emitter) {
|
|
756
2417
|
emitter->onPageScrollStateChanged({
|
|
@@ -779,6 +2440,43 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
779
2440
|
|
|
780
2441
|
- (void)finishPagerScrollEmittingSelection:(BOOL)emitSelection
|
|
781
2442
|
{
|
|
2443
|
+
if (_directNativePagerEnabled) {
|
|
2444
|
+
NSInteger index = emitSelection
|
|
2445
|
+
? (NSInteger)llround([self directPagerProgress])
|
|
2446
|
+
: _currentIndex;
|
|
2447
|
+
index = MAX(0, MIN(index, (NSInteger)_pageControllers.count - 1));
|
|
2448
|
+
[self detachScrollObserver];
|
|
2449
|
+
_currentIndex = index;
|
|
2450
|
+
_destinationIndex = index;
|
|
2451
|
+
[_pagerScrollView setContentOffset:CGPointMake(
|
|
2452
|
+
[self directPagerOffsetForIndex:index],
|
|
2453
|
+
0
|
|
2454
|
+
) animated:NO];
|
|
2455
|
+
[_nativeTabBarView setProgress:index];
|
|
2456
|
+
[self attachScrollObserverForCurrentPage];
|
|
2457
|
+
if (emitSelection &&
|
|
2458
|
+
(_pendingGoToIndex < 0 || _pendingGoToIndex == index)) {
|
|
2459
|
+
[self emitPageSelected:index];
|
|
2460
|
+
[self emitDiagnostics:@"page-selected"];
|
|
2461
|
+
}
|
|
2462
|
+
_isPagerDragging = NO;
|
|
2463
|
+
if (_nativeSmoothHeaderScrollEnabled) {
|
|
2464
|
+
RNCCollapsiblePagerLog([NSString stringWithFormat:
|
|
2465
|
+
@"gesture-end=pager page=%ld",
|
|
2466
|
+
(long)_currentIndex]);
|
|
2467
|
+
}
|
|
2468
|
+
const auto emitter = [self eventEmitter];
|
|
2469
|
+
if (emitter) {
|
|
2470
|
+
emitter->onPageScrollStateChanged({
|
|
2471
|
+
.pageScrollState = RNCCollapsiblePagerViewEventEmitter::OnPageScrollStateChangedPageScrollState::Idle
|
|
2472
|
+
});
|
|
2473
|
+
}
|
|
2474
|
+
[self emitDiagnostics:@"pager-idle"];
|
|
2475
|
+
[self completeTransitionOnNextRunLoopForGeneration:_generation
|
|
2476
|
+
transition:_transitionId];
|
|
2477
|
+
return;
|
|
2478
|
+
}
|
|
2479
|
+
|
|
782
2480
|
if (_isPagerDragging) {
|
|
783
2481
|
UIViewController *controller = nil;
|
|
784
2482
|
if (!emitSelection && _currentIndex >= 0 && _currentIndex < _pageControllers.count) {
|
|
@@ -801,16 +2499,11 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
801
2499
|
}
|
|
802
2500
|
NSInteger index = [_pageControllers indexOfObjectIdenticalTo:controller];
|
|
803
2501
|
if (index != NSNotFound) {
|
|
804
|
-
|
|
805
|
-
// Correct UIKit only after its animation completion has returned.
|
|
806
|
-
[_pageViewController setViewControllers:@[controller]
|
|
807
|
-
direction:UIPageViewControllerNavigationDirectionForward
|
|
808
|
-
animated:NO
|
|
809
|
-
completion:nil];
|
|
810
|
-
}
|
|
2502
|
+
BOOL controllerNeedsRestore = _pageViewController.viewControllers.firstObject != controller;
|
|
811
2503
|
[self detachScrollObserver];
|
|
812
2504
|
_currentIndex = index;
|
|
813
2505
|
_destinationIndex = index;
|
|
2506
|
+
[_nativeTabBarView setProgress:index];
|
|
814
2507
|
[self attachScrollObserverForCurrentPage];
|
|
815
2508
|
// A newer tab tap remains authoritative while its queued command runs.
|
|
816
2509
|
if (emitSelection &&
|
|
@@ -818,9 +2511,17 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
818
2511
|
[self emitPageSelected:index];
|
|
819
2512
|
[self emitDiagnostics:@"page-selected"];
|
|
820
2513
|
}
|
|
2514
|
+
if (controllerNeedsRestore && _pendingGoToIndex < 0) {
|
|
2515
|
+
_pendingGoToIndex = index;
|
|
2516
|
+
_pendingGoToAnimated = NO;
|
|
2517
|
+
}
|
|
821
2518
|
}
|
|
822
2519
|
_isPagerDragging = NO;
|
|
823
|
-
|
|
2520
|
+
}
|
|
2521
|
+
if (_nativeSmoothHeaderScrollEnabled) {
|
|
2522
|
+
RNCCollapsiblePagerLog([NSString stringWithFormat:
|
|
2523
|
+
@"gesture-end=pager page=%ld",
|
|
2524
|
+
(long)_currentIndex]);
|
|
824
2525
|
}
|
|
825
2526
|
const auto emitter = [self eventEmitter];
|
|
826
2527
|
if (emitter) {
|
|
@@ -829,7 +2530,8 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
829
2530
|
});
|
|
830
2531
|
}
|
|
831
2532
|
[self emitDiagnostics:@"pager-idle"];
|
|
832
|
-
[self
|
|
2533
|
+
[self completeTransitionOnNextRunLoopForGeneration:_generation
|
|
2534
|
+
transition:_transitionId];
|
|
833
2535
|
}
|
|
834
2536
|
|
|
835
2537
|
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
|
|
@@ -841,11 +2543,29 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
841
2543
|
{
|
|
842
2544
|
CGFloat width = scrollView.bounds.size.width;
|
|
843
2545
|
if (width <= 0) return;
|
|
2546
|
+
if (_directNativePagerEnabled && scrollView == _pagerScrollView) {
|
|
2547
|
+
CGFloat progress = [self directPagerProgress];
|
|
2548
|
+
NSInteger position = (NSInteger)floor(progress);
|
|
2549
|
+
CGFloat offset = progress - position;
|
|
2550
|
+
position = MAX(0, MIN(position, (NSInteger)_pageControllers.count - 1));
|
|
2551
|
+
[_nativeTabBarView setProgress:progress];
|
|
2552
|
+
const auto emitter = [self eventEmitter];
|
|
2553
|
+
if (emitter) {
|
|
2554
|
+
emitter->onPageScroll({.position = (double)position, .offset = (double)offset});
|
|
2555
|
+
}
|
|
2556
|
+
return;
|
|
2557
|
+
}
|
|
844
2558
|
CGFloat rawOffset = (scrollView.contentOffset.x - width) / width;
|
|
845
2559
|
BOOL backwards = self.isLtrLayout ? rawOffset < 0 : rawOffset > 0;
|
|
846
2560
|
NSInteger position = backwards ? _currentIndex - 1 : _currentIndex;
|
|
847
2561
|
CGFloat offset = backwards ? 1 - fabs(rawOffset) : fabs(rawOffset);
|
|
848
2562
|
position = MAX(0, MIN(position, (NSInteger)_pageControllers.count - 1));
|
|
2563
|
+
CGFloat tabProgress = position + offset;
|
|
2564
|
+
if (_transitioning && !_isPagerDragging && _destinationIndex != _currentIndex) {
|
|
2565
|
+
CGFloat transitionProgress = RNCClamp(fabs(rawOffset), 0, 1);
|
|
2566
|
+
tabProgress = _currentIndex + (_destinationIndex - _currentIndex) * transitionProgress;
|
|
2567
|
+
}
|
|
2568
|
+
[_nativeTabBarView setProgress:tabProgress];
|
|
849
2569
|
const auto emitter = [self eventEmitter];
|
|
850
2570
|
if (emitter) {
|
|
851
2571
|
emitter->onPageScroll({.position = (double)position, .offset = (double)offset});
|
|
@@ -939,6 +2659,52 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
939
2659
|
|
|
940
2660
|
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
|
|
941
2661
|
{
|
|
2662
|
+
if (gestureRecognizer == _sharedHeaderOuterPagerGesture) {
|
|
2663
|
+
UIPanGestureRecognizer *pan = (UIPanGestureRecognizer *)gestureRecognizer;
|
|
2664
|
+
CGPoint translation = [pan translationInView:pan.view];
|
|
2665
|
+
CGPoint velocity = [pan velocityInView:pan.view];
|
|
2666
|
+
CGPoint intent = fabs(translation.x) + fabs(translation.y) >= 1
|
|
2667
|
+
? translation
|
|
2668
|
+
: velocity;
|
|
2669
|
+
BOOL hasMotion = MAX(fabs(intent.x), fabs(intent.y)) >= 1;
|
|
2670
|
+
BOOL horizontal = hasMotion && fabs(intent.x) > fabs(intent.y);
|
|
2671
|
+
RNCCollapsiblePagerLog([NSString stringWithFormat:
|
|
2672
|
+
@"header-axis role=outer-pager-block dx=%.2f dy=%.2f velocity=(%.2f,%.2f) result=%d outer-pagers=%lu",
|
|
2673
|
+
intent.x,
|
|
2674
|
+
intent.y,
|
|
2675
|
+
velocity.x,
|
|
2676
|
+
velocity.y,
|
|
2677
|
+
horizontal,
|
|
2678
|
+
(unsigned long)_sharedHeaderOuterPagerGesture.blockedPagerGestures.count]);
|
|
2679
|
+
return horizontal;
|
|
2680
|
+
}
|
|
2681
|
+
if (gestureRecognizer == _sharedHeaderPressCancellationGesture ||
|
|
2682
|
+
gestureRecognizer == _verticalPagerGesture) {
|
|
2683
|
+
UIPanGestureRecognizer *pan = (UIPanGestureRecognizer *)gestureRecognizer;
|
|
2684
|
+
CGPoint translation = [pan translationInView:pan.view];
|
|
2685
|
+
CGPoint velocity = [pan velocityInView:pan.view];
|
|
2686
|
+
CGPoint intent = fabs(translation.x) + fabs(translation.y) >= 1
|
|
2687
|
+
? translation
|
|
2688
|
+
: velocity;
|
|
2689
|
+
BOOL hasMotion = MAX(fabs(intent.x), fabs(intent.y)) >= 1;
|
|
2690
|
+
BOOL vertical = hasMotion && fabs(intent.y) > fabs(intent.x);
|
|
2691
|
+
NSString *role = gestureRecognizer == _sharedHeaderPressCancellationGesture
|
|
2692
|
+
? @"vertical-cancel"
|
|
2693
|
+
: @"vertical-pager-guard";
|
|
2694
|
+
RNCCollapsiblePagerLog([NSString stringWithFormat:
|
|
2695
|
+
@"header-axis role=%@ dx=%.2f dy=%.2f velocity=(%.2f,%.2f) "
|
|
2696
|
+
"result=%d rct=%ld list=%ld pager=%ld",
|
|
2697
|
+
role,
|
|
2698
|
+
intent.x,
|
|
2699
|
+
intent.y,
|
|
2700
|
+
velocity.x,
|
|
2701
|
+
velocity.y,
|
|
2702
|
+
vertical,
|
|
2703
|
+
(long)_reactTouchHandler.state,
|
|
2704
|
+
(long)_observedScrollView.panGestureRecognizer.state,
|
|
2705
|
+
(long)_pagerScrollView.panGestureRecognizer.state]);
|
|
2706
|
+
return vertical;
|
|
2707
|
+
}
|
|
942
2708
|
if (gestureRecognizer != _blockerGesture) return YES;
|
|
943
2709
|
if (!_nestedScrollEnabled) return NO;
|
|
944
2710
|
if (![gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) return NO;
|
|
@@ -962,9 +2728,41 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
962
2728
|
|
|
963
2729
|
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
|
|
964
2730
|
{
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
2731
|
+
if (gestureRecognizer == _sharedHeaderOuterPagerGesture) {
|
|
2732
|
+
if (!_nativeSmoothHeaderScrollEnabled || _isBeingRecycled ||
|
|
2733
|
+
_sharedHeaderPressCancellationGestureHost == nil) return NO;
|
|
2734
|
+
UIView *touchView = touch.view;
|
|
2735
|
+
BOOL beginsInSharedHeader = touchView == _sharedHeaderHostView ||
|
|
2736
|
+
[touchView isDescendantOfView:_sharedHeaderHostView];
|
|
2737
|
+
if (!beginsInSharedHeader) return NO;
|
|
2738
|
+
|
|
2739
|
+
RNCCollapsiblePagerLog([NSString stringWithFormat:
|
|
2740
|
+
@"outer-pager-touch target=%@ outer-pagers=%lu",
|
|
2741
|
+
touchView == nil ? @"none" : NSStringFromClass(touchView.class),
|
|
2742
|
+
(unsigned long)_sharedHeaderOuterPagerGesture.blockedPagerGestures.count]);
|
|
2743
|
+
return YES;
|
|
2744
|
+
}
|
|
2745
|
+
if (gestureRecognizer == _sharedHeaderPressCancellationGesture) {
|
|
2746
|
+
if (!_nativeSmoothHeaderScrollEnabled || _isBeingRecycled ||
|
|
2747
|
+
_sharedHeaderPressCancellationGestureHost == nil) return NO;
|
|
2748
|
+
UIView *touchView = touch.view;
|
|
2749
|
+
BOOL beginsInSharedHeader = touchView == _sharedHeaderHostView ||
|
|
2750
|
+
[touchView isDescendantOfView:_sharedHeaderHostView];
|
|
2751
|
+
if (!beginsInSharedHeader) return NO;
|
|
2752
|
+
|
|
2753
|
+
RNCCollapsiblePagerLog([NSString stringWithFormat:
|
|
2754
|
+
@"header-touch target=%@ shared=1",
|
|
2755
|
+
touchView == nil ? @"none" : NSStringFromClass(touchView.class)]);
|
|
2756
|
+
return YES;
|
|
2757
|
+
}
|
|
2758
|
+
if (gestureRecognizer == _verticalPagerGesture) {
|
|
2759
|
+
UIView *touchView = touch.view;
|
|
2760
|
+
return touchView != _sharedHeaderHostView &&
|
|
2761
|
+
![touchView isDescendantOfView:_sharedHeaderHostView];
|
|
2762
|
+
}
|
|
2763
|
+
// A new touch can cancel UIKit's animation before its first frame. Queue a
|
|
2764
|
+
// non-animated settle for the next safe transition boundary instead of
|
|
2765
|
+
// re-entering setViewControllers while UIKit is flushing the current view.
|
|
968
2766
|
if (gestureRecognizer == _blockerGesture && _transitioning && !_isPagerDragging &&
|
|
969
2767
|
[touch.view isDescendantOfView:_pagerScrollView]) {
|
|
970
2768
|
[self goTo:_destinationIndex animated:NO];
|
|
@@ -975,6 +2773,30 @@ static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerConte
|
|
|
975
2773
|
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
|
|
976
2774
|
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
|
|
977
2775
|
{
|
|
2776
|
+
if (gestureRecognizer == _sharedHeaderOuterPagerGesture ||
|
|
2777
|
+
otherGestureRecognizer == _sharedHeaderOuterPagerGesture) {
|
|
2778
|
+
UIGestureRecognizer *other = gestureRecognizer == _sharedHeaderOuterPagerGesture
|
|
2779
|
+
? otherGestureRecognizer
|
|
2780
|
+
: gestureRecognizer;
|
|
2781
|
+
return ![other isKindOfClass:RCTSurfaceTouchHandler.class] &&
|
|
2782
|
+
![other isKindOfClass:RCTTouchHandler.class] &&
|
|
2783
|
+
![_sharedHeaderOuterPagerGesture.blockedPagerGestures containsObject:other];
|
|
2784
|
+
}
|
|
2785
|
+
if (gestureRecognizer == _sharedHeaderPressCancellationGesture ||
|
|
2786
|
+
otherGestureRecognizer == _sharedHeaderPressCancellationGesture) {
|
|
2787
|
+
UIGestureRecognizer *other = gestureRecognizer == _sharedHeaderPressCancellationGesture
|
|
2788
|
+
? otherGestureRecognizer
|
|
2789
|
+
: gestureRecognizer;
|
|
2790
|
+
return ![other isKindOfClass:RCTSurfaceTouchHandler.class] &&
|
|
2791
|
+
![other isKindOfClass:RCTTouchHandler.class];
|
|
2792
|
+
}
|
|
2793
|
+
if (gestureRecognizer == _verticalPagerGesture ||
|
|
2794
|
+
otherGestureRecognizer == _verticalPagerGesture) {
|
|
2795
|
+
UIGestureRecognizer *other = gestureRecognizer == _verticalPagerGesture
|
|
2796
|
+
? otherGestureRecognizer
|
|
2797
|
+
: gestureRecognizer;
|
|
2798
|
+
return other != _pagerScrollView.panGestureRecognizer;
|
|
2799
|
+
}
|
|
978
2800
|
return gestureRecognizer == _blockerGesture;
|
|
979
2801
|
}
|
|
980
2802
|
|