react-native-navigation 7.47.0 → 7.48.0

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.
@@ -986,10 +986,19 @@ export interface SideMenuSide {
986
986
  height?: number;
987
987
  /**
988
988
  * Stretch sideMenu contents when opened past the width
989
+ *
990
+ * **Not applicable when `openMode` is `aboveContent`**
991
+ *
989
992
  * #### (iOS specific)
990
993
  * @default true
991
994
  */
992
995
  shouldStretchDrawer?: boolean;
996
+ /**
997
+ * Configure the opening mode of the side menu
998
+ * #### (iOS specific)
999
+ * @default 'pushContent'
1000
+ */
1001
+ openMode?: 'pushContent' | 'aboveContent';
993
1002
  }
994
1003
  export interface OptionsSideMenu {
995
1004
  /**
@@ -127,6 +127,14 @@ typedef void (^MMDrawerControllerDrawerVisualStateBlock)(MMDrawerController *dra
127
127
 
128
128
  @interface MMDrawerController : UIViewController
129
129
 
130
+ /**
131
+ Enum defining how the drawer opens
132
+ */
133
+ typedef NS_ENUM(NSInteger, MMDrawerOpenMode) {
134
+ MMDrawerOpenModePushContent = 0, // Original behavior - pushes content aside
135
+ MMDrawerOpenModeAboveContent = 1, // Overlay behavior - opens above content
136
+ };
137
+
130
138
  ///---------------------------------------
131
139
  /// @name Accessing Drawer Container View Controller Properties
132
140
  ///---------------------------------------
@@ -213,6 +221,18 @@ typedef void (^MMDrawerControllerDrawerVisualStateBlock)(MMDrawerController *dra
213
221
  @property(nonatomic, assign) BOOL shouldStretchLeftDrawer;
214
222
  @property(nonatomic, assign) BOOL shouldStretchRightDrawer;
215
223
 
224
+ /**
225
+ * Specifies how the drawer should open relative to the center content.
226
+ *
227
+ * Possible values:
228
+ * - MMDrawerOpenModePushContent: The drawer will push the center content aside when opening (traditional behavior).
229
+ * - MMDrawerOpenModeAboveContent: The drawer will open above the center content with a semi-transparent overlay.
230
+ *
231
+ * By default, this value is set to MMDrawerOpenModePushContent.
232
+ */
233
+ @property(nonatomic, assign) MMDrawerOpenMode leftDrawerOpenMode;
234
+ @property(nonatomic, assign) MMDrawerOpenMode rightDrawerOpenMode;
235
+
216
236
  /**
217
237
  The current open side of the drawer.
218
238
 
@@ -600,4 +620,6 @@ typedef void (^MMDrawerControllerDrawerVisualStateBlock)(MMDrawerController *dra
600
620
  (BOOL (^)(MMDrawerController *drawerController, UIGestureRecognizer *gesture,
601
621
  UITouch *touch))gestureShouldRecognizeTouchBlock;
602
622
 
623
+ - (void)side:(MMDrawerSide)drawerSide openMode:(MMDrawerOpenMode)openMode;
624
+
603
625
  @end
@@ -152,6 +152,8 @@ static NSString *MMDrawerOpenSideKey = @"MMDrawerOpenSide";
152
152
  CGFloat _maximumRightDrawerWidth;
153
153
  CGFloat _maximumLeftDrawerWidth;
154
154
  UIColor *_statusBarViewBackgroundColor;
155
+ MMDrawerOpenMode _leftDrawerOpenMode;
156
+ MMDrawerOpenMode _rightDrawerOpenMode;
155
157
  }
156
158
 
157
159
  @property(nonatomic, assign, readwrite) MMDrawerSide openSide;
@@ -160,13 +162,15 @@ static NSString *MMDrawerOpenSideKey = @"MMDrawerOpenSide";
160
162
  @property(nonatomic, strong) MMDrawerCenterContainerView *centerContainerView;
161
163
  @property(nonatomic, strong) UIView *dummyStatusBarView;
162
164
 
163
- @property(nonatomic, assign) CGRect startingPanRect;
164
165
  @property(nonatomic, copy) MMDrawerControllerDrawerVisualStateBlock drawerVisualState;
165
166
  @property(nonatomic, copy) MMDrawerGestureShouldRecognizeTouchBlock gestureShouldRecognizeTouch;
166
167
  @property(nonatomic, copy) MMDrawerGestureCompletionBlock gestureStart;
167
168
  @property(nonatomic, copy) MMDrawerGestureCompletionBlock gestureCompletion;
168
169
  @property(nonatomic, assign, getter=isAnimatingDrawer) BOOL animatingDrawer;
169
170
  @property(nonatomic, strong) UIGestureRecognizer *pan;
171
+ @property (nonatomic, strong) UIView *centerContentOverlay;
172
+ @property(nonatomic, assign) CGRect startingPanRect;
173
+ @property(nonatomic, assign) MMDrawerSide panDrawerSide;
170
174
 
171
175
  @end
172
176
 
@@ -227,6 +231,8 @@ static NSString *MMDrawerOpenSideKey = @"MMDrawerOpenSide";
227
231
  [self setShowsShadow:YES];
228
232
  [self setShouldStretchLeftDrawer:YES];
229
233
  [self setShouldStretchRightDrawer:YES];
234
+ [self side:MMDrawerSideRight openMode:MMDrawerOpenModePushContent];
235
+ [self side:MMDrawerSideLeft openMode:MMDrawerOpenModePushContent];
230
236
 
231
237
  [self setOpenDrawerGestureModeMask:MMOpenDrawerGestureModeNone];
232
238
  [self setCloseDrawerGestureModeMask:MMCloseDrawerGestureModeNone];
@@ -323,52 +329,126 @@ static NSString *MMDrawerOpenSideKey = @"MMDrawerOpenSide";
323
329
  }
324
330
  } else {
325
331
  [self setAnimatingDrawer:animated];
326
- CGRect newFrame = self.childControllerContainerView.bounds;
332
+ MMDrawerSide visibleSide = self.openSide;
327
333
 
328
- CGFloat distance = ABS(CGRectGetMinX(self.centerContainerView.frame));
329
- NSTimeInterval duration = MAX(distance / ABS(velocity), MMDrawerMinimumAnimationDuration);
334
+ if (visibleSide == MMDrawerSideNone) {
335
+ [self setAnimatingDrawer:NO];
336
+ if (completion) {
337
+ completion(NO);
338
+ }
339
+ return;
340
+ }
330
341
 
331
- BOOL leftDrawerVisible = CGRectGetMinX(self.centerContainerView.frame) > 0;
332
- BOOL rightDrawerVisible = CGRectGetMinX(self.centerContainerView.frame) < 0;
342
+ UIViewController *sideDrawerViewController = [self sideDrawerViewControllerForSide:visibleSide];
343
+ [sideDrawerViewController beginAppearanceTransition:NO animated:animated];
333
344
 
334
- MMDrawerSide visibleSide = MMDrawerSideNone;
335
- CGFloat percentVisble = 0.0;
345
+ MMDrawerOpenMode openMode = [self getDrawerOpenMode:visibleSide];
336
346
 
337
- if (leftDrawerVisible) {
338
- CGFloat visibleDrawerPoints = CGRectGetMinX(self.centerContainerView.frame);
339
- percentVisble = MAX(0.0, visibleDrawerPoints / self.maximumLeftDrawerWidth);
340
- visibleSide = MMDrawerSideLeft;
341
- } else if (rightDrawerVisible) {
342
- CGFloat visibleDrawerPoints = CGRectGetWidth(self.centerContainerView.frame) -
343
- CGRectGetMaxX(self.centerContainerView.frame);
344
- percentVisble = MAX(0.0, visibleDrawerPoints / self.maximumRightDrawerWidth);
345
- visibleSide = MMDrawerSideRight;
347
+ if (openMode == MMDrawerOpenModeAboveContent) {
348
+ [self closeDrawerInOverlayMode:sideDrawerViewController
349
+ visibleSide:visibleSide
350
+ animated:animated
351
+ velocity:velocity
352
+ animationOptions:options
353
+ completion:completion];
354
+ } else {
355
+ [self closeDrawerInPushMode:sideDrawerViewController
356
+ visibleSide:visibleSide
357
+ animated:animated
358
+ velocity:velocity
359
+ animationOptions:options
360
+ completion:completion];
346
361
  }
362
+ }
363
+ }
347
364
 
348
- UIViewController *sideDrawerViewController =
349
- [self sideDrawerViewControllerForSide:visibleSide];
365
+ - (void)closeDrawerInOverlayMode:(UIViewController *)sideDrawerViewController
366
+ visibleSide:(MMDrawerSide)visibleSide
367
+ animated:(BOOL)animated
368
+ velocity:(CGFloat)velocity
369
+ animationOptions:(UIViewAnimationOptions)options
370
+ completion:(void (^)(BOOL finished))completion {
371
+ CGFloat maximumDrawerWidth = [self maximumDrawerWidthForSide:visibleSide];
350
372
 
351
- [self updateDrawerVisualStateForDrawerSide:visibleSide percentVisible:percentVisble];
373
+ CGRect currentFrame = sideDrawerViewController.view.frame;
374
+ CGRect finalFrame = currentFrame;
352
375
 
353
- [sideDrawerViewController beginAppearanceTransition:NO animated:animated];
376
+ // Set final position based on side
377
+ if (visibleSide == MMDrawerSideLeft) {
378
+ finalFrame.origin.x = -maximumDrawerWidth; // Off-screen left
379
+ } else { // MMDrawerSideRight
380
+ finalFrame.origin.x = self.view.bounds.size.width; // Off-screen right
381
+ }
354
382
 
355
- [UIView animateWithDuration:(animated ? duration : 0.0)
356
- delay:0.0
357
- options:options
358
- animations:^{
359
- [self setNeedsStatusBarAppearanceUpdateIfSupported];
360
- [self.centerContainerView setFrame:newFrame withLayoutAlpha:0.0];
361
- [self updateDrawerVisualStateForDrawerSide:visibleSide percentVisible:0.0];
362
- }
363
- completion:^(BOOL finished) {
364
- [sideDrawerViewController endAppearanceTransition];
365
- [self setOpenSide:MMDrawerSideNone];
366
- [self resetDrawerVisualStateForDrawerSide:visibleSide];
367
- [self setAnimatingDrawer:NO];
368
- if (completion) {
369
- completion(finished);
370
- }
371
- }];
383
+ // Ensure overlay is in view hierarchy
384
+ if (self.centerContentOverlay && self.centerContentOverlay.superview == nil) {
385
+ [self.centerContainerView addSubview:self.centerContentOverlay];
386
+ [self.centerContainerView bringSubviewToFront:self.centerContentOverlay];
387
+ self.centerContentOverlay.alpha = 0.5;
388
+ }
389
+
390
+ CGFloat distance = ABS(currentFrame.origin.x - finalFrame.origin.x);
391
+ NSTimeInterval duration = [self animationDurationForDistance:distance velocity:velocity];
392
+
393
+ [UIView animateWithDuration:(animated ? duration : 0.0)
394
+ delay:0.0
395
+ options:options
396
+ animations:^{
397
+ [self setNeedsStatusBarAppearanceUpdateIfSupported];
398
+ [sideDrawerViewController.view setFrame:finalFrame];
399
+ self.centerContentOverlay.alpha = 0.0;
400
+ [self updateDrawerVisualStateForDrawerSide:visibleSide percentVisible:0.0];
401
+ }
402
+ completion:^(BOOL finished) {
403
+ [self completeDrawerClosingForSide:visibleSide
404
+ sideDrawerViewController:sideDrawerViewController
405
+ finished:finished
406
+ completion:completion];
407
+ [self.centerContentOverlay removeFromSuperview];
408
+ }];
409
+ }
410
+
411
+ - (void)closeDrawerInPushMode:(UIViewController *)sideDrawerViewController
412
+ visibleSide:(MMDrawerSide)visibleSide
413
+ animated:(BOOL)animated
414
+ velocity:(CGFloat)velocity
415
+ animationOptions:(UIViewAnimationOptions)options
416
+ completion:(void (^)(BOOL finished))completion {
417
+
418
+ CGRect newFrame = self.childControllerContainerView.bounds;
419
+
420
+ CGFloat distance = ABS(CGRectGetMinX(self.centerContainerView.frame));
421
+ NSTimeInterval duration = [self animationDurationForDistance:distance velocity:velocity];
422
+ sideDrawerViewController.view.hidden = NO;
423
+
424
+ [UIView animateWithDuration:(animated ? duration : 0.0)
425
+ delay:0.0
426
+ options:options
427
+ animations:^{
428
+ [self setNeedsStatusBarAppearanceUpdateIfSupported];
429
+ [self.centerContainerView setFrame:newFrame];
430
+ [self updateDrawerVisualStateForDrawerSide:visibleSide percentVisible:0.0];
431
+ }
432
+ completion:^(BOOL finished) {
433
+ [self completeDrawerClosingForSide:visibleSide
434
+ sideDrawerViewController:sideDrawerViewController
435
+ finished:finished
436
+ completion:completion];
437
+ }];
438
+ }
439
+
440
+ - (void)completeDrawerClosingForSide:(MMDrawerSide)visibleSide
441
+ sideDrawerViewController:(UIViewController *)sideDrawerViewController
442
+ finished:(BOOL)finished
443
+ completion:(void (^)(BOOL finished))completion {
444
+ [sideDrawerViewController endAppearanceTransition];
445
+
446
+ [self setOpenSide:MMDrawerSideNone];
447
+ [self resetDrawerVisualStateForDrawerSide:visibleSide];
448
+ [self setAnimatingDrawer:NO];
449
+
450
+ if (completion) {
451
+ completion(finished);
372
452
  }
373
453
  }
374
454
 
@@ -397,50 +477,207 @@ static NSString *MMDrawerOpenSideKey = @"MMDrawerOpenSide";
397
477
  }
398
478
  } else {
399
479
  [self setAnimatingDrawer:animated];
400
- UIViewController *sideDrawerViewController =
401
- [self sideDrawerViewControllerForSide:drawerSide];
480
+ UIViewController *sideDrawerViewController = [self sideDrawerViewControllerForSide:drawerSide];
481
+
402
482
  if (self.openSide != drawerSide) {
403
483
  [self prepareToPresentDrawer:drawerSide animated:animated];
404
484
  }
405
485
 
406
486
  if (sideDrawerViewController) {
407
- CGRect newFrame;
408
- CGRect oldFrame = self.centerContainerView.frame;
409
- if (drawerSide == MMDrawerSideLeft) {
410
- newFrame = self.centerContainerView.frame;
411
- newFrame.origin.x = self.maximumLeftDrawerWidth;
487
+ MMDrawerOpenMode openMode = [self getDrawerOpenMode:drawerSide];
488
+
489
+ if (openMode == MMDrawerOpenModeAboveContent) {
490
+ [self openDrawerInOverlayMode:sideDrawerViewController
491
+ drawerSide:drawerSide
492
+ animated:animated
493
+ velocity:velocity
494
+ animationOptions:options
495
+ completion:completion];
412
496
  } else {
413
- newFrame = self.centerContainerView.frame;
414
- newFrame.origin.x = 0 - self.maximumRightDrawerWidth;
497
+ [self openDrawerInPushMode:sideDrawerViewController
498
+ drawerSide:drawerSide
499
+ animated:animated
500
+ velocity:velocity
501
+ animationOptions:options
502
+ completion:completion];
415
503
  }
504
+ }
505
+ }
506
+ }
416
507
 
417
- CGFloat distance = ABS(CGRectGetMinX(oldFrame) - newFrame.origin.x);
418
- NSTimeInterval duration =
419
- MAX(distance / ABS(velocity), MMDrawerMinimumAnimationDuration);
420
-
421
- [UIView animateWithDuration:(animated ? duration : 0.0)
422
- delay:0.0
423
- options:options
424
- animations:^{
425
- [self setNeedsStatusBarAppearanceUpdateIfSupported];
426
- [self.centerContainerView setFrame:newFrame withLayoutAlpha:1.0];
427
- [self updateDrawerVisualStateForDrawerSide:drawerSide percentVisible:1.0];
428
- }
429
- completion:^(BOOL finished) {
430
- // End the appearance transition if it already wasn't open.
431
- if (drawerSide != self.openSide) {
432
- [sideDrawerViewController endAppearanceTransition];
433
- }
434
- [self setOpenSide:drawerSide];
508
+ - (void)openDrawerInOverlayMode:(UIViewController *)sideDrawerViewController
509
+ drawerSide:(MMDrawerSide)drawerSide
510
+ animated:(BOOL)animated
511
+ velocity:(CGFloat)velocity
512
+ animationOptions:(UIViewAnimationOptions)options
513
+ completion:(void (^)(BOOL finished))completion {
435
514
 
436
- [self resetDrawerVisualStateForDrawerSide:drawerSide];
437
- [self setAnimatingDrawer:NO];
438
- if (completion) {
439
- completion(finished);
440
- }
441
- }];
515
+ CGFloat maximumDrawerWidth = [self maximumDrawerWidthForSide:drawerSide];
516
+
517
+ CGRect drawerFrame = sideDrawerViewController.view.frame;
518
+ CGRect initialFrame = sideDrawerViewController.view.frame;
519
+
520
+ drawerFrame.size.width = maximumDrawerWidth;
521
+ initialFrame.size.width = maximumDrawerWidth;
522
+
523
+ if (drawerSide == MMDrawerSideLeft) {
524
+ drawerFrame.origin.x = 0; // Final position
525
+
526
+ if (self.openSide != drawerSide) {
527
+ initialFrame.origin.x = -maximumDrawerWidth; // Start off-screen
528
+ [sideDrawerViewController.view setFrame:initialFrame];
529
+ }
530
+ } else { // MMDrawerSideRight
531
+ CGFloat screenWidth = self.view.bounds.size.width;
532
+ drawerFrame.origin.x = screenWidth - maximumDrawerWidth; // Final position
533
+
534
+ if (self.openSide != drawerSide) {
535
+ initialFrame.origin.x = screenWidth; // Start off-screen
536
+ [sideDrawerViewController.view setFrame:initialFrame];
442
537
  }
443
538
  }
539
+
540
+ [self setupCenterContentOverlay];
541
+ [self.centerContainerView addSubview:self.centerContentOverlay];
542
+ [self.centerContainerView bringSubviewToFront:self.centerContentOverlay];
543
+ self.centerContentOverlay.alpha = 0.0; // Start transparent
544
+
545
+ // Make sure drawer is visible and in front
546
+ sideDrawerViewController.view.hidden = NO;
547
+ [self.childControllerContainerView bringSubviewToFront:sideDrawerViewController.view];
548
+
549
+ CGFloat distance = ABS(initialFrame.origin.x - drawerFrame.origin.x);
550
+ NSTimeInterval duration = [self animationDurationForDistance:distance velocity:velocity];
551
+
552
+ [UIView animateWithDuration:(animated ? duration : 0.0)
553
+ delay:0.0
554
+ options:options
555
+ animations:^{
556
+ [self setNeedsStatusBarAppearanceUpdateIfSupported];
557
+ [sideDrawerViewController.view setFrame:drawerFrame]; // Move to final position
558
+ self.centerContentOverlay.alpha = 0.5;
559
+ [self updateDrawerVisualStateForDrawerSide:drawerSide percentVisible:1.0];
560
+ }
561
+ completion:^(BOOL finished) {
562
+ [self completeDrawerOpeningForSide:drawerSide
563
+ sideDrawerViewController:sideDrawerViewController
564
+ finished:finished
565
+ completion:completion];
566
+ }];
567
+ }
568
+
569
+ - (void)openDrawerInPushMode:(UIViewController *)sideDrawerViewController
570
+ drawerSide:(MMDrawerSide)drawerSide
571
+ animated:(BOOL)animated
572
+ velocity:(CGFloat)velocity
573
+ animationOptions:(UIViewAnimationOptions)options
574
+ completion:(void (^)(BOOL finished))completion {
575
+
576
+ CGRect oldFrame = self.centerContainerView.frame;
577
+ CGRect newFrame = self.centerContainerView.frame;
578
+
579
+ if (drawerSide == MMDrawerSideLeft) {
580
+ newFrame.origin.x = self.maximumLeftDrawerWidth;
581
+ } else {
582
+ newFrame.origin.x = 0 - self.maximumRightDrawerWidth;
583
+ }
584
+
585
+ CGFloat distance = ABS(CGRectGetMinX(oldFrame) - newFrame.origin.x);
586
+ NSTimeInterval duration = MAX(distance / ABS(velocity), MMDrawerMinimumAnimationDuration);
587
+ BOOL isGestureOpen = !CGRectIsNull(self.startingPanRect) && [self shouldStretchForSide:drawerSide];
588
+ sideDrawerViewController.view.hidden = NO;
589
+
590
+ [UIView animateWithDuration:(animated ? duration : 0.0)
591
+ delay:0.0
592
+ options:options
593
+ animations:^{
594
+ [self setNeedsStatusBarAppearanceUpdateIfSupported];
595
+ [self.centerContainerView setFrame:newFrame withLayoutAlpha:1.0];
596
+ [self updateDrawerVisualStateForDrawerSide:drawerSide percentVisible:1.0];
597
+ }
598
+ completion:^(BOOL finished) {
599
+ [self completeDrawerOpeningForSide:drawerSide
600
+ sideDrawerViewController:sideDrawerViewController
601
+ finished:finished
602
+ completion:^(BOOL innerFinished) {
603
+
604
+ if (isGestureOpen) {
605
+ [self applyBounceForDrawerSide:drawerSide];
606
+ }
607
+
608
+ if (completion) {
609
+ completion(innerFinished);
610
+ }
611
+ }];
612
+ }];
613
+ }
614
+
615
+ - (void)applyBounceForDrawerSide:(MMDrawerSide)drawerSide {
616
+ UIViewController *sideDrawerViewController = [self sideDrawerViewControllerForSide:drawerSide];
617
+ if (!sideDrawerViewController) return;
618
+
619
+ CGRect centerFrame = self.centerContainerView.frame;
620
+ CGRect bounceFrame = centerFrame;
621
+ CGFloat bounceAmount = 15.0;
622
+
623
+ if (drawerSide == MMDrawerSideLeft) {
624
+ bounceFrame.origin.x += bounceAmount;
625
+ } else {
626
+ bounceFrame.origin.x -= bounceAmount;
627
+ }
628
+
629
+ CATransform3D originalTransform = sideDrawerViewController.view.layer.transform;
630
+ CGFloat scale = 1.0 + (bounceAmount / (drawerSide == MMDrawerSideLeft ?
631
+ self.maximumLeftDrawerWidth :
632
+ self.maximumRightDrawerWidth));
633
+
634
+ CATransform3D bounceTransform = CATransform3DMakeScale(scale, 1.0, 1.0);
635
+
636
+ if (drawerSide == MMDrawerSideLeft) {
637
+ bounceTransform = CATransform3DTranslate(bounceTransform,
638
+ self.maximumLeftDrawerWidth * (scale - 1.0) / 2.0,
639
+ 0, 0);
640
+ } else {
641
+ bounceTransform = CATransform3DTranslate(bounceTransform,
642
+ -self.maximumRightDrawerWidth * (scale - 1.0) / 2.0,
643
+ 0, 0);
644
+ }
645
+ [UIView animateWithDuration:0.15
646
+ delay:0.0
647
+ options:UIViewAnimationOptionCurveEaseOut
648
+ animations:^{
649
+ self.centerContainerView.frame = bounceFrame;
650
+ sideDrawerViewController.view.layer.transform = bounceTransform;
651
+ }
652
+ completion:^(BOOL bounceFinished) {
653
+ [UIView animateWithDuration:0.15
654
+ delay:0.0
655
+ options:UIViewAnimationOptionCurveEaseIn
656
+ animations:^{
657
+ self.centerContainerView.frame = centerFrame;
658
+ sideDrawerViewController.view.layer.transform = originalTransform;
659
+ }
660
+ completion:nil];
661
+ }];
662
+ }
663
+
664
+ - (void)completeDrawerOpeningForSide:(MMDrawerSide)drawerSide
665
+ sideDrawerViewController:(UIViewController *)sideDrawerViewController
666
+ finished:(BOOL)finished
667
+ completion:(void (^)(BOOL finished))completion {
668
+
669
+ // End the appearance transition if it already wasn't open.
670
+ if (drawerSide != self.openSide) {
671
+ [sideDrawerViewController endAppearanceTransition];
672
+ }
673
+
674
+ [self setOpenSide:drawerSide];
675
+ [self resetDrawerVisualStateForDrawerSide:drawerSide];
676
+ [self setAnimatingDrawer:NO];
677
+
678
+ if (completion) {
679
+ completion(finished);
680
+ }
444
681
  }
445
682
 
446
683
  #pragma mark - Updating the Center View Controller
@@ -801,7 +1038,7 @@ static NSString *MMDrawerOpenSideKey = @"MMDrawerOpenSide";
801
1038
  [super viewDidLoad];
802
1039
 
803
1040
  [self.view setBackgroundColor:[UIColor blackColor]];
804
-
1041
+ [self.view setAccessibilityIdentifier:@"SideMenuContainer"];
805
1042
  [self setupGestureRecognizers];
806
1043
  }
807
1044
 
@@ -1070,6 +1307,16 @@ static NSString *MMDrawerOpenSideKey = @"MMDrawerOpenSide";
1070
1307
  [self.dummyStatusBarView setBackgroundColor:_statusBarViewBackgroundColor];
1071
1308
  }
1072
1309
 
1310
+ - (void)setLeftDrawerOpenMode:(MMDrawerOpenMode)openMode {
1311
+ if (self.openSide == MMDrawerSideLeft) return;
1312
+ _leftDrawerOpenMode = openMode;
1313
+ }
1314
+
1315
+ - (void)setRightDrawerOpenMode:(MMDrawerOpenMode)openMode {
1316
+ if (self.openSide == MMDrawerSideRight) return;
1317
+ _rightDrawerOpenMode = openMode;
1318
+ }
1319
+
1073
1320
  - (void)setAnimatingDrawer:(BOOL)animatingDrawer {
1074
1321
  _animatingDrawer = animatingDrawer;
1075
1322
  [self.view setUserInteractionEnabled:!animatingDrawer];
@@ -1085,6 +1332,7 @@ static NSString *MMDrawerOpenSideKey = @"MMDrawerOpenSide";
1085
1332
  [self updatePanHandlersState];
1086
1333
  }
1087
1334
 
1335
+
1088
1336
  #pragma mark - Getters
1089
1337
  - (CGFloat)maximumLeftDrawerWidth {
1090
1338
  if (self.leftDrawerViewController) {
@@ -1155,107 +1403,420 @@ static NSString *MMDrawerOpenSideKey = @"MMDrawerOpenSide";
1155
1403
  return _statusBarViewBackgroundColor;
1156
1404
  }
1157
1405
 
1406
+ - (MMDrawerOpenMode)rightDrawerOpenMode {
1407
+ return _rightDrawerOpenMode;
1408
+ }
1409
+
1410
+ - (MMDrawerOpenMode)leftDrawerOpenMode {
1411
+ return _leftDrawerOpenMode;
1412
+ }
1413
+
1158
1414
  #pragma mark - Gesture Handlers
1159
1415
 
1160
1416
  - (void)tapGestureCallback:(UITapGestureRecognizer *)tapGesture {
1161
1417
  if (self.openSide != MMDrawerSideNone && self.isAnimatingDrawer == NO) {
1162
- [self closeDrawerAnimated:YES
1163
- completion:^(BOOL finished) {
1164
- if (self.gestureCompletion) {
1165
- self.gestureCompletion(self, tapGesture);
1166
- }
1167
- }];
1418
+ // Get the tap location
1419
+ CGPoint tapLocation = [tapGesture locationInView:self.childControllerContainerView];
1420
+
1421
+ // Get the open drawer view controller
1422
+ UIViewController *sideDrawerViewController = [self sideDrawerViewControllerForSide:self.openSide];
1423
+
1424
+ // Check if we are in above content mode
1425
+ MMDrawerOpenMode openMode = [self getDrawerOpenMode:self.openSide];
1426
+
1427
+ // Only close if not tapping on the drawer itself in above content mode
1428
+ BOOL shouldClose = YES;
1429
+
1430
+ if (openMode == MMDrawerOpenModeAboveContent && sideDrawerViewController) {
1431
+ // Check if tap is inside the drawer view
1432
+ CGPoint tapInDrawerView = [tapGesture locationInView:sideDrawerViewController.view];
1433
+ if (CGRectContainsPoint(sideDrawerViewController.view.bounds, tapInDrawerView)) {
1434
+ shouldClose = NO;
1435
+ }
1436
+ }
1437
+
1438
+ if (shouldClose) {
1439
+ [self closeDrawerAnimated:YES
1440
+ completion:^(BOOL finished) {
1441
+ if (self.gestureCompletion) {
1442
+ self.gestureCompletion(self, tapGesture);
1443
+ }
1444
+ }];
1445
+ }
1168
1446
  }
1169
1447
  }
1170
1448
 
1171
1449
  - (void)panGestureCallback:(UIPanGestureRecognizer *)panGesture {
1172
1450
  switch (panGesture.state) {
1173
- case UIGestureRecognizerStateBegan: {
1174
- if (self.gestureStart) {
1175
- self.gestureStart(self, panGesture);
1451
+ case UIGestureRecognizerStateBegan:
1452
+ [self handlePanGestureBegan:panGesture];
1453
+ break;
1454
+ case UIGestureRecognizerStateChanged:
1455
+ [self handlePanGestureChanged:panGesture];
1456
+ break;
1457
+ case UIGestureRecognizerStateEnded:
1458
+ case UIGestureRecognizerStateCancelled:
1459
+ [self handlePanGestureEnded:panGesture];
1460
+ break;
1461
+ default:
1462
+ break;
1463
+ }
1464
+ }
1465
+
1466
+ - (void)handlePanGestureBegan:(UIPanGestureRecognizer *)panGesture {
1467
+ if (self.gestureStart) {
1468
+ self.gestureStart(self, panGesture);
1469
+ }
1470
+
1471
+ if (self.animatingDrawer) {
1472
+ [panGesture setEnabled:NO];
1473
+ return;
1474
+ }
1475
+
1476
+ MMDrawerSide drawerSide = [self determineDrawerSideForPanGesture:panGesture];
1477
+ if (drawerSide == MMDrawerSideNone) {
1478
+ return;
1479
+ }
1480
+
1481
+ MMDrawerOpenMode openMode = [self getDrawerOpenMode:drawerSide];
1482
+ if (openMode == MMDrawerOpenModeAboveContent) {
1483
+ [self setupPanningStartFrameOverlayMode:drawerSide];
1484
+ } else {
1485
+ [self setupPanningStartFramePushMode];
1486
+ }
1487
+ }
1488
+
1489
+ - (MMDrawerSide)determineDrawerSideForPanGesture:(UIPanGestureRecognizer *)panGesture {
1490
+ MMDrawerSide drawerSide = self.openSide;
1491
+
1492
+ if (drawerSide == MMDrawerSideNone) {
1493
+ CGPoint velocity = [panGesture velocityInView:self.view];
1494
+ drawerSide = (velocity.x > 0) ? MMDrawerSideLeft : MMDrawerSideRight;
1495
+
1496
+ if ((drawerSide == MMDrawerSideLeft && !_leftSideEnabled) ||
1497
+ (drawerSide == MMDrawerSideRight && !_rightSideEnabled)) {
1498
+ drawerSide = (drawerSide == MMDrawerSideLeft) ? MMDrawerSideRight : MMDrawerSideLeft;
1499
+
1500
+ if ((drawerSide == MMDrawerSideLeft && !_leftSideEnabled) ||
1501
+ (drawerSide == MMDrawerSideRight && !_rightSideEnabled)) {
1502
+ return MMDrawerSideNone;
1503
+ }
1176
1504
  }
1177
- if (self.animatingDrawer) {
1178
- [panGesture setEnabled:NO];
1179
- break;
1505
+ }
1506
+ return drawerSide;
1507
+ }
1508
+
1509
+ - (void)setupPanningStartFrameOverlayMode:(MMDrawerSide)drawerSide {
1510
+ UIViewController *drawerViewController = [self sideDrawerViewControllerForSide:drawerSide];
1511
+
1512
+ // If drawer is closed, set up initial position
1513
+ if (self.openSide == MMDrawerSideNone) {
1514
+ self.startingPanRect =
1515
+ [self calcClosedDrawerPanStartFrameInOverlay:drawerSide
1516
+ drawerViewController:(UIViewController *)drawerViewController];
1517
+
1518
+ [drawerViewController.view setFrame:self.startingPanRect];
1519
+
1520
+ // Ensure drawer is visible and in front
1521
+ drawerViewController.view.hidden = NO;
1522
+ [self.childControllerContainerView bringSubviewToFront:drawerViewController.view];
1523
+ } else {
1524
+ self.startingPanRect = drawerViewController.view.frame;
1525
+ }
1526
+
1527
+ // Save for upcoming gesture updates in above-content mode
1528
+ self.panDrawerSide = drawerSide;
1529
+ }
1530
+
1531
+ - (void)setupPanningStartFramePushMode {
1532
+ self.startingPanRect = self.centerContainerView.frame;
1533
+ }
1534
+
1535
+ - (CGRect)calcClosedDrawerPanStartFrameInOverlay:(MMDrawerSide)drawerSide
1536
+ drawerViewController:(UIViewController *)drawerViewController {
1537
+ CGRect drawerFrame = drawerViewController.view.frame;
1538
+ CGFloat maximumDrawerWidth = [self maximumDrawerWidthForSide:drawerSide];
1539
+
1540
+ drawerFrame.size.width = maximumDrawerWidth;
1541
+
1542
+ // Position off-screen based on side
1543
+ if (drawerSide == MMDrawerSideLeft) {
1544
+ drawerFrame.origin.x = -maximumDrawerWidth;
1545
+ } else {
1546
+ drawerFrame.origin.x = self.view.bounds.size.width;
1547
+ }
1548
+ return drawerFrame;
1549
+ }
1550
+
1551
+ - (void)handlePanGestureChanged:(UIPanGestureRecognizer *)panGesture {
1552
+ self.view.userInteractionEnabled = NO;
1553
+
1554
+ MMDrawerSide drawerSide = self.panDrawerSide;
1555
+ MMDrawerOpenMode openMode = [self getDrawerOpenMode:drawerSide];
1556
+ if (openMode == MMDrawerOpenModeAboveContent) {
1557
+ [self handlePanGestureChangedOverlayMode:panGesture forDrawerSide:drawerSide];
1558
+ } else {
1559
+ [self handlePanGestureChangedPushMode:panGesture];
1560
+ }
1561
+ }
1562
+
1563
+ - (void)handlePanGestureChangedOverlayMode:(UIPanGestureRecognizer *)panGesture
1564
+ forDrawerSide:(MMDrawerSide)drawerSide {
1565
+
1566
+ UIViewController *drawerViewController = [self sideDrawerViewControllerForSide:drawerSide];
1567
+ if (!drawerViewController) {
1568
+ return;
1569
+ }
1570
+
1571
+ CGFloat maximumDrawerWidth = [self maximumDrawerWidthForSide:drawerSide];
1572
+ CGPoint translatedPoint = [panGesture translationInView:self.view];
1573
+
1574
+ CGRect newFrame = [self calculateNewFrameForOverlayDrawer:drawerViewController
1575
+ withTranslation:translatedPoint
1576
+ forDrawerSide:drawerSide
1577
+ maximumDrawerWidth:maximumDrawerWidth];
1578
+
1579
+ CGFloat percentVisible = [self calculatePercentVisibleForOverlayDrawer:drawerSide
1580
+ withFrame:newFrame
1581
+ maximumDrawerWidth:maximumDrawerWidth];
1582
+
1583
+ // Handle overlay
1584
+ [self updateOverlayWithPercentVisible:percentVisible];
1585
+
1586
+ MMDrawerSide visibleSide = (percentVisible > 0.01) ? drawerSide : MMDrawerSideNone;
1587
+
1588
+ [self updateAppearanceTransitions:self.openSide toSide:visibleSide];
1589
+
1590
+ // Apply new frame
1591
+ drawerViewController.view.frame = newFrame;
1592
+ [self.childControllerContainerView bringSubviewToFront:drawerViewController.view];
1593
+ }
1594
+
1595
+ - (CGRect)calculateNewFrameForOverlayDrawer:(UIViewController *)drawerViewController
1596
+ withTranslation:(CGPoint)translatedPoint
1597
+ forDrawerSide:(MMDrawerSide)drawerSide
1598
+ maximumDrawerWidth:(CGFloat)maximumDrawerWidth {
1599
+ CGRect newFrame = drawerViewController.view.frame;
1600
+ if (self.openSide == drawerSide) {
1601
+ // If drawer is already open, adjust from starting position
1602
+ newFrame.origin.x = self.startingPanRect.origin.x + translatedPoint.x;
1603
+ } else {
1604
+ // If drawer is closed, calculate from off-screen position
1605
+ if (drawerSide == MMDrawerSideLeft) {
1606
+ newFrame.origin.x = -maximumDrawerWidth + translatedPoint.x;
1180
1607
  } else {
1181
- self.startingPanRect = self.centerContainerView.frame;
1182
- }
1183
- }
1184
- case UIGestureRecognizerStateChanged: {
1185
- self.view.userInteractionEnabled = NO;
1186
- CGRect newFrame = self.startingPanRect;
1187
- CGPoint translatedPoint = [panGesture translationInView:self.centerContainerView];
1188
- newFrame.origin.x =
1189
- [self roundedOriginXForDrawerConstriants:CGRectGetMinX(self.startingPanRect) +
1190
- translatedPoint.x];
1191
- newFrame = CGRectIntegral(newFrame);
1192
- CGFloat xOffset = newFrame.origin.x;
1193
-
1194
- MMDrawerSide visibleSide = MMDrawerSideNone;
1195
- CGFloat percentVisible = 0.0;
1196
- if (xOffset > 0) {
1197
- visibleSide = MMDrawerSideLeft;
1198
- percentVisible = xOffset / self.maximumLeftDrawerWidth;
1199
- } else if (xOffset < 0) {
1200
- visibleSide = MMDrawerSideRight;
1201
- percentVisible = ABS(xOffset) / self.maximumRightDrawerWidth;
1202
- }
1203
-
1204
- if ((!_leftSideEnabled && visibleSide == MMDrawerSideLeft) ||
1205
- (!_rightSideEnabled && visibleSide == MMDrawerSideRight)) {
1206
- return;
1608
+ CGFloat screenWidth = self.view.bounds.size.width;
1609
+ newFrame.origin.x = screenWidth + translatedPoint.x;
1207
1610
  }
1611
+ }
1208
1612
 
1209
- UIViewController *visibleSideDrawerViewController =
1210
- [self sideDrawerViewControllerForSide:visibleSide];
1613
+ // Apply constraints based on drawer side
1614
+ if (drawerSide == MMDrawerSideLeft) {
1615
+ newFrame.origin.x = MIN(0, newFrame.origin.x);
1616
+ newFrame.origin.x = MAX(-maximumDrawerWidth, newFrame.origin.x);
1617
+ } else {
1618
+ CGFloat screenWidth = self.view.bounds.size.width;
1619
+ newFrame.origin.x = MAX(screenWidth - maximumDrawerWidth, newFrame.origin.x);
1620
+ newFrame.origin.x = MIN(screenWidth, newFrame.origin.x);
1621
+ }
1622
+
1623
+ return newFrame;
1624
+ }
1211
1625
 
1212
- if (self.openSide != visibleSide) {
1213
- // Handle disappearing the visible drawer
1214
- UIViewController *sideDrawerViewController =
1215
- [self sideDrawerViewControllerForSide:self.openSide];
1216
- [sideDrawerViewController beginAppearanceTransition:NO animated:NO];
1217
- [sideDrawerViewController endAppearanceTransition];
1626
+ - (CGFloat)calculatePercentVisibleForOverlayDrawer:(MMDrawerSide)drawerSide
1627
+ withFrame:(CGRect)frame
1628
+ maximumDrawerWidth:(CGFloat)maximumDrawerWidth {
1629
+ CGFloat percentVisible;
1630
+ if (drawerSide == MMDrawerSideLeft) {
1631
+ percentVisible = (maximumDrawerWidth + frame.origin.x) / maximumDrawerWidth;
1632
+ } else {
1633
+ CGFloat rightEdge = self.view.bounds.size.width;
1634
+ percentVisible = (rightEdge - frame.origin.x) / maximumDrawerWidth;
1635
+ }
1636
+ return MAX(0, MIN(1.0, percentVisible));
1637
+ }
1218
1638
 
1219
- // Drawer is about to become visible
1220
- [self prepareToPresentDrawer:visibleSide animated:NO];
1221
- [visibleSideDrawerViewController endAppearanceTransition];
1222
- [self setOpenSide:visibleSide];
1223
- } else if (visibleSide == MMDrawerSideNone) {
1224
- [self setOpenSide:MMDrawerSideNone];
1639
+ - (void)updateOverlayWithPercentVisible:(CGFloat)percentVisible {
1640
+ [self setupCenterContentOverlay];
1641
+ if (self.centerContentOverlay.superview != self.centerContainerView) {
1642
+ [self.centerContainerView addSubview:self.centerContentOverlay];
1643
+ [self.centerContainerView bringSubviewToFront:self.centerContentOverlay];
1644
+ }
1645
+ self.centerContentOverlay.alpha = percentVisible * 0.5;
1646
+ }
1647
+
1648
+ - (void)updateAppearanceTransitions:(MMDrawerSide)fromSide toSide:(MMDrawerSide)toSide {
1649
+ if (fromSide != toSide) {
1650
+ if (fromSide != MMDrawerSideNone) {
1651
+ UIViewController *sideDrawerVC = [self sideDrawerViewControllerForSide:fromSide];
1652
+ [sideDrawerVC beginAppearanceTransition:NO animated:NO];
1653
+ [sideDrawerVC endAppearanceTransition];
1225
1654
  }
1226
1655
 
1227
- [self updateDrawerVisualStateForDrawerSide:visibleSide percentVisible:percentVisible];
1656
+ if (toSide != MMDrawerSideNone) {
1657
+ [self prepareToPresentDrawer:toSide animated:NO];
1658
+ UIViewController *visibleDrawerVC = [self sideDrawerViewControllerForSide:toSide];
1659
+ [visibleDrawerVC endAppearanceTransition];
1660
+ }
1228
1661
 
1229
- [self.centerContainerView
1230
- setCenter:CGPointMake(CGRectGetMidX(newFrame), CGRectGetMidY(newFrame))];
1662
+ [self setOpenSide:toSide];
1663
+ }
1664
+ }
1231
1665
 
1232
- newFrame = self.centerContainerView.frame;
1233
- newFrame.origin.x = floor(newFrame.origin.x);
1234
- newFrame.origin.y = floor(newFrame.origin.y);
1235
- self.centerContainerView.frame = newFrame;
1666
+ - (void)handlePanGestureChangedPushMode:(UIPanGestureRecognizer *)panGesture {
1667
+ CGRect newFrame = self.startingPanRect;
1668
+ CGPoint translatedPoint = [panGesture translationInView:self.centerContainerView];
1236
1669
 
1237
- [self.centerContainerView addSubview:self.centerContainerView.overlayView];
1238
- [self.centerContainerView bringSubviewToFront:self.centerContainerView.overlayView];
1239
- self.centerContainerView.overlayView.alpha = percentVisible;
1670
+ newFrame.origin.x = [self
1671
+ roundedOriginXForDrawerConstriants:CGRectGetMinX(self.startingPanRect) + translatedPoint.x];
1672
+ newFrame = CGRectIntegral(newFrame);
1673
+ CGFloat xOffset = newFrame.origin.x;
1240
1674
 
1241
- break;
1675
+ MMDrawerSide visibleSide = MMDrawerSideNone;
1676
+ CGFloat percentVisible = 0.0;
1677
+ if (xOffset > 0) {
1678
+ visibleSide = MMDrawerSideLeft;
1679
+ percentVisible = xOffset / self.maximumLeftDrawerWidth;
1680
+ } else if (xOffset < 0) {
1681
+ visibleSide = MMDrawerSideRight;
1682
+ percentVisible = ABS(xOffset) / self.maximumRightDrawerWidth;
1242
1683
  }
1243
- case UIGestureRecognizerStateEnded:
1244
- case UIGestureRecognizerStateCancelled: {
1245
- self.startingPanRect = CGRectNull;
1246
- CGPoint velocity = [panGesture velocityInView:self.childControllerContainerView];
1247
- [self finishAnimationForPanGestureWithXVelocity:velocity.x
1248
- completion:^(BOOL finished) {
1249
- if (self.gestureCompletion) {
1250
- self.gestureCompletion(self, panGesture);
1251
- }
1252
- }];
1253
- self.view.userInteractionEnabled = YES;
1254
- break;
1684
+
1685
+ if ((!_leftSideEnabled && visibleSide == MMDrawerSideLeft) ||
1686
+ (!_rightSideEnabled && visibleSide == MMDrawerSideRight)) {
1687
+ return;
1255
1688
  }
1256
- default:
1257
- break;
1689
+
1690
+ [self handleAppearanceTransitionPushMode:visibleSide];
1691
+ [self updateDrawerVisualStateForDrawerSide:visibleSide percentVisible:percentVisible];
1692
+ [self.centerContainerView
1693
+ setCenter:CGPointMake(CGRectGetMidX(newFrame), CGRectGetMidY(newFrame))];
1694
+
1695
+ newFrame = self.centerContainerView.frame;
1696
+ newFrame.origin.x = floor(newFrame.origin.x);
1697
+ newFrame.origin.y = floor(newFrame.origin.y);
1698
+ self.centerContainerView.frame = newFrame;
1699
+
1700
+ [self.centerContainerView addSubview:self.centerContainerView.overlayView];
1701
+ [self.centerContainerView bringSubviewToFront:self.centerContainerView.overlayView];
1702
+ self.centerContainerView.overlayView.alpha = percentVisible;
1703
+ }
1704
+
1705
+ - (void)handleAppearanceTransitionPushMode:(MMDrawerSide)visibleSide {
1706
+ UIViewController *visibleSideDrawerViewController = [self sideDrawerViewControllerForSide:visibleSide];
1707
+
1708
+ if (self.openSide != visibleSide) {
1709
+ // Handle disappearing the visible drawer
1710
+ UIViewController *sideDrawerVC = [self sideDrawerViewControllerForSide:self.openSide];
1711
+ [sideDrawerVC beginAppearanceTransition:NO animated:NO];
1712
+ [sideDrawerVC endAppearanceTransition];
1713
+
1714
+ // Drawer is about to become visible
1715
+ [self prepareToPresentDrawer:visibleSide animated:NO];
1716
+ [visibleSideDrawerViewController endAppearanceTransition];
1717
+ [self setOpenSide:visibleSide];
1718
+ } else if (visibleSide == MMDrawerSideNone) {
1719
+ [self setOpenSide:MMDrawerSideNone];
1720
+ }
1721
+ }
1722
+
1723
+ - (void)handlePanGestureEnded:(UIPanGestureRecognizer *)panGesture {
1724
+ MMDrawerSide drawerSide = self.panDrawerSide;
1725
+ MMDrawerOpenMode openMode = [self getDrawerOpenMode:drawerSide];
1726
+
1727
+ if (openMode == MMDrawerOpenModeAboveContent) {
1728
+ [self completePanningOverlayMode:drawerSide withPanGesture:panGesture];
1729
+ } else {
1730
+ [self completePanningPushMode:panGesture];
1258
1731
  }
1732
+
1733
+ self.startingPanRect = CGRectNull;
1734
+ self.view.userInteractionEnabled = YES;
1735
+ }
1736
+
1737
+ - (void)completePanningOverlayMode:(MMDrawerSide)drawerSide
1738
+ withPanGesture:(UIPanGestureRecognizer *)panGesture {
1739
+ // Get drawer view controller
1740
+ UIViewController *drawerVC = [self sideDrawerViewControllerForSide:drawerSide];
1741
+ if (!drawerVC) {
1742
+ self.panDrawerSide = MMDrawerSideNone;
1743
+ return;
1744
+ }
1745
+
1746
+ BOOL shouldOpen = [self shouldOpenDrawerForGestureEnd:panGesture withDrawerSide:drawerSide andCurrentPosition:drawerVC.view.frame.origin.x];
1747
+ [self animateOverlayDrawerToFinalState:shouldOpen forDrawerSide:drawerSide withDrawerVC:drawerVC andPanGesture:panGesture];
1748
+ }
1749
+
1750
+ - (BOOL)shouldOpenDrawerForGestureEnd:(UIPanGestureRecognizer *)panGesture withDrawerSide:(MMDrawerSide)drawerSide andCurrentPosition:(CGFloat)currentX {
1751
+ CGPoint velocity = [panGesture velocityInView:self.view];
1752
+ CGFloat maximumDrawerWidth = [self maximumDrawerWidthForSide:drawerSide];
1753
+ CGFloat screenWidth = self.view.bounds.size.width;
1754
+ BOOL shouldOpen = NO;
1755
+
1756
+ if (drawerSide == MMDrawerSideLeft) {
1757
+ if (velocity.x > 500) shouldOpen = YES; // Fast right swipe
1758
+ else if (velocity.x < -500) shouldOpen = NO; // Fast left swipe
1759
+ else shouldOpen = (currentX > -maximumDrawerWidth/2.0); // Based on position
1760
+ } else {
1761
+ if (velocity.x < -500) shouldOpen = YES; // Fast left swipe
1762
+ else if (velocity.x > 500) shouldOpen = NO; // Fast right swipe
1763
+ else shouldOpen = (currentX < screenWidth - maximumDrawerWidth/2.0); // Based on position
1764
+ }
1765
+
1766
+ return shouldOpen;
1767
+ }
1768
+
1769
+ - (void)animateOverlayDrawerToFinalState:(BOOL)shouldOpen forDrawerSide:(MMDrawerSide)drawerSide withDrawerVC:(UIViewController *)drawerVC andPanGesture:(UIPanGestureRecognizer *)panGesture {
1770
+ CGFloat maximumDrawerWidth = [self maximumDrawerWidthForSide:drawerSide];
1771
+ CGFloat screenWidth = self.view.bounds.size.width;
1772
+
1773
+ [UIView animateWithDuration:0.25
1774
+ animations:^{
1775
+ CGRect frame = drawerVC.view.frame;
1776
+
1777
+ if (shouldOpen) {
1778
+ if (drawerSide == MMDrawerSideLeft) {
1779
+ frame.origin.x = 0;
1780
+ } else {
1781
+ frame.origin.x = screenWidth - maximumDrawerWidth;
1782
+ }
1783
+
1784
+ self.centerContentOverlay.alpha = 0.5;
1785
+ } else {
1786
+ if (drawerSide == MMDrawerSideLeft) {
1787
+ frame.origin.x = -maximumDrawerWidth;
1788
+ } else {
1789
+ frame.origin.x = screenWidth;
1790
+ }
1791
+
1792
+ self.centerContentOverlay.alpha = 0.0;
1793
+ }
1794
+
1795
+ drawerVC.view.frame = frame;
1796
+ } completion:^(BOOL finished) {
1797
+ if (shouldOpen) {
1798
+ [self setOpenSide:drawerSide];
1799
+ } else {
1800
+ [self setOpenSide:MMDrawerSideNone];
1801
+ [self.centerContentOverlay removeFromSuperview];
1802
+ }
1803
+
1804
+ self.panDrawerSide = MMDrawerSideNone;
1805
+
1806
+ if (self.gestureCompletion) {
1807
+ self.gestureCompletion(self, panGesture);
1808
+ }
1809
+ }];
1810
+ }
1811
+
1812
+ - (void)completePanningPushMode:(UIPanGestureRecognizer *)panGesture {
1813
+ CGPoint velocity = [panGesture velocityInView:self.childControllerContainerView];
1814
+ [self finishAnimationForPanGestureWithXVelocity:velocity.x
1815
+ completion:^(BOOL finished) {
1816
+ if (self.gestureCompletion) {
1817
+ self.gestureCompletion(self, panGesture);
1818
+ }
1819
+ }];
1259
1820
  }
1260
1821
 
1261
1822
  - (void)updatePanHandlersState {
@@ -1270,6 +1831,29 @@ static NSString *MMDrawerOpenSideKey = @"MMDrawerOpenSide";
1270
1831
  }
1271
1832
  }
1272
1833
 
1834
+ - (void)setupCenterContentOverlay {
1835
+ if (!self.centerContentOverlay) {
1836
+ // Create overlay view if it doesn't exist
1837
+ self.centerContentOverlay = [[UIView alloc] initWithFrame:self.centerContainerView.bounds];
1838
+ self.centerContentOverlay.backgroundColor = [UIColor blackColor];
1839
+ self.centerContentOverlay.alpha = 0.0; // Start fully transparent
1840
+ self.centerContentOverlay.userInteractionEnabled = YES;
1841
+
1842
+ // Add tap gesture to close drawer when tapping overlay
1843
+ UITapGestureRecognizer *tapGesture =
1844
+ [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(overlayTapped:)];
1845
+ [self.centerContentOverlay addGestureRecognizer:tapGesture];
1846
+ }
1847
+
1848
+ // Update frame to match current center container bounds
1849
+ self.centerContentOverlay.frame = self.centerContainerView.bounds;
1850
+ }
1851
+
1852
+ - (void)overlayTapped:(UITapGestureRecognizer *)tapGesture {
1853
+ // Close drawer when overlay is tapped
1854
+ [self closeDrawerAnimated:YES completion:nil];
1855
+ }
1856
+
1273
1857
  #pragma mark - iOS 7 Status Bar Helpers
1274
1858
  - (UIViewController *)childViewControllerForStatusBarStyle {
1275
1859
  return [self childViewControllerForSide:self.openSide];
@@ -1367,23 +1951,46 @@ static NSString *MMDrawerOpenSideKey = @"MMDrawerOpenSide";
1367
1951
  }
1368
1952
  }
1369
1953
 
1954
+ - (void)side:(MMDrawerSide)drawerSide openMode:(MMDrawerOpenMode)openMode {
1955
+ if (drawerSide == MMDrawerSideLeft) {
1956
+ self.leftDrawerOpenMode = openMode;
1957
+ } else if (drawerSide == MMDrawerSideRight) {
1958
+ self.rightDrawerOpenMode = openMode;
1959
+ }
1960
+ }
1961
+
1370
1962
  - (void)applyOvershootScaleTransformForDrawerSide:(MMDrawerSide)drawerSide
1371
1963
  percentVisible:(CGFloat)percentVisible {
1372
-
1373
1964
  if (percentVisible >= 1.f) {
1374
1965
  CATransform3D transform = CATransform3DIdentity;
1375
- UIViewController *sideDrawerViewController =
1376
- [self sideDrawerViewControllerForSide:drawerSide];
1377
- if (drawerSide == MMDrawerSideLeft) {
1378
- transform = CATransform3DMakeScale(percentVisible, 1.f, 1.f);
1379
- transform = CATransform3DTranslate(
1380
- transform, self.maximumLeftDrawerWidth * (percentVisible - 1.f) / 2, 0.f, 0.f);
1381
- } else if (drawerSide == MMDrawerSideRight) {
1382
- transform = CATransform3DMakeScale(percentVisible, 1.f, 1.f);
1383
- transform = CATransform3DTranslate(
1384
- transform, -self.maximumRightDrawerWidth * (percentVisible - 1.f) / 2, 0.f, 0.f);
1966
+
1967
+ MMDrawerOpenMode openMode = [self getDrawerOpenMode:drawerSide];
1968
+ UIViewController *sideDrawerViewController = [self sideDrawerViewControllerForSide:drawerSide];
1969
+
1970
+ if (openMode == MMDrawerOpenModePushContent) {
1971
+ CGFloat stretchFactor = MIN(percentVisible, 1.1);
1972
+
1973
+ if (drawerSide == MMDrawerSideLeft) {
1974
+ transform = CATransform3DMakeScale(stretchFactor, 1.f, 1.f);
1975
+ transform = CATransform3DTranslate(transform, self.maximumLeftDrawerWidth * (stretchFactor - 1.f) / 2, 0.f, 0.f);
1976
+ } else if (drawerSide == MMDrawerSideRight) {
1977
+ transform = CATransform3DMakeScale(stretchFactor, 1.f, 1.f);
1978
+ transform = CATransform3DTranslate(transform, -self.maximumRightDrawerWidth * (stretchFactor - 1.f) / 2, 0.f, 0.f);
1979
+ }
1980
+
1981
+ sideDrawerViewController.view.layer.transform = transform;
1982
+ }
1983
+ else if (openMode == MMDrawerOpenModeAboveContent) {
1984
+ if (drawerSide == MMDrawerSideLeft) {
1985
+ transform = CATransform3DMakeScale(percentVisible, 1.f, 1.f);
1986
+ transform = CATransform3DTranslate(transform, self.maximumLeftDrawerWidth * (percentVisible - 1.f) / 2, 0.f, 0.f);
1987
+ } else if (drawerSide == MMDrawerSideRight) {
1988
+ transform = CATransform3DMakeScale(percentVisible, 1.f, 1.f);
1989
+ transform = CATransform3DTranslate(transform, -self.maximumRightDrawerWidth * (percentVisible - 1.f) / 2, 0.f, 0.f);
1990
+ }
1991
+
1992
+ sideDrawerViewController.view.layer.transform = transform;
1385
1993
  }
1386
- sideDrawerViewController.view.layer.transform = transform;
1387
1994
  }
1388
1995
  }
1389
1996
 
@@ -1679,4 +2286,18 @@ static inline CGFloat originXForDrawerOriginAndTargetOriginOffset(CGFloat origin
1679
2286
  return (CGRectContainsPoint(rightBezelRect, point) &&
1680
2287
  [self isPointContainedWithinCenterViewContentRect:point]);
1681
2288
  }
2289
+
2290
+ - (MMDrawerOpenMode)getDrawerOpenMode:(MMDrawerSide)drawerSide {
2291
+ return (drawerSide == MMDrawerSideLeft) ? self.leftDrawerOpenMode : self.rightDrawerOpenMode;
2292
+ }
2293
+
2294
+ - (CGFloat)maximumDrawerWidthForSide:(MMDrawerSide)drawerSide {
2295
+ return (drawerSide == MMDrawerSideLeft) ? self.maximumLeftDrawerWidth : self.maximumRightDrawerWidth;
2296
+ }
2297
+
2298
+ // Helper method to calculate animation duration based on distance and velocity
2299
+ - (NSTimeInterval)animationDurationForDistance:(CGFloat)distance velocity:(CGFloat)velocity {
2300
+ return MAX(distance / ABS(velocity), MMDrawerMinimumAnimationDuration);
2301
+ }
2302
+
1682
2303
  @end
@@ -1,5 +1,6 @@
1
1
  #import "RNNSideMenuPresenter.h"
2
2
  #import "RNNSideMenuController.h"
3
+ #import "RNNSideMenuSideOptions.h"
3
4
 
4
5
  @implementation RNNSideMenuPresenter
5
6
 
@@ -52,6 +53,20 @@
52
53
 
53
54
  [self.sideMenuController.view
54
55
  setBackgroundColor:[withDefault.layout.backgroundColor withDefault:nil]];
56
+
57
+ MMDrawerOpenMode openModeLeft = MMDrawerOpenModePushContent; // Default value
58
+ if (withDefault.sideMenu.left.openMode.hasValue) {
59
+ NSString *openModeString = withDefault.sideMenu.left.openMode.get;
60
+ openModeLeft = MMDrawerOpenModeFromString(openModeString);
61
+ }
62
+ [self.sideMenuController side:MMDrawerSideLeft openMode:openModeLeft];
63
+
64
+ MMDrawerOpenMode openModeRight = MMDrawerOpenModePushContent; // Default value
65
+ if (withDefault.sideMenu.right.openMode.hasValue) {
66
+ NSString *openModeString = withDefault.sideMenu.right.openMode.get;
67
+ openModeRight = MMDrawerOpenModeFromString(openModeString);
68
+ }
69
+ [self.sideMenuController side:MMDrawerSideRight openMode:openModeRight];
55
70
  }
56
71
 
57
72
  - (void)applyOptionsOnInit:(RNNNavigationOptions *)initialOptions {
@@ -112,6 +127,18 @@
112
127
  options.sideMenu.right.shouldStretchDrawer.get;
113
128
  }
114
129
 
130
+ if (options.sideMenu.left.openMode.hasValue) {
131
+ NSString *openModeString = options.sideMenu.left.openMode.get;
132
+ MMDrawerOpenMode openMode = MMDrawerOpenModeFromString(openModeString);
133
+ [self.sideMenuController side:MMDrawerSideLeft openMode:openMode];
134
+ }
135
+
136
+ if (options.sideMenu.right.openMode.hasValue) {
137
+ NSString *openModeString = options.sideMenu.right.openMode.get;
138
+ MMDrawerOpenMode openMode = MMDrawerOpenModeFromString(openModeString);
139
+ [self.sideMenuController side:MMDrawerSideRight openMode:openMode];
140
+ }
141
+
115
142
  if (options.sideMenu.left.animationVelocity.hasValue) {
116
143
  self.sideMenuController.animationVelocityLeft = options.sideMenu.left.animationVelocity.get;
117
144
  }
@@ -8,5 +8,11 @@
8
8
  @property(nonatomic, strong) Double *width;
9
9
  @property(nonatomic, strong) Bool *shouldStretchDrawer;
10
10
  @property(nonatomic, strong) Double *animationVelocity;
11
+ @property (nonatomic, strong) Text *openMode;
11
12
 
13
+ /**
14
+ * Converts a string open mode to the equivalent MMDrawerOpenMode enum value
15
+ */
16
+ MMDrawerOpenMode MMDrawerOpenModeFromString(NSString *openModeString);
17
+
12
18
  @end
@@ -10,7 +10,7 @@
10
10
  self.width = [DoubleParser parse:dict key:@"width"];
11
11
  self.shouldStretchDrawer = [BoolParser parse:dict key:@"shouldStretchDrawer"];
12
12
  self.animationVelocity = [DoubleParser parse:dict key:@"animationVelocity"];
13
-
13
+ self.openMode = [TextParser parse:dict key:@"openMode"];
14
14
  return self;
15
15
  }
16
16
 
@@ -25,6 +25,18 @@
25
25
  self.shouldStretchDrawer = options.shouldStretchDrawer;
26
26
  if (options.animationVelocity.hasValue)
27
27
  self.animationVelocity = options.animationVelocity;
28
+ if (options.openMode.hasValue)
29
+ self.openMode = options.openMode;
30
+ }
31
+
32
+ /**
33
+ Converts a string open mode to the equivalent MMDrawerOpenMode enum value
34
+ */
35
+ MMDrawerOpenMode MMDrawerOpenModeFromString(NSString *openModeString) {
36
+ if ([openModeString isEqualToString:@"aboveContent"]) {
37
+ return MMDrawerOpenModeAboveContent;
38
+ }
39
+ return MMDrawerOpenModePushContent;
28
40
  }
29
41
 
30
42
  @end
@@ -1078,10 +1078,19 @@ export interface SideMenuSide {
1078
1078
  height?: number;
1079
1079
  /**
1080
1080
  * Stretch sideMenu contents when opened past the width
1081
+ *
1082
+ * **Not applicable when `openMode` is `aboveContent`**
1083
+ *
1081
1084
  * #### (iOS specific)
1082
1085
  * @default true
1083
1086
  */
1084
1087
  shouldStretchDrawer?: boolean;
1088
+ /**
1089
+ * Configure the opening mode of the side menu
1090
+ * #### (iOS specific)
1091
+ * @default 'pushContent'
1092
+ */
1093
+ openMode?: 'pushContent' | 'aboveContent';
1085
1094
  }
1086
1095
 
1087
1096
  export interface OptionsSideMenu {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-navigation",
3
- "version": "7.47.0",
3
+ "version": "7.48.0",
4
4
  "description": "React Native Navigation - truly native navigation for iOS and Android",
5
5
  "license": "MIT",
6
6
  "nativePackage": true,