@lodev09/react-native-true-sheet 3.0.0-beta.9 → 3.0.1
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/README.md +16 -6
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +29 -33
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetModule.kt +3 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +53 -43
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +390 -89
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +42 -4
- package/android/src/main/java/com/lodev09/truesheet/core/RNScreensFragmentObserver.kt +0 -5
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDialogObserver.kt +67 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetGrabberView.kt +70 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetDragEvents.kt +71 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetFocusEvents.kt +65 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetLifecycleEvents.kt +94 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetStateEvents.kt +56 -0
- package/android/src/main/java/com/lodev09/truesheet/utils/ScreenUtils.kt +37 -33
- package/android/src/main/res/anim/true_sheet_slide_in.xml +13 -0
- package/android/src/main/res/anim/true_sheet_slide_out.xml +13 -0
- package/android/src/main/res/values/styles.xml +13 -1
- package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetViewShadowNode.cpp +5 -3
- package/ios/TrueSheetContainerView.mm +4 -0
- package/ios/TrueSheetContentView.h +2 -1
- package/ios/TrueSheetContentView.mm +91 -11
- package/ios/TrueSheetView.mm +94 -41
- package/ios/TrueSheetViewController.h +22 -10
- package/ios/TrueSheetViewController.mm +360 -173
- package/ios/core/TrueSheetBlurView.h +26 -0
- package/ios/{utils/ConversionUtil.mm → core/TrueSheetBlurView.mm} +64 -3
- package/ios/core/TrueSheetGrabberView.h +42 -0
- package/ios/core/TrueSheetGrabberView.mm +107 -0
- package/ios/events/TrueSheetDragEvents.h +39 -0
- package/ios/events/TrueSheetDragEvents.mm +62 -0
- package/ios/events/{OnPositionChangeEvent.h → TrueSheetFocusEvents.h} +8 -5
- package/ios/events/TrueSheetFocusEvents.mm +49 -0
- package/ios/events/TrueSheetLifecycleEvents.h +40 -0
- package/ios/events/TrueSheetLifecycleEvents.mm +71 -0
- package/ios/events/TrueSheetStateEvents.h +35 -0
- package/ios/events/TrueSheetStateEvents.mm +49 -0
- package/ios/utils/GestureUtil.h +7 -0
- package/ios/utils/GestureUtil.mm +12 -0
- package/lib/module/TrueSheet.js +72 -12
- package/lib/module/TrueSheet.js.map +1 -1
- package/lib/module/fabric/TrueSheetViewNativeComponent.ts +28 -5
- package/lib/module/index.js +0 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/reanimated/ReanimatedTrueSheet.js +13 -7
- package/lib/module/reanimated/ReanimatedTrueSheet.js.map +1 -1
- package/lib/module/reanimated/ReanimatedTrueSheetProvider.js +4 -2
- package/lib/module/reanimated/ReanimatedTrueSheetProvider.js.map +1 -1
- package/lib/typescript/src/TrueSheet.d.ts +4 -0
- package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.types.d.ts +105 -6
- package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +25 -5
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +0 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/reanimated/ReanimatedTrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/reanimated/ReanimatedTrueSheetProvider.d.ts +8 -2
- package/lib/typescript/src/reanimated/ReanimatedTrueSheetProvider.d.ts.map +1 -1
- package/package.json +8 -2
- package/src/TrueSheet.tsx +87 -10
- package/src/TrueSheet.types.ts +114 -6
- package/src/__mocks__/index.js +0 -5
- package/src/fabric/TrueSheetViewNativeComponent.ts +28 -5
- package/src/index.ts +0 -1
- package/src/reanimated/ReanimatedTrueSheet.tsx +12 -7
- package/src/reanimated/ReanimatedTrueSheetProvider.tsx +11 -3
- package/android/src/main/java/com/lodev09/truesheet/events/DetentChangeEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/DidDismissEvent.kt +0 -20
- package/android/src/main/java/com/lodev09/truesheet/events/DidPresentEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/DragBeginEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/DragChangeEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/DragEndEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/MountEvent.kt +0 -20
- package/android/src/main/java/com/lodev09/truesheet/events/PositionChangeEvent.kt +0 -32
- package/android/src/main/java/com/lodev09/truesheet/events/WillDismissEvent.kt +0 -20
- package/android/src/main/java/com/lodev09/truesheet/events/WillPresentEvent.kt +0 -26
- package/ios/events/OnDetentChangeEvent.h +0 -28
- package/ios/events/OnDetentChangeEvent.mm +0 -30
- package/ios/events/OnDidDismissEvent.h +0 -26
- package/ios/events/OnDidDismissEvent.mm +0 -25
- package/ios/events/OnDidPresentEvent.h +0 -28
- package/ios/events/OnDidPresentEvent.mm +0 -30
- package/ios/events/OnDragBeginEvent.h +0 -28
- package/ios/events/OnDragBeginEvent.mm +0 -30
- package/ios/events/OnDragChangeEvent.h +0 -28
- package/ios/events/OnDragChangeEvent.mm +0 -30
- package/ios/events/OnDragEndEvent.h +0 -28
- package/ios/events/OnDragEndEvent.mm +0 -30
- package/ios/events/OnMountEvent.h +0 -26
- package/ios/events/OnMountEvent.mm +0 -25
- package/ios/events/OnPositionChangeEvent.mm +0 -32
- package/ios/events/OnWillDismissEvent.h +0 -26
- package/ios/events/OnWillDismissEvent.mm +0 -25
- package/ios/events/OnWillPresentEvent.h +0 -28
- package/ios/events/OnWillPresentEvent.mm +0 -30
- package/ios/utils/ConversionUtil.h +0 -24
- package/lib/module/TrueSheetGrabber.js +0 -51
- package/lib/module/TrueSheetGrabber.js.map +0 -1
- package/lib/typescript/src/TrueSheetGrabber.d.ts +0 -39
- package/lib/typescript/src/TrueSheetGrabber.d.ts.map +0 -1
- package/src/TrueSheetGrabber.tsx +0 -82
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
#import "TrueSheetViewController.h"
|
|
10
10
|
#import "TrueSheetContentView.h"
|
|
11
|
-
#import "
|
|
11
|
+
#import "core/TrueSheetBlurView.h"
|
|
12
|
+
#import "core/TrueSheetGrabberView.h"
|
|
12
13
|
#import "utils/GestureUtil.h"
|
|
13
14
|
#import "utils/WindowUtil.h"
|
|
14
15
|
|
|
@@ -21,14 +22,20 @@
|
|
|
21
22
|
|
|
22
23
|
@implementation TrueSheetViewController {
|
|
23
24
|
CGFloat _lastPosition;
|
|
24
|
-
CGFloat _lastTransitionPosition;
|
|
25
|
-
BOOL _isTransitioning;
|
|
26
25
|
BOOL _isDragging;
|
|
27
|
-
|
|
26
|
+
NSInteger _pendingDetentIndex;
|
|
28
27
|
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
// Reference to parent TrueSheetViewController (if presented from another sheet)
|
|
29
|
+
__weak TrueSheetViewController *_parentSheetController;
|
|
30
|
+
|
|
31
|
+
// Blur effect view
|
|
32
|
+
TrueSheetBlurView *_blurView;
|
|
33
|
+
|
|
34
|
+
// Custom grabber view
|
|
35
|
+
TrueSheetGrabberView *_grabberView;
|
|
36
|
+
|
|
37
|
+
// Resolved detent positions (Y coordinate when sheet rests at each detent)
|
|
38
|
+
NSMutableArray<NSNumber *> *_resolvedDetentPositions;
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
#pragma mark - Initialization
|
|
@@ -39,32 +46,24 @@
|
|
|
39
46
|
_contentHeight = @(0);
|
|
40
47
|
_headerHeight = @(0);
|
|
41
48
|
_grabber = YES;
|
|
49
|
+
_draggable = YES;
|
|
42
50
|
_dimmed = YES;
|
|
43
51
|
_dimmedDetentIndex = @(0);
|
|
44
52
|
_pageSizing = YES;
|
|
45
53
|
_lastPosition = 0;
|
|
46
|
-
_lastTransitionPosition = 0;
|
|
47
|
-
_isTransitioning = NO;
|
|
48
54
|
_isDragging = NO;
|
|
49
|
-
_isTrackingPositionFromLayout = NO;
|
|
50
|
-
_layoutTransitioning = NO;
|
|
51
55
|
_isPresented = NO;
|
|
52
56
|
_activeDetentIndex = -1;
|
|
57
|
+
_pendingDetentIndex = -1;
|
|
53
58
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
_fakeTransitionView.userInteractionEnabled = NO;
|
|
59
|
+
_blurInteraction = YES;
|
|
60
|
+
_resolvedDetentPositions = [NSMutableArray array];
|
|
57
61
|
}
|
|
58
62
|
return self;
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
- (void)dealloc {
|
|
62
66
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
63
|
-
|
|
64
|
-
if (_displayLink) {
|
|
65
|
-
[_displayLink invalidate];
|
|
66
|
-
_displayLink = nil;
|
|
67
|
-
}
|
|
68
67
|
}
|
|
69
68
|
|
|
70
69
|
#pragma mark - Computed Properties
|
|
@@ -77,7 +76,7 @@
|
|
|
77
76
|
}
|
|
78
77
|
|
|
79
78
|
- (BOOL)isActiveAndVisible {
|
|
80
|
-
return self.isViewLoaded && self.view.window != nil
|
|
79
|
+
return self.isViewLoaded && self.view.window != nil;
|
|
81
80
|
}
|
|
82
81
|
|
|
83
82
|
- (UIView *)presentedView {
|
|
@@ -89,21 +88,8 @@
|
|
|
89
88
|
return presentedView ? presentedView.frame.origin.y : 0.0;
|
|
90
89
|
}
|
|
91
90
|
|
|
92
|
-
- (CGFloat)
|
|
93
|
-
|
|
94
|
-
return 0;
|
|
95
|
-
}
|
|
96
|
-
UIWindow *window = [WindowUtil keyWindow];
|
|
97
|
-
return window ? window.safeAreaInsets.bottom : 0;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
- (CGFloat)currentHeight {
|
|
101
|
-
return self.containerHeight - self.currentPosition - self.bottomInset;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
- (CGFloat)containerHeight {
|
|
105
|
-
UIView *sheetContainerView = self.sheetPresentationController.containerView;
|
|
106
|
-
return sheetContainerView ? sheetContainerView.frame.size.height : 0.0;
|
|
91
|
+
- (CGFloat)screenHeight {
|
|
92
|
+
return UIScreen.mainScreen.bounds.size.height;
|
|
107
93
|
}
|
|
108
94
|
|
|
109
95
|
- (NSInteger)currentDetentIndex {
|
|
@@ -139,6 +125,11 @@
|
|
|
139
125
|
- (void)viewDidLoad {
|
|
140
126
|
[super viewDidLoad];
|
|
141
127
|
self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
|
|
128
|
+
|
|
129
|
+
// Create custom grabber view (hidden by default, shown when grabberOptions is set)
|
|
130
|
+
_grabberView = [[TrueSheetGrabberView alloc] init];
|
|
131
|
+
_grabberView.hidden = YES;
|
|
132
|
+
[_grabberView addToView:self.view];
|
|
142
133
|
}
|
|
143
134
|
|
|
144
135
|
- (void)viewWillAppear:(BOOL)animated {
|
|
@@ -146,10 +137,25 @@
|
|
|
146
137
|
|
|
147
138
|
// Only trigger on initial presentation, not repositioning
|
|
148
139
|
if (!_isPresented) {
|
|
149
|
-
if
|
|
150
|
-
|
|
140
|
+
// Capture parent sheet reference if presented from another TrueSheet
|
|
141
|
+
UIViewController *presenter = self.presentingViewController;
|
|
142
|
+
if ([presenter isKindOfClass:[TrueSheetViewController class]]) {
|
|
143
|
+
_parentSheetController = (TrueSheetViewController *)presenter;
|
|
144
|
+
// Notify parent that it is about to lose focus
|
|
145
|
+
if ([_parentSheetController.delegate respondsToSelector:@selector(viewControllerWillBlur)]) {
|
|
146
|
+
[_parentSheetController.delegate viewControllerWillBlur];
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerWillPresentAtIndex:position:detent:)]) {
|
|
151
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
152
|
+
NSInteger index = [self currentDetentIndex];
|
|
153
|
+
CGFloat position = self.currentPosition;
|
|
154
|
+
CGFloat detent = [self detentValueForIndex:index];
|
|
155
|
+
|
|
156
|
+
[self.delegate viewControllerWillPresentAtIndex:index position:position detent:detent];
|
|
157
|
+
});
|
|
151
158
|
}
|
|
152
|
-
[self setupTransitionPositionTracking];
|
|
153
159
|
}
|
|
154
160
|
}
|
|
155
161
|
|
|
@@ -157,40 +163,69 @@
|
|
|
157
163
|
[super viewDidAppear:animated];
|
|
158
164
|
|
|
159
165
|
if (!_isPresented) {
|
|
160
|
-
|
|
161
|
-
|
|
166
|
+
// Notify parent that it has lost focus (after the child sheet appeared)
|
|
167
|
+
if (_parentSheetController) {
|
|
168
|
+
if ([_parentSheetController.delegate respondsToSelector:@selector(viewControllerDidBlur)]) {
|
|
169
|
+
[_parentSheetController.delegate viewControllerDidBlur];
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerDidPresentAtIndex:position:detent:)]) {
|
|
174
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
175
|
+
NSInteger index = [self currentDetentIndex];
|
|
176
|
+
CGFloat detent = [self detentValueForIndex:index];
|
|
177
|
+
[self.delegate viewControllerDidPresentAtIndex:index position:self.currentPosition detent:detent];
|
|
178
|
+
});
|
|
162
179
|
}
|
|
163
180
|
[self setupGestureRecognizer];
|
|
164
181
|
_isPresented = YES;
|
|
165
182
|
}
|
|
166
183
|
}
|
|
167
184
|
|
|
185
|
+
- (BOOL)isDismissing {
|
|
186
|
+
return self.presentingViewController == nil || self.isBeingDismissed;
|
|
187
|
+
}
|
|
188
|
+
|
|
168
189
|
- (void)viewWillDisappear:(BOOL)animated {
|
|
169
190
|
[super viewWillDisappear:animated];
|
|
170
191
|
|
|
171
|
-
if (self.
|
|
172
|
-
|
|
173
|
-
|
|
192
|
+
if (self.isDismissing) {
|
|
193
|
+
_isPresented = NO;
|
|
194
|
+
_activeDetentIndex = -1;
|
|
195
|
+
|
|
196
|
+
// Notify the parent sheet (if any) that it is about to regain focus
|
|
197
|
+
if (_parentSheetController) {
|
|
198
|
+
if ([_parentSheetController.delegate respondsToSelector:@selector(viewControllerWillFocus)]) {
|
|
199
|
+
[_parentSheetController.delegate viewControllerWillFocus];
|
|
200
|
+
}
|
|
201
|
+
}
|
|
174
202
|
|
|
175
|
-
|
|
176
|
-
|
|
203
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
204
|
+
[self emitChangePositionDelegateWithPosition:self.currentPosition realtime:NO];
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerWillDismiss)]) {
|
|
208
|
+
[self.delegate viewControllerWillDismiss];
|
|
209
|
+
}
|
|
210
|
+
}
|
|
177
211
|
}
|
|
178
212
|
|
|
179
213
|
- (void)viewDidDisappear:(BOOL)animated {
|
|
180
214
|
[super viewDidDisappear:animated];
|
|
181
215
|
|
|
182
216
|
// Only dispatch didDismiss when actually dismissing (not when another modal is presented on top)
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
217
|
+
if (self.isDismissing) {
|
|
218
|
+
// Notify the parent sheet (if any) that it regained focus
|
|
219
|
+
if (_parentSheetController) {
|
|
220
|
+
if ([_parentSheetController.delegate respondsToSelector:@selector(viewControllerDidFocus)]) {
|
|
221
|
+
[_parentSheetController.delegate viewControllerDidFocus];
|
|
222
|
+
}
|
|
223
|
+
_parentSheetController = nil;
|
|
224
|
+
}
|
|
190
225
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
226
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerDidDismiss)]) {
|
|
227
|
+
[self.delegate viewControllerDidDismiss];
|
|
228
|
+
}
|
|
194
229
|
}
|
|
195
230
|
}
|
|
196
231
|
|
|
@@ -201,16 +236,29 @@
|
|
|
201
236
|
[self.delegate viewControllerDidChangeSize:self.view.frame.size];
|
|
202
237
|
}
|
|
203
238
|
|
|
204
|
-
if (
|
|
205
|
-
|
|
239
|
+
// Check if there's an active presented controller that has settled (not being presented/dismissed)
|
|
240
|
+
UIViewController *presented = self.presentedViewController;
|
|
241
|
+
BOOL hasPresentedController = presented != nil && !presented.isBeingPresented && !presented.isBeingDismissed;
|
|
206
242
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
243
|
+
if (!_isDragging) {
|
|
244
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
245
|
+
// Update stored position for current detent (handles content size changes)
|
|
246
|
+
[self storeResolvedPositionForIndex:[self currentDetentIndex]];
|
|
210
247
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
248
|
+
[self emitChangePositionDelegateWithPosition:self.currentPosition realtime:hasPresentedController];
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Emit pending detent change after programmatic resize settles
|
|
253
|
+
if (_pendingDetentIndex >= 0) {
|
|
254
|
+
NSInteger pendingIndex = _pendingDetentIndex;
|
|
255
|
+
_pendingDetentIndex = -1;
|
|
256
|
+
|
|
257
|
+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
258
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerDidChangeDetent:position:detent:)]) {
|
|
259
|
+
CGFloat detent = [self detentValueForIndex:pendingIndex];
|
|
260
|
+
[self.delegate viewControllerDidChangeDetent:pendingIndex position:self.currentPosition detent:detent];
|
|
261
|
+
}
|
|
214
262
|
});
|
|
215
263
|
}
|
|
216
264
|
}
|
|
@@ -237,13 +285,28 @@
|
|
|
237
285
|
if (!presentedView)
|
|
238
286
|
return;
|
|
239
287
|
|
|
288
|
+
// Disable pan gestures if draggable is NO
|
|
289
|
+
if (!self.draggable) {
|
|
290
|
+
[GestureUtil setPanGesturesEnabled:NO forView:presentedView];
|
|
291
|
+
|
|
292
|
+
// Also disable ScrollView's pan gesture if present
|
|
293
|
+
TrueSheetContentView *contentView = [self findContentView:presentedView];
|
|
294
|
+
if (contentView) {
|
|
295
|
+
RCTScrollViewComponentView *scrollViewComponent = [contentView findScrollView:nil];
|
|
296
|
+
if (scrollViewComponent && scrollViewComponent.scrollView) {
|
|
297
|
+
[GestureUtil setPanGesturesEnabled:NO forView:scrollViewComponent.scrollView];
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
240
303
|
// Attach to presented view's pan gesture (sheet's drag gesture from UIKit)
|
|
241
304
|
[GestureUtil attachPanGestureHandler:presentedView target:self selector:@selector(handlePanGesture:)];
|
|
242
305
|
|
|
243
306
|
// Also attach to ScrollView's pan gesture if present
|
|
244
307
|
TrueSheetContentView *contentView = [self findContentView:presentedView];
|
|
245
308
|
if (contentView) {
|
|
246
|
-
RCTScrollViewComponentView *scrollViewComponent = [contentView findScrollView];
|
|
309
|
+
RCTScrollViewComponentView *scrollViewComponent = [contentView findScrollView:nil];
|
|
247
310
|
if (scrollViewComponent && scrollViewComponent.scrollView) {
|
|
248
311
|
[GestureUtil attachPanGestureHandler:scrollViewComponent.scrollView
|
|
249
312
|
target:self
|
|
@@ -252,11 +315,29 @@
|
|
|
252
315
|
}
|
|
253
316
|
}
|
|
254
317
|
|
|
318
|
+
- (void)updateDraggable {
|
|
319
|
+
UIView *presentedView = self.presentedView;
|
|
320
|
+
if (!presentedView)
|
|
321
|
+
return;
|
|
322
|
+
|
|
323
|
+
[GestureUtil setPanGesturesEnabled:self.draggable forView:presentedView];
|
|
324
|
+
|
|
325
|
+
// Also update ScrollView's pan gesture if present
|
|
326
|
+
TrueSheetContentView *contentView = [self findContentView:presentedView];
|
|
327
|
+
if (contentView) {
|
|
328
|
+
RCTScrollViewComponentView *scrollViewComponent = [contentView findScrollView:nil];
|
|
329
|
+
if (scrollViewComponent && scrollViewComponent.scrollView) {
|
|
330
|
+
[GestureUtil setPanGesturesEnabled:self.draggable forView:scrollViewComponent.scrollView];
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
255
335
|
- (void)handlePanGesture:(UIPanGestureRecognizer *)gesture {
|
|
256
336
|
NSInteger index = [self currentDetentIndex];
|
|
337
|
+
CGFloat detent = [self detentValueForIndex:index];
|
|
257
338
|
|
|
258
|
-
if ([self.delegate respondsToSelector:@selector(viewControllerDidDrag:index:position:)]) {
|
|
259
|
-
[self.delegate viewControllerDidDrag:gesture.state index:index position:self.currentPosition];
|
|
339
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerDidDrag:index:position:detent:)]) {
|
|
340
|
+
[self.delegate viewControllerDidDrag:gesture.state index:index position:self.currentPosition detent:detent];
|
|
260
341
|
}
|
|
261
342
|
|
|
262
343
|
switch (gesture.state) {
|
|
@@ -264,14 +345,19 @@
|
|
|
264
345
|
_isDragging = YES;
|
|
265
346
|
break;
|
|
266
347
|
case UIGestureRecognizerStateChanged:
|
|
267
|
-
|
|
268
|
-
[self emitChangePositionDelegateWithPosition:self.currentPosition transitioning:NO];
|
|
269
|
-
}
|
|
348
|
+
[self emitChangePositionDelegateWithPosition:self.currentPosition realtime:YES];
|
|
270
349
|
break;
|
|
271
350
|
case UIGestureRecognizerStateEnded:
|
|
272
|
-
case UIGestureRecognizerStateCancelled:
|
|
351
|
+
case UIGestureRecognizerStateCancelled: {
|
|
273
352
|
_isDragging = NO;
|
|
353
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
354
|
+
// Store resolved position when drag ends
|
|
355
|
+
[self storeResolvedPositionForIndex:[self currentDetentIndex]];
|
|
356
|
+
|
|
357
|
+
[self emitChangePositionDelegateWithPosition:self.currentPosition realtime:NO];
|
|
358
|
+
});
|
|
274
359
|
break;
|
|
360
|
+
}
|
|
275
361
|
default:
|
|
276
362
|
break;
|
|
277
363
|
}
|
|
@@ -279,90 +365,156 @@
|
|
|
279
365
|
|
|
280
366
|
#pragma mark - Position Tracking
|
|
281
367
|
|
|
282
|
-
- (void)emitChangePositionDelegateWithPosition:(CGFloat)position
|
|
283
|
-
|
|
368
|
+
- (void)emitChangePositionDelegateWithPosition:(CGFloat)position realtime:(BOOL)realtime {
|
|
369
|
+
// Use epsilon comparison to avoid missing updates due to floating point precision
|
|
370
|
+
if (fabs(_lastPosition - position) > 0.01) {
|
|
284
371
|
_lastPosition = position;
|
|
285
372
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
373
|
+
CGFloat index = [self interpolatedIndexForPosition:position];
|
|
374
|
+
CGFloat detent = [self interpolatedDetentForPosition:position];
|
|
375
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerDidChangePosition:position:detent:realtime:)]) {
|
|
376
|
+
[self.delegate viewControllerDidChangePosition:index position:position detent:detent realtime:realtime];
|
|
289
377
|
}
|
|
290
378
|
}
|
|
291
379
|
}
|
|
292
380
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
*/
|
|
300
|
-
- (void)setupTransitionPositionTracking {
|
|
301
|
-
if (self.transitionCoordinator == nil)
|
|
302
|
-
return;
|
|
381
|
+
/// Stores the current position for the given detent index
|
|
382
|
+
- (void)storeResolvedPositionForIndex:(NSInteger)index {
|
|
383
|
+
if (index >= 0 && index < (NSInteger)_resolvedDetentPositions.count) {
|
|
384
|
+
_resolvedDetentPositions[index] = @(self.currentPosition);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
303
387
|
|
|
304
|
-
|
|
388
|
+
/// Returns the estimated Y position for a detent index, using stored positions when available
|
|
389
|
+
- (CGFloat)estimatedPositionForIndex:(NSInteger)index {
|
|
390
|
+
if (index < 0 || index >= (NSInteger)_resolvedDetentPositions.count)
|
|
391
|
+
return 0;
|
|
305
392
|
|
|
306
|
-
|
|
307
|
-
|
|
393
|
+
CGFloat storedPos = [_resolvedDetentPositions[index] doubleValue];
|
|
394
|
+
if (storedPos > 0) {
|
|
395
|
+
return storedPos;
|
|
396
|
+
}
|
|
308
397
|
|
|
309
|
-
|
|
310
|
-
|
|
398
|
+
// Estimate based on detent value and known offset from first resolved position
|
|
399
|
+
CGFloat detentValue = [self detentValueForIndex:index];
|
|
400
|
+
CGFloat basePosition = self.screenHeight - (detentValue * self.screenHeight);
|
|
401
|
+
|
|
402
|
+
// Find a resolved position to calculate offset
|
|
403
|
+
for (NSInteger i = 0; i < (NSInteger)_resolvedDetentPositions.count; i++) {
|
|
404
|
+
CGFloat pos = [_resolvedDetentPositions[i] doubleValue];
|
|
405
|
+
if (pos > 0) {
|
|
406
|
+
CGFloat knownDetent = [self detentValueForIndex:i];
|
|
407
|
+
CGFloat expectedPos = self.screenHeight - (knownDetent * self.screenHeight);
|
|
408
|
+
CGFloat offset = pos - expectedPos;
|
|
409
|
+
return basePosition + offset;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
311
412
|
|
|
312
|
-
|
|
313
|
-
|
|
413
|
+
return basePosition;
|
|
414
|
+
}
|
|
314
415
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
416
|
+
/// Finds the segment containing the given position and returns the lower index and progress within that segment.
|
|
417
|
+
/// Returns YES if a segment was found, NO otherwise. When NO, `outIndex` contains the boundary index.
|
|
418
|
+
- (BOOL)findSegmentForPosition:(CGFloat)position outIndex:(NSInteger *)outIndex outProgress:(CGFloat *)outProgress {
|
|
419
|
+
NSInteger count = _resolvedDetentPositions.count;
|
|
420
|
+
if (count == 0) {
|
|
421
|
+
*outIndex = -1;
|
|
422
|
+
*outProgress = 0;
|
|
423
|
+
return NO;
|
|
424
|
+
}
|
|
318
425
|
|
|
319
|
-
|
|
320
|
-
|
|
426
|
+
if (count == 1) {
|
|
427
|
+
*outIndex = 0;
|
|
428
|
+
*outProgress = 0;
|
|
429
|
+
return NO;
|
|
430
|
+
}
|
|
321
431
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
self->_fakeTransitionView.frame = finalFrame;
|
|
432
|
+
CGFloat firstPos = [self estimatedPositionForIndex:0];
|
|
433
|
+
CGFloat lastPos = [self estimatedPositionForIndex:count - 1];
|
|
325
434
|
|
|
326
|
-
|
|
435
|
+
// Below first detent (position > firstPos means sheet is smaller)
|
|
436
|
+
if (position > firstPos) {
|
|
437
|
+
CGFloat range = self.screenHeight - firstPos;
|
|
438
|
+
*outIndex = -1;
|
|
439
|
+
*outProgress = range > 0 ? (position - firstPos) / range : 0;
|
|
440
|
+
return NO;
|
|
441
|
+
}
|
|
327
442
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
443
|
+
// Above last detent
|
|
444
|
+
if (position < lastPos) {
|
|
445
|
+
*outIndex = count - 1;
|
|
446
|
+
*outProgress = 0;
|
|
447
|
+
return NO;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Find segment (positions decrease as index increases)
|
|
451
|
+
for (NSInteger i = 0; i < count - 1; i++) {
|
|
452
|
+
CGFloat pos = [self estimatedPositionForIndex:i];
|
|
453
|
+
CGFloat nextPos = [self estimatedPositionForIndex:i + 1];
|
|
332
454
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
455
|
+
if (position <= pos && position >= nextPos) {
|
|
456
|
+
CGFloat range = pos - nextPos;
|
|
457
|
+
*outIndex = i;
|
|
458
|
+
*outProgress = range > 0 ? (pos - position) / range : 0;
|
|
459
|
+
return YES;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
*outIndex = count - 1;
|
|
464
|
+
*outProgress = 0;
|
|
465
|
+
return NO;
|
|
341
466
|
}
|
|
342
467
|
|
|
343
|
-
- (
|
|
344
|
-
|
|
468
|
+
- (CGFloat)interpolatedIndexForPosition:(CGFloat)position {
|
|
469
|
+
NSInteger index;
|
|
470
|
+
CGFloat progress;
|
|
471
|
+
BOOL found = [self findSegmentForPosition:position outIndex:&index outProgress:&progress];
|
|
345
472
|
|
|
346
|
-
if (
|
|
347
|
-
|
|
473
|
+
if (!found) {
|
|
474
|
+
if (index == -1) {
|
|
475
|
+
// Below first detent - return negative progress
|
|
476
|
+
return -progress;
|
|
477
|
+
}
|
|
478
|
+
// At or beyond boundary
|
|
479
|
+
return index;
|
|
480
|
+
}
|
|
348
481
|
|
|
349
|
-
//
|
|
350
|
-
|
|
482
|
+
// Within a segment - interpolate
|
|
483
|
+
return index + fmax(0, fmin(1, progress));
|
|
484
|
+
}
|
|
351
485
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
486
|
+
- (CGFloat)interpolatedDetentForPosition:(CGFloat)position {
|
|
487
|
+
NSInteger index;
|
|
488
|
+
CGFloat progress;
|
|
489
|
+
BOOL found = [self findSegmentForPosition:position outIndex:&index outProgress:&progress];
|
|
355
490
|
|
|
356
|
-
|
|
357
|
-
if (
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
_lastTransitionPosition = position;
|
|
491
|
+
if (!found) {
|
|
492
|
+
if (index == -1) {
|
|
493
|
+
// Below first detent
|
|
494
|
+
CGFloat firstDetent = [self detentValueForIndex:0];
|
|
495
|
+
return fmax(0, firstDetent * (1 - progress));
|
|
362
496
|
}
|
|
497
|
+
// At or beyond boundary
|
|
498
|
+
return [self detentValueForIndex:index];
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Within a segment - interpolate between detent values
|
|
502
|
+
CGFloat detent = [self detentValueForIndex:index];
|
|
503
|
+
CGFloat nextDetent = [self detentValueForIndex:index + 1];
|
|
504
|
+
return detent + progress * (nextDetent - detent);
|
|
505
|
+
}
|
|
363
506
|
|
|
364
|
-
|
|
507
|
+
- (CGFloat)detentValueForIndex:(NSInteger)index {
|
|
508
|
+
if (index >= 0 && index < (NSInteger)_detents.count) {
|
|
509
|
+
CGFloat value = [_detents[index] doubleValue];
|
|
510
|
+
// For auto (-1), calculate actual fraction from content + header height
|
|
511
|
+
if (value == -1) {
|
|
512
|
+
CGFloat autoHeight = [self.contentHeight floatValue] + [self.headerHeight floatValue];
|
|
513
|
+
return autoHeight / self.screenHeight;
|
|
514
|
+
}
|
|
515
|
+
return value;
|
|
365
516
|
}
|
|
517
|
+
return 0;
|
|
366
518
|
}
|
|
367
519
|
|
|
368
520
|
#pragma mark - Sheet Configuration
|
|
@@ -373,16 +525,18 @@
|
|
|
373
525
|
return;
|
|
374
526
|
|
|
375
527
|
NSMutableArray<UISheetPresentationControllerDetent *> *detents = [NSMutableArray array];
|
|
528
|
+
[_resolvedDetentPositions removeAllObjects];
|
|
376
529
|
|
|
377
|
-
|
|
378
|
-
CGFloat totalHeight = [self.contentHeight floatValue] + [self.headerHeight floatValue] - self.bottomInset;
|
|
530
|
+
CGFloat autoHeight = [self.contentHeight floatValue] + [self.headerHeight floatValue];
|
|
379
531
|
|
|
380
532
|
for (NSInteger index = 0; index < self.detents.count; index++) {
|
|
381
533
|
id detent = self.detents[index];
|
|
382
534
|
UISheetPresentationControllerDetent *sheetDetent = [self detentForValue:detent
|
|
383
|
-
|
|
535
|
+
withAutoHeight:autoHeight
|
|
384
536
|
atIndex:index];
|
|
385
537
|
[detents addObject:sheetDetent];
|
|
538
|
+
// Initialize with placeholder - will be updated when sheet settles at each detent
|
|
539
|
+
[_resolvedDetentPositions addObject:@(0)];
|
|
386
540
|
}
|
|
387
541
|
|
|
388
542
|
sheet.detents = detents;
|
|
@@ -408,25 +562,19 @@
|
|
|
408
562
|
}
|
|
409
563
|
}
|
|
410
564
|
|
|
411
|
-
- (UISheetPresentationControllerDetent *)detentForValue:(id)detent
|
|
565
|
+
- (UISheetPresentationControllerDetent *)detentForValue:(id)detent
|
|
566
|
+
withAutoHeight:(CGFloat)autoHeight
|
|
567
|
+
atIndex:(NSInteger)index {
|
|
412
568
|
if (![detent isKindOfClass:[NSNumber class]]) {
|
|
413
569
|
return [UISheetPresentationControllerDetent mediumDetent];
|
|
414
570
|
}
|
|
415
571
|
|
|
416
|
-
CGFloat value = [detent
|
|
572
|
+
CGFloat value = [detent doubleValue];
|
|
417
573
|
|
|
418
574
|
// -1 represents "auto" (fit content height)
|
|
419
575
|
if (value == -1) {
|
|
420
576
|
if (@available(iOS 16.0, *)) {
|
|
421
|
-
|
|
422
|
-
return [UISheetPresentationControllerDetent
|
|
423
|
-
customDetentWithIdentifier:detentId
|
|
424
|
-
resolver:^CGFloat(id<UISheetPresentationControllerDetentResolutionContext> context) {
|
|
425
|
-
CGFloat maxDetentValue = context.maximumDetentValue;
|
|
426
|
-
CGFloat maxValue =
|
|
427
|
-
self.maxHeight ? fmin(maxDetentValue, [self.maxHeight floatValue]) : maxDetentValue;
|
|
428
|
-
return fmin(height, maxValue);
|
|
429
|
-
}];
|
|
577
|
+
return [self customDetentWithIdentifier:@"custom-auto" height:autoHeight];
|
|
430
578
|
} else {
|
|
431
579
|
return [UISheetPresentationControllerDetent mediumDetent];
|
|
432
580
|
}
|
|
@@ -438,21 +586,9 @@
|
|
|
438
586
|
}
|
|
439
587
|
|
|
440
588
|
if (@available(iOS 16.0, *)) {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
return [UISheetPresentationControllerDetent mediumDetent];
|
|
445
|
-
} else {
|
|
446
|
-
NSString *detentId = [NSString stringWithFormat:@"custom-%f", value];
|
|
447
|
-
return [UISheetPresentationControllerDetent
|
|
448
|
-
customDetentWithIdentifier:detentId
|
|
449
|
-
resolver:^CGFloat(id<UISheetPresentationControllerDetentResolutionContext> context) {
|
|
450
|
-
CGFloat maxDetentValue = context.maximumDetentValue;
|
|
451
|
-
CGFloat maxValue =
|
|
452
|
-
self.maxHeight ? fmin(maxDetentValue, [self.maxHeight floatValue]) : maxDetentValue;
|
|
453
|
-
return fmin(value * maxDetentValue, maxValue);
|
|
454
|
-
}];
|
|
455
|
-
}
|
|
589
|
+
NSString *detentId = [NSString stringWithFormat:@"custom-%f", value];
|
|
590
|
+
CGFloat sheetHeight = value * self.screenHeight;
|
|
591
|
+
return [self customDetentWithIdentifier:detentId height:sheetHeight];
|
|
456
592
|
} else if (value >= 0.5) {
|
|
457
593
|
return [UISheetPresentationControllerDetent largeDetent];
|
|
458
594
|
} else {
|
|
@@ -460,6 +596,18 @@
|
|
|
460
596
|
}
|
|
461
597
|
}
|
|
462
598
|
|
|
599
|
+
- (UISheetPresentationControllerDetent *)customDetentWithIdentifier:(NSString *)identifier
|
|
600
|
+
height:(CGFloat)height API_AVAILABLE(ios(16.0)) {
|
|
601
|
+
return [UISheetPresentationControllerDetent
|
|
602
|
+
customDetentWithIdentifier:identifier
|
|
603
|
+
resolver:^CGFloat(id<UISheetPresentationControllerDetentResolutionContext> context) {
|
|
604
|
+
CGFloat maxDetentValue = context.maximumDetentValue;
|
|
605
|
+
CGFloat maxValue =
|
|
606
|
+
self.maxHeight ? fmin(maxDetentValue, [self.maxHeight floatValue]) : maxDetentValue;
|
|
607
|
+
return fmin(height, maxValue);
|
|
608
|
+
}];
|
|
609
|
+
}
|
|
610
|
+
|
|
463
611
|
- (UISheetPresentationControllerDetentIdentifier)detentIdentifierForIndex:(NSInteger)index {
|
|
464
612
|
UISheetPresentationController *sheet = self.sheetPresentationController;
|
|
465
613
|
if (!sheet)
|
|
@@ -512,6 +660,16 @@
|
|
|
512
660
|
[self applyActiveDetent];
|
|
513
661
|
}
|
|
514
662
|
|
|
663
|
+
- (void)resizeToDetentIndex:(NSInteger)index {
|
|
664
|
+
if (index == _activeDetentIndex) {
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
_pendingDetentIndex = index;
|
|
669
|
+
_activeDetentIndex = index;
|
|
670
|
+
[self applyActiveDetent];
|
|
671
|
+
}
|
|
672
|
+
|
|
515
673
|
- (void)setupSheetProps {
|
|
516
674
|
UISheetPresentationController *sheet = self.sheetPresentationController;
|
|
517
675
|
if (!sheet) {
|
|
@@ -527,7 +685,6 @@
|
|
|
527
685
|
}
|
|
528
686
|
|
|
529
687
|
sheet.prefersEdgeAttachedInCompactHeight = YES;
|
|
530
|
-
sheet.prefersGrabberVisible = self.grabber;
|
|
531
688
|
|
|
532
689
|
if (self.cornerRadius) {
|
|
533
690
|
sheet.preferredCornerRadius = [self.cornerRadius floatValue];
|
|
@@ -537,19 +694,44 @@
|
|
|
537
694
|
|
|
538
695
|
self.view.backgroundColor = self.backgroundColor;
|
|
539
696
|
|
|
540
|
-
// Setup
|
|
697
|
+
// Setup blur effect view - recreate only when blurTint changes
|
|
698
|
+
BOOL blurTintChanged = ![_blurView.blurTint isEqualToString:self.blurTint];
|
|
699
|
+
|
|
700
|
+
if (_blurView && blurTintChanged) {
|
|
701
|
+
[_blurView removeFromSuperview];
|
|
702
|
+
_blurView = nil;
|
|
703
|
+
}
|
|
704
|
+
|
|
541
705
|
if (self.blurTint && self.blurTint.length > 0) {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
blurView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
546
|
-
[self.view insertSubview:blurView atIndex:0];
|
|
547
|
-
} else {
|
|
548
|
-
for (UIView *subview in self.view.subviews) {
|
|
549
|
-
if ([subview isKindOfClass:[UIVisualEffectView class]]) {
|
|
550
|
-
[subview removeFromSuperview];
|
|
551
|
-
}
|
|
706
|
+
if (!_blurView) {
|
|
707
|
+
_blurView = [[TrueSheetBlurView alloc] init];
|
|
708
|
+
[_blurView addToView:self.view];
|
|
552
709
|
}
|
|
710
|
+
_blurView.blurTint = self.blurTint;
|
|
711
|
+
_blurView.blurIntensity = self.blurIntensity;
|
|
712
|
+
_blurView.blurInteraction = self.blurInteraction;
|
|
713
|
+
[_blurView applyBlurEffect];
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// Setup grabber
|
|
717
|
+
BOOL showGrabber = self.grabber && self.draggable;
|
|
718
|
+
|
|
719
|
+
if (self.grabberOptions) {
|
|
720
|
+
// Use custom grabber view when options are provided
|
|
721
|
+
sheet.prefersGrabberVisible = NO;
|
|
722
|
+
|
|
723
|
+
NSDictionary *options = self.grabberOptions;
|
|
724
|
+
_grabberView.grabberWidth = options[@"width"];
|
|
725
|
+
_grabberView.grabberHeight = options[@"height"];
|
|
726
|
+
_grabberView.topMargin = options[@"topMargin"];
|
|
727
|
+
_grabberView.cornerRadius = options[@"cornerRadius"];
|
|
728
|
+
_grabberView.color = options[@"color"];
|
|
729
|
+
[_grabberView applyConfiguration];
|
|
730
|
+
_grabberView.hidden = !showGrabber;
|
|
731
|
+
} else {
|
|
732
|
+
// Use system default grabber when no options provided
|
|
733
|
+
sheet.prefersGrabberVisible = showGrabber;
|
|
734
|
+
_grabberView.hidden = YES;
|
|
553
735
|
}
|
|
554
736
|
}
|
|
555
737
|
|
|
@@ -557,9 +739,14 @@
|
|
|
557
739
|
|
|
558
740
|
- (void)sheetPresentationControllerDidChangeSelectedDetentIdentifier:
|
|
559
741
|
(UISheetPresentationController *)sheetPresentationController {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
742
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerDidChangeDetent:position:detent:)]) {
|
|
743
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
744
|
+
NSInteger index = [self currentDetentIndex];
|
|
745
|
+
if (index >= 0) {
|
|
746
|
+
CGFloat detent = [self detentValueForIndex:index];
|
|
747
|
+
[self.delegate viewControllerDidChangeDetent:index position:self.currentPosition detent:detent];
|
|
748
|
+
}
|
|
749
|
+
});
|
|
563
750
|
}
|
|
564
751
|
}
|
|
565
752
|
|