@lodev09/react-native-true-sheet 3.1.0-beta.2 → 3.1.0-beta.3
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.
|
@@ -64,12 +64,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
64
64
|
private const val GRABBER_TAG = "TrueSheetGrabber"
|
|
65
65
|
private const val DEFAULT_MAX_WIDTH = 640 // dp
|
|
66
66
|
private const val DEFAULT_CORNER_RADIUS = 16 // dp
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Gets the effective sheet height by subtracting headerHeight * 2.
|
|
70
|
-
* This is needed because both native layout and Yoga layout account for the header separately.
|
|
71
|
-
*/
|
|
72
|
-
fun getEffectiveSheetHeight(sheetHeight: Int, headerHeight: Int): Int = sheetHeight - headerHeight * 2
|
|
73
67
|
}
|
|
74
68
|
|
|
75
69
|
// ====================================================================
|
|
@@ -298,7 +292,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
298
292
|
|
|
299
293
|
// Store resolved position for initial detent
|
|
300
294
|
storeResolvedPosition(detentInfo.index)
|
|
301
|
-
emitChangePositionDelegate(
|
|
295
|
+
emitChangePositionDelegate(positionPx, realtime = false)
|
|
302
296
|
|
|
303
297
|
// Notify parent sheet that it has lost focus (after this sheet appeared)
|
|
304
298
|
parentSheetView?.viewControllerDidBlur()
|
|
@@ -337,9 +331,8 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
337
331
|
override fun onSlide(sheetView: View, slideOffset: Float) {
|
|
338
332
|
val behavior = behavior ?: return
|
|
339
333
|
val positionPx = getCurrentPositionPx(sheetView)
|
|
340
|
-
val detentIndex = getDetentIndexForPosition(positionPx)
|
|
341
334
|
|
|
342
|
-
emitChangePositionDelegate(
|
|
335
|
+
emitChangePositionDelegate(positionPx, realtime = true)
|
|
343
336
|
|
|
344
337
|
when (behavior.state) {
|
|
345
338
|
BottomSheetBehavior.STATE_DRAGGING,
|
|
@@ -407,14 +400,26 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
407
400
|
private fun setupModalObserver() {
|
|
408
401
|
rnScreensObserver = RNScreensFragmentObserver(
|
|
409
402
|
reactContext = reactContext,
|
|
403
|
+
onModalWillPresent = {
|
|
404
|
+
if (isPresented) {
|
|
405
|
+
delegate?.viewControllerWillBlur()
|
|
406
|
+
}
|
|
407
|
+
},
|
|
410
408
|
onModalPresented = {
|
|
411
409
|
if (isPresented) {
|
|
412
410
|
hideDialog()
|
|
411
|
+
delegate?.viewControllerDidBlur()
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
onModalWillDismiss = {
|
|
415
|
+
if (isPresented) {
|
|
416
|
+
delegate?.viewControllerWillFocus()
|
|
413
417
|
}
|
|
414
418
|
},
|
|
415
419
|
onModalDismissed = {
|
|
416
420
|
if (isPresented) {
|
|
417
421
|
showDialog()
|
|
422
|
+
delegate?.viewControllerDidFocus()
|
|
418
423
|
}
|
|
419
424
|
}
|
|
420
425
|
)
|
|
@@ -465,7 +470,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
465
470
|
dialog?.window?.decorView?.visibility = View.INVISIBLE
|
|
466
471
|
|
|
467
472
|
// Emit off-screen position (detent = 0 since sheet is fully hidden)
|
|
468
|
-
emitChangePositionDelegate(
|
|
473
|
+
emitChangePositionDelegate(screenHeight, realtime = false)
|
|
469
474
|
}
|
|
470
475
|
|
|
471
476
|
/**
|
|
@@ -478,7 +483,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
478
483
|
|
|
479
484
|
// Emit current position
|
|
480
485
|
val positionPx = bottomSheetView?.let { ScreenUtils.getScreenY(it) } ?: screenHeight
|
|
481
|
-
emitChangePositionDelegate(
|
|
486
|
+
emitChangePositionDelegate(positionPx, realtime = false)
|
|
482
487
|
}
|
|
483
488
|
|
|
484
489
|
// ====================================================================
|
|
@@ -522,7 +527,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
522
527
|
fun dismiss() {
|
|
523
528
|
this.post {
|
|
524
529
|
// Emit off-screen position (detent = 0 since sheet is fully hidden)
|
|
525
|
-
emitChangePositionDelegate(
|
|
530
|
+
emitChangePositionDelegate(screenHeight, realtime = false)
|
|
526
531
|
}
|
|
527
532
|
dialog?.dismiss()
|
|
528
533
|
}
|
|
@@ -552,7 +557,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
552
557
|
isReconfiguring = true
|
|
553
558
|
|
|
554
559
|
behavior.apply {
|
|
555
|
-
skipCollapsed = false
|
|
556
560
|
isFitToContents = false
|
|
557
561
|
maxWidth = DEFAULT_MAX_WIDTH.dpToPx().toInt()
|
|
558
562
|
|
|
@@ -560,6 +564,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
560
564
|
1 -> {
|
|
561
565
|
setPeekHeight(getDetentHeight(detents[0]), isPresented)
|
|
562
566
|
expandedOffset = screenHeight - peekHeight
|
|
567
|
+
isFitToContents = true
|
|
563
568
|
}
|
|
564
569
|
|
|
565
570
|
2 -> {
|
|
@@ -569,7 +574,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
569
574
|
|
|
570
575
|
3 -> {
|
|
571
576
|
setPeekHeight(getDetentHeight(detents[0]), isPresented)
|
|
572
|
-
halfExpandedRatio = minOf(getDetentHeight(detents[1]).toFloat() / screenHeight.toFloat(),
|
|
577
|
+
halfExpandedRatio = minOf(getDetentHeight(detents[1]).toFloat() / screenHeight.toFloat(), 0.9f)
|
|
573
578
|
expandedOffset = screenHeight - getDetentHeight(detents[2])
|
|
574
579
|
}
|
|
575
580
|
}
|
|
@@ -695,11 +700,10 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
695
700
|
|
|
696
701
|
/**
|
|
697
702
|
* Emits position change to the delegate if the position has changed.
|
|
698
|
-
* @param index The current detent index (discrete, used as fallback)
|
|
699
703
|
* @param positionPx The current position in pixels (screen Y coordinate)
|
|
700
704
|
* @param realtime Whether the position is a real-time value (during drag or animation tracking)
|
|
701
705
|
*/
|
|
702
|
-
private fun emitChangePositionDelegate(
|
|
706
|
+
private fun emitChangePositionDelegate(positionPx: Int, realtime: Boolean) {
|
|
703
707
|
if (positionPx == lastEmittedPositionPx) return
|
|
704
708
|
|
|
705
709
|
lastEmittedPositionPx = positionPx
|
|
@@ -853,26 +857,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
853
857
|
|
|
854
858
|
private fun getCurrentPositionPx(sheetView: View): Int = ScreenUtils.getScreenY(sheetView)
|
|
855
859
|
|
|
856
|
-
/**
|
|
857
|
-
* Returns the detent index for the current position.
|
|
858
|
-
* Only reports a higher index when the sheet has reached that detent's height.
|
|
859
|
-
*/
|
|
860
|
-
private fun getDetentIndexForPosition(positionPx: Int): Int {
|
|
861
|
-
if (detents.isEmpty()) return 0
|
|
862
|
-
|
|
863
|
-
val sheetHeight = screenHeight - positionPx
|
|
864
|
-
|
|
865
|
-
// Find the highest detent index that the sheet has reached
|
|
866
|
-
for (i in detents.indices.reversed()) {
|
|
867
|
-
val detentHeight = getDetentHeight(detents[i])
|
|
868
|
-
if (sheetHeight >= detentHeight) {
|
|
869
|
-
return i
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
return 0
|
|
874
|
-
}
|
|
875
|
-
|
|
876
860
|
private fun handleDragBegin(sheetView: View) {
|
|
877
861
|
val detentInfo = getCurrentDetentInfo(sheetView)
|
|
878
862
|
val detent = getDetentValueForIndex(detentInfo.index)
|
|
@@ -980,6 +964,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
980
964
|
super.onSizeChanged(w, h, oldw, oldh)
|
|
981
965
|
if (w == oldw && h == oldh) return
|
|
982
966
|
|
|
967
|
+
// Fix issue where size keeps changing when full expanding to full screen (edgeToEdgeFullScreen enabled)
|
|
968
|
+
if (h + statusBarHeight >= screenHeight && isExpanded && oldw == w) return
|
|
969
|
+
|
|
983
970
|
delegate?.viewControllerDidChangeSize(w, h)
|
|
984
971
|
|
|
985
972
|
val oldScreenHeight = screenHeight
|
|
@@ -991,7 +978,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
991
978
|
positionFooter()
|
|
992
979
|
storeResolvedPosition(currentDetentIndex)
|
|
993
980
|
val positionPx = bottomSheetView?.let { ScreenUtils.getScreenY(it) } ?: screenHeight
|
|
994
|
-
emitChangePositionDelegate(
|
|
981
|
+
emitChangePositionDelegate(positionPx, realtime = false)
|
|
995
982
|
}
|
|
996
983
|
}
|
|
997
984
|
}
|
|
@@ -13,7 +13,9 @@ private const val RN_SCREENS_PACKAGE = "com.swmansion.rnscreens"
|
|
|
13
13
|
*/
|
|
14
14
|
class RNScreensFragmentObserver(
|
|
15
15
|
private val reactContext: ReactContext,
|
|
16
|
+
private val onModalWillPresent: () -> Unit = {},
|
|
16
17
|
private val onModalPresented: () -> Unit,
|
|
18
|
+
private val onModalWillDismiss: () -> Unit = {},
|
|
17
19
|
private val onModalDismissed: () -> Unit
|
|
18
20
|
) {
|
|
19
21
|
private var fragmentLifecycleCallback: FragmentManager.FragmentLifecycleCallbacks? = null
|
|
@@ -27,13 +29,18 @@ class RNScreensFragmentObserver(
|
|
|
27
29
|
val fragmentManager = activity.supportFragmentManager
|
|
28
30
|
|
|
29
31
|
fragmentLifecycleCallback = object : FragmentManager.FragmentLifecycleCallbacks() {
|
|
30
|
-
override fun
|
|
31
|
-
super.
|
|
32
|
+
override fun onFragmentStarted(fm: FragmentManager, fragment: Fragment) {
|
|
33
|
+
super.onFragmentStarted(fm, fragment)
|
|
34
|
+
|
|
35
|
+
if (isModalFragment(fragment) && !activeModalFragments.contains(fragment)) {
|
|
36
|
+
// Notify willPresent before adding to active set
|
|
37
|
+
if (activeModalFragments.isEmpty()) {
|
|
38
|
+
onModalWillPresent()
|
|
39
|
+
}
|
|
32
40
|
|
|
33
|
-
if (isModalFragment(fragment)) {
|
|
34
41
|
activeModalFragments.add(fragment)
|
|
35
42
|
|
|
36
|
-
// Notify
|
|
43
|
+
// Notify didPresent after modal is started
|
|
37
44
|
if (activeModalFragments.size == 1) {
|
|
38
45
|
onModalPresented()
|
|
39
46
|
}
|
|
@@ -44,9 +51,14 @@ class RNScreensFragmentObserver(
|
|
|
44
51
|
super.onFragmentStopped(fm, fragment)
|
|
45
52
|
|
|
46
53
|
if (activeModalFragments.contains(fragment)) {
|
|
54
|
+
// Notify willDismiss before removing from active set
|
|
55
|
+
if (activeModalFragments.size == 1) {
|
|
56
|
+
onModalWillDismiss()
|
|
57
|
+
}
|
|
58
|
+
|
|
47
59
|
activeModalFragments.remove(fragment)
|
|
48
60
|
|
|
49
|
-
// Notify
|
|
61
|
+
// Notify didDismiss after all modals are dismissed
|
|
50
62
|
if (activeModalFragments.isEmpty()) {
|
|
51
63
|
onModalDismissed()
|
|
52
64
|
}
|
|
@@ -25,6 +25,11 @@
|
|
|
25
25
|
BOOL _isDragging;
|
|
26
26
|
NSInteger _pendingDetentIndex;
|
|
27
27
|
|
|
28
|
+
// Transitioning tracker
|
|
29
|
+
CADisplayLink *_transitioningTimer;
|
|
30
|
+
UIView *_transitionFakeView;
|
|
31
|
+
BOOL _isTransitioning;
|
|
32
|
+
|
|
28
33
|
// Reference to parent TrueSheetViewController (if presented from another sheet)
|
|
29
34
|
__weak TrueSheetViewController *_parentSheetController;
|
|
30
35
|
|
|
@@ -36,6 +41,9 @@
|
|
|
36
41
|
|
|
37
42
|
// Resolved detent positions (Y coordinate when sheet rests at each detent)
|
|
38
43
|
NSMutableArray<NSNumber *> *_resolvedDetentPositions;
|
|
44
|
+
|
|
45
|
+
// Tracks whether this sheet has a presented controller (e.g., RN Screens modal)
|
|
46
|
+
BOOL _hasPresentedController;
|
|
39
47
|
}
|
|
40
48
|
|
|
41
49
|
#pragma mark - Initialization
|
|
@@ -55,9 +63,12 @@
|
|
|
55
63
|
_isPresented = NO;
|
|
56
64
|
_activeDetentIndex = -1;
|
|
57
65
|
_pendingDetentIndex = -1;
|
|
66
|
+
_isTransitioning = NO;
|
|
67
|
+
_transitionFakeView = [UIView new];
|
|
58
68
|
|
|
59
69
|
_blurInteraction = YES;
|
|
60
70
|
_resolvedDetentPositions = [NSMutableArray array];
|
|
71
|
+
_hasPresentedController = NO;
|
|
61
72
|
}
|
|
62
73
|
return self;
|
|
63
74
|
}
|
|
@@ -75,10 +86,6 @@
|
|
|
75
86
|
return self.presentedViewController == nil;
|
|
76
87
|
}
|
|
77
88
|
|
|
78
|
-
- (BOOL)isActiveAndVisible {
|
|
79
|
-
return self.isViewLoaded && self.view.window != nil;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
89
|
- (UIView *)presentedView {
|
|
83
90
|
return self.sheetPresentationController.presentedView;
|
|
84
91
|
}
|
|
@@ -137,6 +144,11 @@
|
|
|
137
144
|
|
|
138
145
|
// Only trigger on initial presentation, not repositioning
|
|
139
146
|
if (!_isPresented) {
|
|
147
|
+
// Initially store resolved position during presentation
|
|
148
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
149
|
+
[self storeResolvedPositionForIndex:self.currentDetentIndex];
|
|
150
|
+
});
|
|
151
|
+
|
|
140
152
|
// Capture parent sheet reference if presented from another TrueSheet
|
|
141
153
|
UIViewController *presenter = self.presentingViewController;
|
|
142
154
|
if ([presenter isKindOfClass:[TrueSheetViewController class]]) {
|
|
@@ -149,7 +161,7 @@
|
|
|
149
161
|
|
|
150
162
|
if ([self.delegate respondsToSelector:@selector(viewControllerWillPresentAtIndex:position:detent:)]) {
|
|
151
163
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
152
|
-
NSInteger index =
|
|
164
|
+
NSInteger index = self.currentDetentIndex;
|
|
153
165
|
CGFloat position = self.currentPosition;
|
|
154
166
|
CGFloat detent = [self detentValueForIndex:index];
|
|
155
167
|
|
|
@@ -157,6 +169,8 @@
|
|
|
157
169
|
});
|
|
158
170
|
}
|
|
159
171
|
}
|
|
172
|
+
|
|
173
|
+
[self setupTransitionTracker];
|
|
160
174
|
}
|
|
161
175
|
|
|
162
176
|
- (void)viewDidAppear:(BOOL)animated {
|
|
@@ -190,17 +204,10 @@
|
|
|
190
204
|
[super viewWillDisappear:animated];
|
|
191
205
|
|
|
192
206
|
if (self.isDismissing) {
|
|
193
|
-
_isPresented = NO;
|
|
194
|
-
_activeDetentIndex = -1;
|
|
195
|
-
|
|
196
207
|
if ([self.delegate respondsToSelector:@selector(viewControllerWillDismiss)]) {
|
|
197
208
|
[self.delegate viewControllerWillDismiss];
|
|
198
209
|
}
|
|
199
210
|
|
|
200
|
-
dispatch_async(dispatch_get_main_queue(), ^{
|
|
201
|
-
[self emitChangePositionDelegateWithPosition:self.currentPosition realtime:NO];
|
|
202
|
-
});
|
|
203
|
-
|
|
204
211
|
// Notify the parent sheet (if any) that it is about to regain focus
|
|
205
212
|
if (_parentSheetController) {
|
|
206
213
|
if ([_parentSheetController.delegate respondsToSelector:@selector(viewControllerWillFocus)]) {
|
|
@@ -208,6 +215,8 @@
|
|
|
208
215
|
}
|
|
209
216
|
}
|
|
210
217
|
}
|
|
218
|
+
|
|
219
|
+
[self setupTransitionTracker];
|
|
211
220
|
}
|
|
212
221
|
|
|
213
222
|
- (void)viewDidDisappear:(BOOL)animated {
|
|
@@ -215,6 +224,9 @@
|
|
|
215
224
|
|
|
216
225
|
// Only dispatch didDismiss when actually dismissing (not when another modal is presented on top)
|
|
217
226
|
if (self.isDismissing) {
|
|
227
|
+
_isPresented = NO;
|
|
228
|
+
_activeDetentIndex = -1;
|
|
229
|
+
|
|
218
230
|
// Notify the parent sheet (if any) that it regained focus
|
|
219
231
|
if (_parentSheetController) {
|
|
220
232
|
if ([_parentSheetController.delegate respondsToSelector:@selector(viewControllerDidFocus)]) {
|
|
@@ -236,17 +248,13 @@
|
|
|
236
248
|
[self.delegate viewControllerDidChangeSize:self.view.frame.size];
|
|
237
249
|
}
|
|
238
250
|
|
|
239
|
-
// Check if there's an active presented controller that has settled (not being presented/dismissed)
|
|
240
251
|
UIViewController *presented = self.presentedViewController;
|
|
241
252
|
BOOL hasPresentedController = presented != nil && !presented.isBeingPresented && !presented.isBeingDismissed;
|
|
242
253
|
|
|
243
|
-
if (!_isDragging) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
[self emitChangePositionDelegateWithPosition:self.currentPosition realtime:hasPresentedController];
|
|
249
|
-
});
|
|
254
|
+
if (!_isDragging && !_isTransitioning) {
|
|
255
|
+
// Update stored position for current detent (handles content size changes)
|
|
256
|
+
[self storeResolvedPositionForIndex:self.currentDetentIndex];
|
|
257
|
+
[self emitChangePositionDelegateWithPosition:self.currentPosition realtime:hasPresentedController];
|
|
250
258
|
}
|
|
251
259
|
|
|
252
260
|
// Emit pending detent change after programmatic resize settles
|
|
@@ -263,6 +271,62 @@
|
|
|
263
271
|
}
|
|
264
272
|
}
|
|
265
273
|
|
|
274
|
+
#pragma mark - Presentation Tracking (for RN Screens integration)
|
|
275
|
+
|
|
276
|
+
- (void)presentViewController:(UIViewController *)viewControllerToPresent
|
|
277
|
+
animated:(BOOL)flag
|
|
278
|
+
completion:(void (^)(void))completion {
|
|
279
|
+
// Check if this is a non-TrueSheet controller (e.g., RN Screens modal)
|
|
280
|
+
BOOL isExternalController = ![viewControllerToPresent isKindOfClass:[TrueSheetViewController class]];
|
|
281
|
+
|
|
282
|
+
if (isExternalController && !_hasPresentedController) {
|
|
283
|
+
_hasPresentedController = YES;
|
|
284
|
+
|
|
285
|
+
// Emit blur events when an external controller is presented on top
|
|
286
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerWillBlur)]) {
|
|
287
|
+
[self.delegate viewControllerWillBlur];
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
[super presentViewController:viewControllerToPresent
|
|
292
|
+
animated:flag
|
|
293
|
+
completion:^{
|
|
294
|
+
if (isExternalController && self->_hasPresentedController) {
|
|
295
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerDidBlur)]) {
|
|
296
|
+
[self.delegate viewControllerDidBlur];
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (completion) {
|
|
300
|
+
completion();
|
|
301
|
+
}
|
|
302
|
+
}];
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion {
|
|
306
|
+
UIViewController *presented = self.presentedViewController;
|
|
307
|
+
BOOL isExternalController = presented && ![presented isKindOfClass:[TrueSheetViewController class]];
|
|
308
|
+
|
|
309
|
+
if (isExternalController && _hasPresentedController) {
|
|
310
|
+
// Emit focus events when external controller is dismissed
|
|
311
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerWillFocus)]) {
|
|
312
|
+
[self.delegate viewControllerWillFocus];
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
[super dismissViewControllerAnimated:flag
|
|
317
|
+
completion:^{
|
|
318
|
+
if (isExternalController && self->_hasPresentedController) {
|
|
319
|
+
self->_hasPresentedController = NO;
|
|
320
|
+
if ([self.delegate respondsToSelector:@selector(viewControllerDidFocus)]) {
|
|
321
|
+
[self.delegate viewControllerDidFocus];
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (completion) {
|
|
325
|
+
completion();
|
|
326
|
+
}
|
|
327
|
+
}];
|
|
328
|
+
}
|
|
329
|
+
|
|
266
330
|
#pragma mark - Gesture Handling
|
|
267
331
|
|
|
268
332
|
- (TrueSheetContentView *)findContentView:(UIView *)view {
|
|
@@ -333,7 +397,7 @@
|
|
|
333
397
|
}
|
|
334
398
|
|
|
335
399
|
- (void)handlePanGesture:(UIPanGestureRecognizer *)gesture {
|
|
336
|
-
NSInteger index =
|
|
400
|
+
NSInteger index = self.currentDetentIndex;
|
|
337
401
|
CGFloat detent = [self detentValueForIndex:index];
|
|
338
402
|
|
|
339
403
|
if ([self.delegate respondsToSelector:@selector(viewControllerDidDrag:index:position:detent:)]) {
|
|
@@ -349,13 +413,15 @@
|
|
|
349
413
|
break;
|
|
350
414
|
case UIGestureRecognizerStateEnded:
|
|
351
415
|
case UIGestureRecognizerStateCancelled: {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
416
|
+
if (!_isTransitioning) {
|
|
417
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
418
|
+
// Store resolved position when drag ends
|
|
419
|
+
[self storeResolvedPositionForIndex:self.currentDetentIndex];
|
|
420
|
+
[self emitChangePositionDelegateWithPosition:self.currentPosition realtime:NO];
|
|
421
|
+
});
|
|
422
|
+
}
|
|
356
423
|
|
|
357
|
-
|
|
358
|
-
});
|
|
424
|
+
_isDragging = NO;
|
|
359
425
|
break;
|
|
360
426
|
}
|
|
361
427
|
default:
|
|
@@ -365,6 +431,56 @@
|
|
|
365
431
|
|
|
366
432
|
#pragma mark - Position Tracking
|
|
367
433
|
|
|
434
|
+
- (void)setupTransitionTracker {
|
|
435
|
+
if (!self.transitionCoordinator)
|
|
436
|
+
return;
|
|
437
|
+
|
|
438
|
+
_isTransitioning = YES;
|
|
439
|
+
|
|
440
|
+
CGRect dismissedFrame = CGRectMake(0, self.screenHeight, 0, 0);
|
|
441
|
+
CGRect presentedFrame = CGRectMake(0, self.presentedView.frame.origin.y, 0, 0);
|
|
442
|
+
|
|
443
|
+
// Set starting fake view position
|
|
444
|
+
_transitionFakeView.frame = self.isDismissing ? presentedFrame : dismissedFrame;
|
|
445
|
+
[self storeResolvedPositionForIndex:self.currentDetentIndex];
|
|
446
|
+
|
|
447
|
+
auto animation = ^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
|
|
448
|
+
[[context containerView] addSubview:self->_transitionFakeView];
|
|
449
|
+
|
|
450
|
+
// Set the fake view target position
|
|
451
|
+
self->_transitionFakeView.frame = self.isDismissing ? dismissedFrame : presentedFrame;
|
|
452
|
+
|
|
453
|
+
self->_transitioningTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleTransitionTracker)];
|
|
454
|
+
[self->_transitioningTimer addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
[self.transitionCoordinator
|
|
458
|
+
animateAlongsideTransition:animation
|
|
459
|
+
completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
|
|
460
|
+
[self->_transitioningTimer setPaused:YES];
|
|
461
|
+
[self->_transitioningTimer invalidate];
|
|
462
|
+
[self->_transitionFakeView removeFromSuperview];
|
|
463
|
+
self->_isTransitioning = NO;
|
|
464
|
+
}];
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
- (void)handleTransitionTracker {
|
|
468
|
+
if (!_isDragging && _transitionFakeView.layer) {
|
|
469
|
+
CALayer *layer = _transitionFakeView.layer;
|
|
470
|
+
|
|
471
|
+
CGFloat layerPosition = layer.presentationLayer.frame.origin.y;
|
|
472
|
+
|
|
473
|
+
if (self.currentPosition >= self.screenHeight) {
|
|
474
|
+
// Dismissing position
|
|
475
|
+
[self emitChangePositionDelegateWithPosition:layerPosition realtime:YES];
|
|
476
|
+
} else {
|
|
477
|
+
// Presenting position
|
|
478
|
+
CGFloat position = fmax(self.currentPosition, layerPosition);
|
|
479
|
+
[self emitChangePositionDelegateWithPosition:position realtime:YES];
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
368
484
|
- (void)emitChangePositionDelegateWithPosition:(CGFloat)position realtime:(BOOL)realtime {
|
|
369
485
|
// Use epsilon comparison to avoid missing updates due to floating point precision
|
|
370
486
|
if (fabs(_lastPosition - position) > 0.01) {
|
|
@@ -741,7 +857,7 @@
|
|
|
741
857
|
(UISheetPresentationController *)sheetPresentationController {
|
|
742
858
|
if ([self.delegate respondsToSelector:@selector(viewControllerDidChangeDetent:position:detent:)]) {
|
|
743
859
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
744
|
-
NSInteger index =
|
|
860
|
+
NSInteger index = self.currentDetentIndex;
|
|
745
861
|
if (index >= 0) {
|
|
746
862
|
CGFloat detent = [self detentValueForIndex:index];
|
|
747
863
|
[self.delegate viewControllerDidChangeDetent:index position:self.currentPosition detent:detent];
|
package/package.json
CHANGED