react-native-navigation 7.45.0 → 7.47.0-snapshot.1697

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.
Files changed (72) hide show
  1. package/lib/Mock/Components/ComponentScreen.tsx +2 -1
  2. package/lib/Mock/Components/LayoutComponent.tsx +9 -0
  3. package/lib/Mock/Components/SideMenu.tsx +27 -0
  4. package/lib/Mock/Layouts/BottomTabsNode.ts +4 -2
  5. package/lib/Mock/Layouts/LayoutNodeFactory.ts +14 -1
  6. package/lib/Mock/Layouts/ParentNode.ts +4 -0
  7. package/lib/Mock/Layouts/SideMenu.ts +87 -0
  8. package/lib/Mock/Stores/LayoutStore.ts +42 -10
  9. package/lib/Mock/actions/layoutActions.ts +11 -0
  10. package/lib/Mock/index.js +2 -2
  11. package/lib/Mock/mocks/NativeCommandsSender.tsx +1 -0
  12. package/lib/android/app/build.gradle +0 -1
  13. package/lib/android/app/src/main/java/com/reactnativenavigation/NavigationApplication.java +3 -9
  14. package/lib/android/app/src/main/java/com/reactnativenavigation/react/ReactView.java +3 -3
  15. package/lib/android/app/src/main/java/com/reactnativenavigation/react/modal/ModalHostLayout.kt +1 -1
  16. package/lib/android/app/src/main/java/com/reactnativenavigation/utils/ReactTypefaceUtils.java +2 -3
  17. package/lib/android/app/src/main/java/com/reactnativenavigation/utils/ReactViewGroup.kt +2 -4
  18. package/lib/android/app/src/main/java/com/reactnativenavigation/utils/ViewUtils.java +4 -6
  19. package/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/viewcontroller/LayoutDirectionApplier.kt +4 -8
  20. package/lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/viewcontroller/RootPresenter.java +1 -1
  21. package/lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/BackgroundColorAnimator.kt +4 -6
  22. package/lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/BackgroundColorEvaluator.kt +2 -4
  23. package/lib/android/app/src/reactNative71/java/com/reactnativenavigation/react/modal/ModalContentLayout.kt +6 -6
  24. package/lib/dist/Mock/Application.d.ts +6 -4
  25. package/lib/dist/Mock/Components/BottomTabs.d.ts +13 -9
  26. package/lib/dist/Mock/Components/ComponentScreen.d.ts +7 -5
  27. package/lib/dist/Mock/Components/ComponentScreen.js +2 -1
  28. package/lib/dist/Mock/Components/LayoutComponent.d.ts +13 -9
  29. package/lib/dist/Mock/Components/LayoutComponent.js +9 -0
  30. package/lib/dist/Mock/Components/Modals.d.ts +13 -9
  31. package/lib/dist/Mock/Components/NavigationButton.d.ts +15 -11
  32. package/lib/dist/Mock/Components/Overlays.d.ts +13 -9
  33. package/lib/dist/Mock/Components/SideMenu.d.ts +62 -0
  34. package/lib/dist/Mock/Components/SideMenu.js +25 -0
  35. package/lib/dist/Mock/Components/Stack.d.ts +13 -9
  36. package/lib/dist/Mock/Components/TopBar.d.ts +9 -7
  37. package/lib/dist/Mock/Layouts/BottomTabsNode.d.ts +1 -1
  38. package/lib/dist/Mock/Layouts/BottomTabsNode.js +3 -2
  39. package/lib/dist/Mock/Layouts/LayoutNodeFactory.d.ts +2 -1
  40. package/lib/dist/Mock/Layouts/LayoutNodeFactory.js +10 -1
  41. package/lib/dist/Mock/Layouts/ParentNode.d.ts +1 -0
  42. package/lib/dist/Mock/Layouts/ParentNode.js +3 -0
  43. package/lib/dist/Mock/Layouts/SideMenu.d.ts +31 -0
  44. package/lib/dist/Mock/Layouts/SideMenu.js +80 -0
  45. package/lib/dist/Mock/Stores/LayoutStore.js +38 -9
  46. package/lib/dist/Mock/actions/layoutActions.d.ts +3 -0
  47. package/lib/dist/Mock/actions/layoutActions.js +11 -1
  48. package/lib/dist/Mock/connect.js +1 -2
  49. package/lib/dist/Mock/index.js +2 -2
  50. package/lib/dist/Mock/mocks/NativeCommandsSender.js +1 -0
  51. package/lib/dist/src/adapters/NativeEventsReceiver.js +1 -1
  52. package/lib/dist/src/adapters/TouchablePreview.d.ts +2 -2
  53. package/lib/dist/src/commands/LayoutType.js +1 -1
  54. package/lib/dist/src/components/Modal.d.ts +1 -1
  55. package/lib/dist/src/interfaces/CommandName.js +1 -1
  56. package/lib/dist/src/interfaces/Options.d.ts +9 -0
  57. package/lib/dist/src/interfaces/Options.js +2 -2
  58. package/lib/dist/src/types.d.ts +0 -1
  59. package/lib/ios/BottomTabsBasePresenter.m +2 -3
  60. package/lib/ios/RNNAppDelegate.mm +3 -2
  61. package/lib/ios/RNNConvert.h +0 -2
  62. package/lib/ios/RNNConvert.m +0 -4
  63. package/lib/ios/RNNSideMenu/MMDrawerController/MMDrawerController.h +22 -0
  64. package/lib/ios/RNNSideMenu/MMDrawerController/MMDrawerController.m +813 -159
  65. package/lib/ios/RNNSideMenuPresenter.m +27 -0
  66. package/lib/ios/RNNSideMenuSideOptions.h +6 -0
  67. package/lib/ios/RNNSideMenuSideOptions.m +13 -1
  68. package/lib/ios/RNNStackPresenter.m +2 -2
  69. package/lib/src/adapters/NativeEventsReceiver.ts +3 -3
  70. package/lib/src/adapters/TouchablePreview.tsx +3 -3
  71. package/lib/src/interfaces/Options.ts +9 -0
  72. package/package.json +31 -37
@@ -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;
@@ -167,6 +169,9 @@ static NSString *MMDrawerOpenSideKey = @"MMDrawerOpenSide";
167
169
  @property(nonatomic, copy) MMDrawerGestureCompletionBlock gestureCompletion;
168
170
  @property(nonatomic, assign, getter=isAnimatingDrawer) BOOL animatingDrawer;
169
171
  @property(nonatomic, strong) UIGestureRecognizer *pan;
172
+ @property (nonatomic, strong) UIView *centerContentOverlay;
173
+ @property (nonatomic, assign) MMDrawerSide startingDrawerSide;
174
+
170
175
 
171
176
  @end
172
177
 
@@ -227,6 +232,8 @@ static NSString *MMDrawerOpenSideKey = @"MMDrawerOpenSide";
227
232
  [self setShowsShadow:YES];
228
233
  [self setShouldStretchLeftDrawer:YES];
229
234
  [self setShouldStretchRightDrawer:YES];
235
+ [self side:MMDrawerSideRight openMode:MMDrawerOpenModePushContent];
236
+ [self side:MMDrawerSideLeft openMode:MMDrawerOpenModePushContent];
230
237
 
231
238
  [self setOpenDrawerGestureModeMask:MMOpenDrawerGestureModeNone];
232
239
  [self setCloseDrawerGestureModeMask:MMCloseDrawerGestureModeNone];
@@ -323,52 +330,126 @@ static NSString *MMDrawerOpenSideKey = @"MMDrawerOpenSide";
323
330
  }
324
331
  } else {
325
332
  [self setAnimatingDrawer:animated];
326
- CGRect newFrame = self.childControllerContainerView.bounds;
333
+ MMDrawerSide visibleSide = self.openSide;
327
334
 
328
- CGFloat distance = ABS(CGRectGetMinX(self.centerContainerView.frame));
329
- NSTimeInterval duration = MAX(distance / ABS(velocity), MMDrawerMinimumAnimationDuration);
335
+ if (visibleSide == MMDrawerSideNone) {
336
+ [self setAnimatingDrawer:NO];
337
+ if (completion) {
338
+ completion(NO);
339
+ }
340
+ return;
341
+ }
330
342
 
331
- BOOL leftDrawerVisible = CGRectGetMinX(self.centerContainerView.frame) > 0;
332
- BOOL rightDrawerVisible = CGRectGetMinX(self.centerContainerView.frame) < 0;
343
+ UIViewController *sideDrawerViewController = [self sideDrawerViewControllerForSide:visibleSide];
344
+ [sideDrawerViewController beginAppearanceTransition:NO animated:animated];
333
345
 
334
- MMDrawerSide visibleSide = MMDrawerSideNone;
335
- CGFloat percentVisble = 0.0;
346
+ MMDrawerOpenMode openMode = [self drawerOpenModeForSide:visibleSide];
336
347
 
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;
348
+ if (openMode == MMDrawerOpenModeAboveContent) {
349
+ [self closeDrawerInOverlayMode:sideDrawerViewController
350
+ visibleSide:visibleSide
351
+ animated:animated
352
+ velocity:velocity
353
+ animationOptions:options
354
+ completion:completion];
355
+ } else {
356
+ [self closeDrawerInPushMode:sideDrawerViewController
357
+ visibleSide:visibleSide
358
+ animated:animated
359
+ velocity:velocity
360
+ animationOptions:options
361
+ completion:completion];
346
362
  }
363
+ }
364
+ }
347
365
 
348
- UIViewController *sideDrawerViewController =
349
- [self sideDrawerViewControllerForSide:visibleSide];
366
+ - (void)closeDrawerInOverlayMode:(UIViewController *)sideDrawerViewController
367
+ visibleSide:(MMDrawerSide)visibleSide
368
+ animated:(BOOL)animated
369
+ velocity:(CGFloat)velocity
370
+ animationOptions:(UIViewAnimationOptions)options
371
+ completion:(void (^)(BOOL finished))completion {
372
+ CGFloat maximumDrawerWidth = [self maximumDrawerWidthForSide:visibleSide];
350
373
 
351
- [self updateDrawerVisualStateForDrawerSide:visibleSide percentVisible:percentVisble];
374
+ CGRect currentFrame = sideDrawerViewController.view.frame;
375
+ CGRect finalFrame = currentFrame;
352
376
 
353
- [sideDrawerViewController beginAppearanceTransition:NO animated:animated];
377
+ // Set final position based on side
378
+ if (visibleSide == MMDrawerSideLeft) {
379
+ finalFrame.origin.x = -maximumDrawerWidth; // Off-screen left
380
+ } else { // MMDrawerSideRight
381
+ finalFrame.origin.x = self.view.bounds.size.width; // Off-screen right
382
+ }
354
383
 
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
- }];
384
+ // Ensure overlay is in view hierarchy
385
+ if (self.centerContentOverlay && self.centerContentOverlay.superview == nil) {
386
+ [self.centerContainerView addSubview:self.centerContentOverlay];
387
+ [self.centerContainerView bringSubviewToFront:self.centerContentOverlay];
388
+ self.centerContentOverlay.alpha = 0.5;
389
+ }
390
+
391
+ CGFloat distance = ABS(currentFrame.origin.x - finalFrame.origin.x);
392
+ NSTimeInterval duration = [self animationDurationForDistance:distance velocity:velocity];
393
+
394
+ [UIView animateWithDuration:(animated ? duration : 0.0)
395
+ delay:0.0
396
+ options:options
397
+ animations:^{
398
+ [self setNeedsStatusBarAppearanceUpdateIfSupported];
399
+ [sideDrawerViewController.view setFrame:finalFrame];
400
+ self.centerContentOverlay.alpha = 0.0;
401
+ [self updateDrawerVisualStateForDrawerSide:visibleSide percentVisible:0.0];
402
+ }
403
+ completion:^(BOOL finished) {
404
+ [self completeDrawerClosingForSide:visibleSide
405
+ sideDrawerViewController:sideDrawerViewController
406
+ finished:finished
407
+ completion:completion];
408
+ [self.centerContentOverlay removeFromSuperview];
409
+ }];
410
+ }
411
+
412
+ - (void)closeDrawerInPushMode:(UIViewController *)sideDrawerViewController
413
+ visibleSide:(MMDrawerSide)visibleSide
414
+ animated:(BOOL)animated
415
+ velocity:(CGFloat)velocity
416
+ animationOptions:(UIViewAnimationOptions)options
417
+ completion:(void (^)(BOOL finished))completion {
418
+
419
+ CGRect newFrame = self.childControllerContainerView.bounds;
420
+
421
+ CGFloat distance = ABS(CGRectGetMinX(self.centerContainerView.frame));
422
+ NSTimeInterval duration = [self animationDurationForDistance:distance velocity:velocity];
423
+ sideDrawerViewController.view.hidden = NO;
424
+
425
+ [UIView animateWithDuration:(animated ? duration : 0.0)
426
+ delay:0.0
427
+ options:options
428
+ animations:^{
429
+ [self setNeedsStatusBarAppearanceUpdateIfSupported];
430
+ [self.centerContainerView setFrame:newFrame];
431
+ [self updateDrawerVisualStateForDrawerSide:visibleSide percentVisible:0.0];
432
+ }
433
+ completion:^(BOOL finished) {
434
+ [self completeDrawerClosingForSide:visibleSide
435
+ sideDrawerViewController:sideDrawerViewController
436
+ finished:finished
437
+ completion:completion];
438
+ }];
439
+ }
440
+
441
+ - (void)completeDrawerClosingForSide:(MMDrawerSide)visibleSide
442
+ sideDrawerViewController:(UIViewController *)sideDrawerViewController
443
+ finished:(BOOL)finished
444
+ completion:(void (^)(BOOL finished))completion {
445
+ [sideDrawerViewController endAppearanceTransition];
446
+
447
+ [self setOpenSide:MMDrawerSideNone];
448
+ [self resetDrawerVisualStateForDrawerSide:visibleSide];
449
+ [self setAnimatingDrawer:NO];
450
+
451
+ if (completion) {
452
+ completion(finished);
372
453
  }
373
454
  }
374
455
 
@@ -397,49 +478,206 @@ static NSString *MMDrawerOpenSideKey = @"MMDrawerOpenSide";
397
478
  }
398
479
  } else {
399
480
  [self setAnimatingDrawer:animated];
400
- UIViewController *sideDrawerViewController =
401
- [self sideDrawerViewControllerForSide:drawerSide];
481
+ UIViewController *sideDrawerViewController = [self sideDrawerViewControllerForSide:drawerSide];
482
+
402
483
  if (self.openSide != drawerSide) {
403
484
  [self prepareToPresentDrawer:drawerSide animated:animated];
404
485
  }
405
486
 
406
487
  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;
488
+ MMDrawerOpenMode openMode = [self drawerOpenModeForSide:drawerSide];
489
+
490
+ if (openMode == MMDrawerOpenModeAboveContent) {
491
+ [self openDrawerInOverlayMode:sideDrawerViewController
492
+ drawerSide:drawerSide
493
+ animated:animated
494
+ velocity:velocity
495
+ animationOptions:options
496
+ completion:completion];
412
497
  } else {
413
- newFrame = self.centerContainerView.frame;
414
- newFrame.origin.x = 0 - self.maximumRightDrawerWidth;
498
+ [self openDrawerInPushMode:sideDrawerViewController
499
+ drawerSide:drawerSide
500
+ animated:animated
501
+ velocity:velocity
502
+ animationOptions:options
503
+ completion:completion];
415
504
  }
505
+ }
506
+ }
507
+ }
416
508
 
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];
509
+ - (void)openDrawerInOverlayMode:(UIViewController *)sideDrawerViewController
510
+ drawerSide:(MMDrawerSide)drawerSide
511
+ animated:(BOOL)animated
512
+ velocity:(CGFloat)velocity
513
+ animationOptions:(UIViewAnimationOptions)options
514
+ completion:(void (^)(BOOL finished))completion {
435
515
 
436
- [self resetDrawerVisualStateForDrawerSide:drawerSide];
437
- [self setAnimatingDrawer:NO];
438
- if (completion) {
439
- completion(finished);
440
- }
441
- }];
516
+ CGFloat maximumDrawerWidth = [self maximumDrawerWidthForSide:drawerSide];
517
+
518
+ CGRect drawerFrame = sideDrawerViewController.view.frame;
519
+ CGRect initialFrame = sideDrawerViewController.view.frame;
520
+
521
+ drawerFrame.size.width = maximumDrawerWidth;
522
+ initialFrame.size.width = maximumDrawerWidth;
523
+
524
+ if (drawerSide == MMDrawerSideLeft) {
525
+ drawerFrame.origin.x = 0; // Final position
526
+
527
+ if (self.openSide != drawerSide) {
528
+ initialFrame.origin.x = -maximumDrawerWidth; // Start off-screen
529
+ [sideDrawerViewController.view setFrame:initialFrame];
530
+ }
531
+ } else { // MMDrawerSideRight
532
+ CGFloat screenWidth = self.view.bounds.size.width;
533
+ drawerFrame.origin.x = screenWidth - maximumDrawerWidth; // Final position
534
+
535
+ if (self.openSide != drawerSide) {
536
+ initialFrame.origin.x = screenWidth; // Start off-screen
537
+ [sideDrawerViewController.view setFrame:initialFrame];
538
+ }
539
+ }
540
+
541
+ [self setupCenterContentOverlay];
542
+ [self.centerContainerView addSubview:self.centerContentOverlay];
543
+ [self.centerContainerView bringSubviewToFront:self.centerContentOverlay];
544
+ self.centerContentOverlay.alpha = 0.0; // Start transparent
545
+
546
+ // Make sure drawer is visible and in front
547
+ sideDrawerViewController.view.hidden = NO;
548
+ [self.childControllerContainerView bringSubviewToFront:sideDrawerViewController.view];
549
+
550
+ CGFloat distance = ABS(initialFrame.origin.x - drawerFrame.origin.x);
551
+ NSTimeInterval duration = [self animationDurationForDistance:distance velocity:velocity];
552
+
553
+ [UIView animateWithDuration:(animated ? duration : 0.0)
554
+ delay:0.0
555
+ options:options
556
+ animations:^{
557
+ [self setNeedsStatusBarAppearanceUpdateIfSupported];
558
+ [sideDrawerViewController.view setFrame:drawerFrame]; // Move to final position
559
+ self.centerContentOverlay.alpha = 0.5;
560
+ [self updateDrawerVisualStateForDrawerSide:drawerSide percentVisible:1.0];
561
+ }
562
+ completion:^(BOOL finished) {
563
+ [self completeDrawerOpeningForSide:drawerSide
564
+ sideDrawerViewController:sideDrawerViewController
565
+ finished:finished
566
+ completion:completion];
567
+ }];
568
+ }
569
+
570
+ - (void)openDrawerInPushMode:(UIViewController *)sideDrawerViewController
571
+ drawerSide:(MMDrawerSide)drawerSide
572
+ animated:(BOOL)animated
573
+ velocity:(CGFloat)velocity
574
+ animationOptions:(UIViewAnimationOptions)options
575
+ completion:(void (^)(BOOL finished))completion {
576
+
577
+ CGRect oldFrame = self.centerContainerView.frame;
578
+ CGRect newFrame = self.centerContainerView.frame;
579
+
580
+ if (drawerSide == MMDrawerSideLeft) {
581
+ newFrame.origin.x = self.maximumLeftDrawerWidth;
582
+ } else {
583
+ newFrame.origin.x = 0 - self.maximumRightDrawerWidth;
584
+ }
585
+
586
+ CGFloat distance = ABS(CGRectGetMinX(oldFrame) - newFrame.origin.x);
587
+ NSTimeInterval duration = MAX(distance / ABS(velocity), MMDrawerMinimumAnimationDuration);
588
+ BOOL isGestureOpen = !CGRectIsNull(self.startingPanRect) && [self shouldStretchForSide:drawerSide];
589
+ sideDrawerViewController.view.hidden = NO;
590
+
591
+ [UIView animateWithDuration:(animated ? duration : 0.0)
592
+ delay:0.0
593
+ options:options
594
+ animations:^{
595
+ [self setNeedsStatusBarAppearanceUpdateIfSupported];
596
+ [self.centerContainerView setFrame:newFrame withLayoutAlpha:1.0];
597
+ [self updateDrawerVisualStateForDrawerSide:drawerSide percentVisible:1.0];
442
598
  }
599
+ completion:^(BOOL finished) {
600
+ [self completeDrawerOpeningForSide:drawerSide
601
+ sideDrawerViewController:sideDrawerViewController
602
+ finished:finished
603
+ completion:^(BOOL innerFinished) {
604
+
605
+ if (isGestureOpen) {
606
+ [self applyBounceForDrawerSide:drawerSide];
607
+ }
608
+
609
+ if (completion) {
610
+ completion(innerFinished);
611
+ }
612
+ }];
613
+ }];
614
+ }
615
+
616
+ - (void)applyBounceForDrawerSide:(MMDrawerSide)drawerSide {
617
+ UIViewController *sideDrawerViewController = [self sideDrawerViewControllerForSide:drawerSide];
618
+ if (!sideDrawerViewController) return;
619
+
620
+ CGRect centerFrame = self.centerContainerView.frame;
621
+ CGRect bounceFrame = centerFrame;
622
+ CGFloat bounceAmount = 15.0;
623
+
624
+ if (drawerSide == MMDrawerSideLeft) {
625
+ bounceFrame.origin.x += bounceAmount;
626
+ } else {
627
+ bounceFrame.origin.x -= bounceAmount;
628
+ }
629
+
630
+ CATransform3D originalTransform = sideDrawerViewController.view.layer.transform;
631
+ CGFloat scale = 1.0 + (bounceAmount / (drawerSide == MMDrawerSideLeft ?
632
+ self.maximumLeftDrawerWidth :
633
+ self.maximumRightDrawerWidth));
634
+
635
+ CATransform3D bounceTransform = CATransform3DMakeScale(scale, 1.0, 1.0);
636
+
637
+ if (drawerSide == MMDrawerSideLeft) {
638
+ bounceTransform = CATransform3DTranslate(bounceTransform,
639
+ self.maximumLeftDrawerWidth * (scale - 1.0) / 2.0,
640
+ 0, 0);
641
+ } else {
642
+ bounceTransform = CATransform3DTranslate(bounceTransform,
643
+ -self.maximumRightDrawerWidth * (scale - 1.0) / 2.0,
644
+ 0, 0);
645
+ }
646
+ [UIView animateWithDuration:0.15
647
+ delay:0.0
648
+ options:UIViewAnimationOptionCurveEaseOut
649
+ animations:^{
650
+ self.centerContainerView.frame = bounceFrame;
651
+ sideDrawerViewController.view.layer.transform = bounceTransform;
652
+ }
653
+ completion:^(BOOL bounceFinished) {
654
+ [UIView animateWithDuration:0.15
655
+ delay:0.0
656
+ options:UIViewAnimationOptionCurveEaseIn
657
+ animations:^{
658
+ self.centerContainerView.frame = centerFrame;
659
+ sideDrawerViewController.view.layer.transform = originalTransform;
660
+ }
661
+ completion:nil];
662
+ }];
663
+ }
664
+
665
+ - (void)completeDrawerOpeningForSide:(MMDrawerSide)drawerSide
666
+ sideDrawerViewController:(UIViewController *)sideDrawerViewController
667
+ finished:(BOOL)finished
668
+ completion:(void (^)(BOOL finished))completion {
669
+
670
+ // End the appearance transition if it already wasn't open.
671
+ if (drawerSide != self.openSide) {
672
+ [sideDrawerViewController endAppearanceTransition];
673
+ }
674
+
675
+ [self setOpenSide:drawerSide];
676
+ [self resetDrawerVisualStateForDrawerSide:drawerSide];
677
+ [self setAnimatingDrawer:NO];
678
+
679
+ if (completion) {
680
+ completion(finished);
443
681
  }
444
682
  }
445
683
 
@@ -801,7 +1039,7 @@ static NSString *MMDrawerOpenSideKey = @"MMDrawerOpenSide";
801
1039
  [super viewDidLoad];
802
1040
 
803
1041
  [self.view setBackgroundColor:[UIColor blackColor]];
804
-
1042
+ [self.view setAccessibilityIdentifier:@"SideMenuContainer"];
805
1043
  [self setupGestureRecognizers];
806
1044
  }
807
1045
 
@@ -1070,6 +1308,16 @@ static NSString *MMDrawerOpenSideKey = @"MMDrawerOpenSide";
1070
1308
  [self.dummyStatusBarView setBackgroundColor:_statusBarViewBackgroundColor];
1071
1309
  }
1072
1310
 
1311
+ - (void)setLeftDrawerOpenMode:(MMDrawerOpenMode)openMode {
1312
+ if (self.openSide == MMDrawerSideLeft) return;
1313
+ _leftDrawerOpenMode = openMode;
1314
+ }
1315
+
1316
+ - (void)setRightDrawerOpenMode:(MMDrawerOpenMode)openMode {
1317
+ if (self.openSide == MMDrawerSideRight) return;
1318
+ _rightDrawerOpenMode = openMode;
1319
+ }
1320
+
1073
1321
  - (void)setAnimatingDrawer:(BOOL)animatingDrawer {
1074
1322
  _animatingDrawer = animatingDrawer;
1075
1323
  [self.view setUserInteractionEnabled:!animatingDrawer];
@@ -1085,6 +1333,7 @@ static NSString *MMDrawerOpenSideKey = @"MMDrawerOpenSide";
1085
1333
  [self updatePanHandlersState];
1086
1334
  }
1087
1335
 
1336
+
1088
1337
  #pragma mark - Getters
1089
1338
  - (CGFloat)maximumLeftDrawerWidth {
1090
1339
  if (self.leftDrawerViewController) {
@@ -1155,107 +1404,452 @@ static NSString *MMDrawerOpenSideKey = @"MMDrawerOpenSide";
1155
1404
  return _statusBarViewBackgroundColor;
1156
1405
  }
1157
1406
 
1407
+ - (MMDrawerOpenMode)rightDrawerOpenMode {
1408
+ return _rightDrawerOpenMode;
1409
+ }
1410
+
1411
+ - (MMDrawerOpenMode)leftDrawerOpenMode {
1412
+ return _leftDrawerOpenMode;
1413
+ }
1414
+
1158
1415
  #pragma mark - Gesture Handlers
1159
1416
 
1160
1417
  - (void)tapGestureCallback:(UITapGestureRecognizer *)tapGesture {
1161
1418
  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
- }];
1419
+ // Get the tap location
1420
+ CGPoint tapLocation = [tapGesture locationInView:self.childControllerContainerView];
1421
+
1422
+ // Get the open drawer view controller
1423
+ UIViewController *sideDrawerViewController = [self sideDrawerViewControllerForSide:self.openSide];
1424
+
1425
+ // Check if we are in above content mode
1426
+ MMDrawerOpenMode openMode = [self drawerOpenModeForSide:self.openSide];
1427
+
1428
+ // Only close if not tapping on the drawer itself in above content mode
1429
+ BOOL shouldClose = YES;
1430
+
1431
+ if (openMode == MMDrawerOpenModeAboveContent && sideDrawerViewController) {
1432
+ // Check if tap is inside the drawer view
1433
+ CGPoint tapInDrawerView = [tapGesture locationInView:sideDrawerViewController.view];
1434
+ if (CGRectContainsPoint(sideDrawerViewController.view.bounds, tapInDrawerView)) {
1435
+ shouldClose = NO;
1436
+ }
1437
+ }
1438
+
1439
+ if (shouldClose) {
1440
+ [self closeDrawerAnimated:YES
1441
+ completion:^(BOOL finished) {
1442
+ if (self.gestureCompletion) {
1443
+ self.gestureCompletion(self, tapGesture);
1444
+ }
1445
+ }];
1446
+ }
1168
1447
  }
1169
1448
  }
1170
1449
 
1171
1450
  - (void)panGestureCallback:(UIPanGestureRecognizer *)panGesture {
1172
1451
  switch (panGesture.state) {
1173
- case UIGestureRecognizerStateBegan: {
1174
- if (self.gestureStart) {
1175
- self.gestureStart(self, panGesture);
1452
+ case UIGestureRecognizerStateBegan:
1453
+ [self handlePanGestureBegan:panGesture];
1454
+ break;
1455
+ case UIGestureRecognizerStateChanged:
1456
+ [self handlePanGestureChanged:panGesture];
1457
+ break;
1458
+ case UIGestureRecognizerStateEnded:
1459
+ case UIGestureRecognizerStateCancelled:
1460
+ [self handlePanGestureEnded:panGesture];
1461
+ break;
1462
+ default:
1463
+ break;
1464
+ }
1465
+ }
1466
+
1467
+ - (CGRect)calculateClosedDrawerPanStartFrameInOverlay:(MMDrawerSide)drawerSide
1468
+ drawerViewController:(UIViewController *)drawerViewController {
1469
+ CGRect drawerFrame = drawerViewController.view.frame;
1470
+ CGFloat maximumDrawerWidth = [self maximumDrawerWidthForSide:drawerSide];
1471
+
1472
+ drawerFrame.size.width = maximumDrawerWidth;
1473
+
1474
+ // Position off-screen based on side
1475
+ if (drawerSide == MMDrawerSideLeft) {
1476
+ drawerFrame.origin.x = -maximumDrawerWidth;
1477
+ } else {
1478
+ drawerFrame.origin.x = self.view.bounds.size.width;
1479
+ }
1480
+ return drawerFrame;
1481
+ }
1482
+
1483
+ - (void)handlePanGestureBegan:(UIPanGestureRecognizer *)panGesture {
1484
+ if (self.gestureStart) {
1485
+ self.gestureStart(self, panGesture);
1486
+ }
1487
+
1488
+ if (self.animatingDrawer) {
1489
+ [panGesture setEnabled:NO];
1490
+ return;
1491
+ }
1492
+
1493
+ self.startingDrawerSide = [self determineDrawerSideForPanGesture:panGesture];
1494
+ if (self.startingDrawerSide == MMDrawerSideNone) {
1495
+ return;
1496
+ }
1497
+
1498
+ [self setupPanGestureStartingFrameForDrawerSide:self.startingDrawerSide];
1499
+ }
1500
+
1501
+ - (MMDrawerSide)determineDrawerSideForPanGesture:(UIPanGestureRecognizer *)panGesture {
1502
+ MMDrawerSide drawerSide = self.openSide;
1503
+
1504
+ if (drawerSide == MMDrawerSideNone) {
1505
+ CGPoint velocity = [panGesture velocityInView:self.view];
1506
+ drawerSide = (velocity.x > 0) ? MMDrawerSideLeft : MMDrawerSideRight;
1507
+
1508
+ if ((drawerSide == MMDrawerSideLeft && !_leftSideEnabled) ||
1509
+ (drawerSide == MMDrawerSideRight && !_rightSideEnabled)) {
1510
+ drawerSide = (drawerSide == MMDrawerSideLeft) ? MMDrawerSideRight : MMDrawerSideLeft;
1511
+
1512
+ if ((drawerSide == MMDrawerSideLeft && !_leftSideEnabled) ||
1513
+ (drawerSide == MMDrawerSideRight && !_rightSideEnabled)) {
1514
+ return MMDrawerSideNone;
1515
+ }
1176
1516
  }
1177
- if (self.animatingDrawer) {
1178
- [panGesture setEnabled:NO];
1179
- break;
1517
+ }
1518
+
1519
+ return drawerSide;
1520
+ }
1521
+
1522
+ - (void)setupPanGestureStartingFrameForDrawerSide:(MMDrawerSide)drawerSide {
1523
+ MMDrawerOpenMode openMode = [self drawerOpenModeForSide:drawerSide];
1524
+
1525
+ if (openMode == MMDrawerOpenModeAboveContent) {
1526
+ [self setupAboveContentStartingFrameForDrawerSide:drawerSide];
1527
+ } else {
1528
+ self.startingPanRect = self.centerContainerView.frame;
1529
+ }
1530
+ }
1531
+
1532
+ - (void)setupAboveContentStartingFrameForDrawerSide:(MMDrawerSide)drawerSide {
1533
+ UIViewController *drawerViewController = [self sideDrawerViewControllerForSide:drawerSide];
1534
+
1535
+ self.startingPanRect = drawerViewController.view.frame;
1536
+
1537
+ // If drawer is closed, set up initial position
1538
+ if (self.openSide == MMDrawerSideNone) {
1539
+ self.startingPanRect = [self calculateClosedDrawerPanStartFrameInOverlay:drawerSide
1540
+ drawerViewController:(UIViewController *)drawerViewController];
1541
+
1542
+ [drawerViewController.view setFrame:self.startingPanRect];
1543
+
1544
+ // Ensure drawer is visible and in front
1545
+ drawerViewController.view.hidden = NO;
1546
+ [self.childControllerContainerView bringSubviewToFront:drawerViewController.view];
1547
+ }
1548
+ }
1549
+
1550
+ - (void)handlePanGestureChanged:(UIPanGestureRecognizer *)panGesture {
1551
+ self.view.userInteractionEnabled = NO;
1552
+
1553
+ // Get translation
1554
+ CGPoint translatedPoint = [panGesture translationInView:self.view];
1555
+
1556
+ MMDrawerSide drawerSide = [self determineCurrentDrawerSideWithTranslation:translatedPoint];
1557
+
1558
+ MMDrawerOpenMode openMode = [self drawerOpenModeForSide:drawerSide];
1559
+
1560
+ if (openMode == MMDrawerOpenModeAboveContent) {
1561
+ [self handleOverlayModeGestureChanged:panGesture withTranslation:translatedPoint forDrawerSide:drawerSide];
1562
+ } else {
1563
+ [self handlePushModeGestureChanged:panGesture withTranslation:translatedPoint];
1564
+ }
1565
+
1566
+ self.view.userInteractionEnabled = YES;
1567
+ }
1568
+
1569
+ - (MMDrawerSide)determineCurrentDrawerSideWithTranslation:(CGPoint)translatedPoint {
1570
+ MMDrawerSide drawerSide = self.startingDrawerSide;
1571
+ if (drawerSide == MMDrawerSideNone) {
1572
+ drawerSide = self.openSide;
1573
+ if (drawerSide == MMDrawerSideNone) {
1574
+ drawerSide = (translatedPoint.x > 0) ? MMDrawerSideLeft : MMDrawerSideRight;
1575
+ }
1576
+ }
1577
+ return drawerSide;
1578
+ }
1579
+
1580
+ - (void)handleOverlayModeGestureChanged:(UIPanGestureRecognizer *)panGesture
1581
+ withTranslation:(CGPoint)translatedPoint
1582
+ forDrawerSide:(MMDrawerSide)drawerSide {
1583
+ UIViewController *drawerViewController = [self sideDrawerViewControllerForSide:drawerSide];
1584
+ if (!drawerViewController) {
1585
+ return;
1586
+ }
1587
+
1588
+ CGFloat maximumDrawerWidth = [self maximumDrawerWidthForSide:drawerSide];
1589
+
1590
+ CGRect newFrame = [self calculateNewFrameForOverlayDrawer:drawerViewController
1591
+ withTranslation:translatedPoint
1592
+ forDrawerSide:drawerSide
1593
+ maximumDrawerWidth:maximumDrawerWidth];
1594
+
1595
+ CGFloat percentVisible = [self calculatePercentVisibleForOverlayDrawer:drawerSide
1596
+ withFrame:newFrame
1597
+ maximumDrawerWidth:maximumDrawerWidth];
1598
+
1599
+ // Handle overlay
1600
+ [self updateOverlayWithPercentVisible:percentVisible];
1601
+
1602
+ MMDrawerSide visibleSide = (percentVisible > 0.01) ? drawerSide : MMDrawerSideNone;
1603
+
1604
+ [self updateAppearanceTransitionsFromSide:self.openSide toSide:visibleSide];
1605
+
1606
+ // Apply new frame
1607
+ drawerViewController.view.frame = newFrame;
1608
+ [self.childControllerContainerView bringSubviewToFront:drawerViewController.view];
1609
+ }
1610
+
1611
+ - (CGRect)calculateNewFrameForOverlayDrawer:(UIViewController *)drawerViewController
1612
+ withTranslation:(CGPoint)translatedPoint
1613
+ forDrawerSide:(MMDrawerSide)drawerSide
1614
+ maximumDrawerWidth:(CGFloat)maximumDrawerWidth {
1615
+ CGRect newFrame = drawerViewController.view.frame;
1616
+ if (self.openSide == drawerSide) {
1617
+ // If drawer is already open, adjust from starting position
1618
+ newFrame.origin.x = self.startingPanRect.origin.x + translatedPoint.x;
1619
+ } else {
1620
+ // If drawer is closed, calculate from off-screen position
1621
+ if (drawerSide == MMDrawerSideLeft) {
1622
+ newFrame.origin.x = -maximumDrawerWidth + translatedPoint.x;
1180
1623
  } 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;
1624
+ CGFloat screenWidth = self.view.bounds.size.width;
1625
+ newFrame.origin.x = screenWidth + translatedPoint.x;
1207
1626
  }
1627
+ }
1208
1628
 
1209
- UIViewController *visibleSideDrawerViewController =
1210
- [self sideDrawerViewControllerForSide:visibleSide];
1629
+ // Apply constraints based on drawer side
1630
+ if (drawerSide == MMDrawerSideLeft) {
1631
+ newFrame.origin.x = MIN(0, newFrame.origin.x);
1632
+ newFrame.origin.x = MAX(-maximumDrawerWidth, newFrame.origin.x);
1633
+ } else {
1634
+ CGFloat screenWidth = self.view.bounds.size.width;
1635
+ newFrame.origin.x = MAX(screenWidth - maximumDrawerWidth, newFrame.origin.x);
1636
+ newFrame.origin.x = MIN(screenWidth, newFrame.origin.x);
1637
+ }
1638
+
1639
+ return newFrame;
1640
+ }
1641
+
1642
+ - (CGFloat)calculatePercentVisibleForOverlayDrawer:(MMDrawerSide)drawerSide
1643
+ withFrame:(CGRect)frame
1644
+ maximumDrawerWidth:(CGFloat)maximumDrawerWidth {
1645
+ CGFloat percentVisible;
1646
+ if (drawerSide == MMDrawerSideLeft) {
1647
+ percentVisible = (maximumDrawerWidth + frame.origin.x) / maximumDrawerWidth;
1648
+ } else {
1649
+ CGFloat rightEdge = self.view.bounds.size.width;
1650
+ percentVisible = (rightEdge - frame.origin.x) / maximumDrawerWidth;
1651
+ }
1652
+ return MAX(0, MIN(1.0, percentVisible));
1653
+ }
1211
1654
 
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];
1655
+ - (void)updateOverlayWithPercentVisible:(CGFloat)percentVisible {
1656
+ [self setupCenterContentOverlay];
1657
+ if (self.centerContentOverlay.superview != self.centerContainerView) {
1658
+ [self.centerContainerView addSubview:self.centerContentOverlay];
1659
+ [self.centerContainerView bringSubviewToFront:self.centerContentOverlay];
1660
+ }
1661
+ self.centerContentOverlay.alpha = percentVisible * 0.5;
1662
+ }
1218
1663
 
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];
1664
+ - (void)updateAppearanceTransitionsFromSide:(MMDrawerSide)fromSide toSide:(MMDrawerSide)toSide {
1665
+ if (fromSide != toSide) {
1666
+ if (fromSide != MMDrawerSideNone) {
1667
+ UIViewController *sideDrawerVC = [self sideDrawerViewControllerForSide:fromSide];
1668
+ [sideDrawerVC beginAppearanceTransition:NO animated:NO];
1669
+ [sideDrawerVC endAppearanceTransition];
1225
1670
  }
1226
1671
 
1227
- [self updateDrawerVisualStateForDrawerSide:visibleSide percentVisible:percentVisible];
1672
+ if (toSide != MMDrawerSideNone) {
1673
+ [self prepareToPresentDrawer:toSide animated:NO];
1674
+ UIViewController *visibleDrawerVC = [self sideDrawerViewControllerForSide:toSide];
1675
+ [visibleDrawerVC endAppearanceTransition];
1676
+ }
1228
1677
 
1229
- [self.centerContainerView
1230
- setCenter:CGPointMake(CGRectGetMidX(newFrame), CGRectGetMidY(newFrame))];
1678
+ [self setOpenSide:toSide];
1679
+ }
1680
+ }
1231
1681
 
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;
1682
+ - (void)handlePushModeGestureChanged:(UIPanGestureRecognizer *)panGesture withTranslation:(CGPoint)translatedPoint {
1683
+ CGRect newFrame = self.startingPanRect;
1236
1684
 
1237
- [self.centerContainerView addSubview:self.centerContainerView.overlayView];
1238
- [self.centerContainerView bringSubviewToFront:self.centerContainerView.overlayView];
1239
- self.centerContainerView.overlayView.alpha = percentVisible;
1685
+ // Calculate new center container position
1686
+ newFrame.origin.x = self.startingPanRect.origin.x + translatedPoint.x;
1240
1687
 
1241
- break;
1688
+ // Apply constraints
1689
+ CGFloat minX = -self.maximumRightDrawerWidth;
1690
+ CGFloat maxX = self.maximumLeftDrawerWidth;
1691
+ newFrame.origin.x = MAX(minX, MIN(maxX, newFrame.origin.x));
1692
+
1693
+ MMDrawerSide visibleSide;
1694
+ CGFloat percentVisible;
1695
+ [self calculateVisibleSideAndPercentage:newFrame.origin.x outSide:&visibleSide outPercentage:&percentVisible];
1696
+
1697
+ if ((!_leftSideEnabled && visibleSide == MMDrawerSideLeft) ||
1698
+ (!_rightSideEnabled && visibleSide == MMDrawerSideRight)) {
1699
+ return;
1242
1700
  }
1243
- case UIGestureRecognizerStateEnded:
1244
- case UIGestureRecognizerStateCancelled: {
1701
+
1702
+ [self handlePushModeAppearanceTransitionsForVisibleSide:visibleSide];
1703
+
1704
+ [self updateDrawerVisualStateForDrawerSide:visibleSide percentVisible:percentVisible];
1705
+ [self.centerContainerView setFrame:newFrame];
1706
+ }
1707
+
1708
+ - (void)calculateVisibleSideAndPercentage:(CGFloat)xOffset outSide:(MMDrawerSide *)visibleSide outPercentage:(CGFloat *)percentVisible {
1709
+ *visibleSide = MMDrawerSideNone;
1710
+ *percentVisible = 0.0;
1711
+
1712
+ if (xOffset > 0) {
1713
+ *visibleSide = MMDrawerSideLeft;
1714
+ *percentVisible = xOffset / self.maximumLeftDrawerWidth;
1715
+ } else if (xOffset < 0) {
1716
+ *visibleSide = MMDrawerSideRight;
1717
+ *percentVisible = ABS(xOffset) / self.maximumRightDrawerWidth;
1718
+ }
1719
+ }
1720
+
1721
+ - (void)handlePushModeAppearanceTransitionsForVisibleSide:(MMDrawerSide)visibleSide {
1722
+ UIViewController *visibleSideDrawerViewController = [self sideDrawerViewControllerForSide:visibleSide];
1723
+
1724
+ if (self.openSide != visibleSide) {
1725
+ // Handle existing drawer disappearing
1726
+ UIViewController *sideDrawerVC = [self sideDrawerViewControllerForSide:self.openSide];
1727
+ [sideDrawerVC beginAppearanceTransition:NO animated:NO];
1728
+ [sideDrawerVC endAppearanceTransition];
1729
+
1730
+ // Handle new drawer appearing
1731
+ [self prepareToPresentDrawer:visibleSide animated:NO];
1732
+ [visibleSideDrawerViewController endAppearanceTransition];
1733
+
1734
+ [self setOpenSide:visibleSide];
1735
+ } else if (visibleSide == MMDrawerSideNone) {
1736
+ [self setOpenSide:MMDrawerSideNone];
1737
+ }
1738
+ }
1739
+
1740
+ - (void)handlePanGestureEnded:(UIPanGestureRecognizer *)panGesture {
1741
+ MMDrawerSide drawerSide = [self determineDrawerSideForGestureEnd:panGesture];
1742
+
1743
+ MMDrawerOpenMode openMode = [self drawerOpenModeForSide:drawerSide];
1744
+
1745
+ if (openMode == MMDrawerOpenModeAboveContent) {
1746
+ [self completeOverlayModeGestureForDrawerSide:drawerSide withPanGesture:panGesture];
1747
+ } else {
1748
+ [self completePushModeGestureWithPanGesture:panGesture];
1749
+ }
1750
+
1751
+ self.startingPanRect = CGRectNull;
1752
+ self.view.userInteractionEnabled = YES;
1753
+ }
1754
+
1755
+ - (MMDrawerSide)determineDrawerSideForGestureEnd:(UIPanGestureRecognizer *)panGesture {
1756
+ MMDrawerSide drawerSide = self.startingDrawerSide;
1757
+
1758
+ if (drawerSide == MMDrawerSideNone) {
1759
+ drawerSide = self.openSide;
1760
+ if (drawerSide == MMDrawerSideNone) {
1761
+ CGPoint velocity = [panGesture velocityInView:self.view];
1762
+ drawerSide = (velocity.x > 0) ? MMDrawerSideLeft : MMDrawerSideRight;
1763
+ }
1764
+ }
1765
+
1766
+ return drawerSide;
1767
+ }
1768
+
1769
+ - (void)completeOverlayModeGestureForDrawerSide:(MMDrawerSide)drawerSide withPanGesture:(UIPanGestureRecognizer *)panGesture {
1770
+ // Get drawer view controller
1771
+ UIViewController *drawerVC = [self sideDrawerViewControllerForSide:drawerSide];
1772
+ if (!drawerVC) {
1773
+ self.startingDrawerSide = MMDrawerSideNone;
1245
1774
  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
1775
  self.view.userInteractionEnabled = YES;
1254
- break;
1255
- }
1256
- default:
1257
- break;
1776
+ return;
1258
1777
  }
1778
+
1779
+ BOOL shouldOpen = [self shouldOpenDrawerForGestureEnd:panGesture withDrawerSide:drawerSide andCurrentPosition:drawerVC.view.frame.origin.x];
1780
+ [self animateOverlayDrawerToFinalState:shouldOpen forDrawerSide:drawerSide withDrawerVC:drawerVC andPanGesture:panGesture];
1781
+ }
1782
+
1783
+ - (BOOL)shouldOpenDrawerForGestureEnd:(UIPanGestureRecognizer *)panGesture withDrawerSide:(MMDrawerSide)drawerSide andCurrentPosition:(CGFloat)currentX {
1784
+ CGPoint velocity = [panGesture velocityInView:self.view];
1785
+ CGFloat maximumDrawerWidth = [self maximumDrawerWidthForSide:drawerSide];
1786
+ CGFloat screenWidth = self.view.bounds.size.width;
1787
+ BOOL shouldOpen = NO;
1788
+
1789
+ if (drawerSide == MMDrawerSideLeft) {
1790
+ if (velocity.x > 500) shouldOpen = YES; // Fast right swipe
1791
+ else if (velocity.x < -500) shouldOpen = NO; // Fast left swipe
1792
+ else shouldOpen = (currentX > -maximumDrawerWidth/2.0); // Based on position
1793
+ } else {
1794
+ if (velocity.x < -500) shouldOpen = YES; // Fast left swipe
1795
+ else if (velocity.x > 500) shouldOpen = NO; // Fast right swipe
1796
+ else shouldOpen = (currentX < screenWidth - maximumDrawerWidth/2.0); // Based on position
1797
+ }
1798
+
1799
+ return shouldOpen;
1800
+ }
1801
+
1802
+ - (void)animateOverlayDrawerToFinalState:(BOOL)shouldOpen forDrawerSide:(MMDrawerSide)drawerSide withDrawerVC:(UIViewController *)drawerVC andPanGesture:(UIPanGestureRecognizer *)panGesture {
1803
+ CGFloat maximumDrawerWidth = [self maximumDrawerWidthForSide:drawerSide];
1804
+ CGFloat screenWidth = self.view.bounds.size.width;
1805
+
1806
+ [UIView animateWithDuration:0.25
1807
+ animations:^{
1808
+ CGRect frame = drawerVC.view.frame;
1809
+
1810
+ if (shouldOpen) {
1811
+ if (drawerSide == MMDrawerSideLeft) {
1812
+ frame.origin.x = 0;
1813
+ } else {
1814
+ frame.origin.x = screenWidth - maximumDrawerWidth;
1815
+ }
1816
+
1817
+ self.centerContentOverlay.alpha = 0.5;
1818
+ } else {
1819
+ if (drawerSide == MMDrawerSideLeft) {
1820
+ frame.origin.x = -maximumDrawerWidth;
1821
+ } else {
1822
+ frame.origin.x = screenWidth;
1823
+ }
1824
+
1825
+ self.centerContentOverlay.alpha = 0.0;
1826
+ }
1827
+
1828
+ drawerVC.view.frame = frame;
1829
+ } completion:^(BOOL finished) {
1830
+ if (shouldOpen) {
1831
+ [self setOpenSide:drawerSide];
1832
+ } else {
1833
+ [self setOpenSide:MMDrawerSideNone];
1834
+ [self.centerContentOverlay removeFromSuperview];
1835
+ }
1836
+
1837
+ self.startingDrawerSide = MMDrawerSideNone;
1838
+
1839
+ if (self.gestureCompletion) {
1840
+ self.gestureCompletion(self, panGesture);
1841
+ }
1842
+ }];
1843
+ }
1844
+
1845
+ - (void)completePushModeGestureWithPanGesture:(UIPanGestureRecognizer *)panGesture {
1846
+ CGPoint velocity = [panGesture velocityInView:self.childControllerContainerView];
1847
+ [self finishAnimationForPanGestureWithXVelocity:velocity.x
1848
+ completion:^(BOOL finished) {
1849
+ if (self.gestureCompletion) {
1850
+ self.gestureCompletion(self, panGesture);
1851
+ }
1852
+ }];
1259
1853
  }
1260
1854
 
1261
1855
  - (void)updatePanHandlersState {
@@ -1270,6 +1864,29 @@ static NSString *MMDrawerOpenSideKey = @"MMDrawerOpenSide";
1270
1864
  }
1271
1865
  }
1272
1866
 
1867
+ - (void)setupCenterContentOverlay {
1868
+ if (!self.centerContentOverlay) {
1869
+ // Create overlay view if it doesn't exist
1870
+ self.centerContentOverlay = [[UIView alloc] initWithFrame:self.centerContainerView.bounds];
1871
+ self.centerContentOverlay.backgroundColor = [UIColor blackColor];
1872
+ self.centerContentOverlay.alpha = 0.0; // Start fully transparent
1873
+ self.centerContentOverlay.userInteractionEnabled = YES;
1874
+
1875
+ // Add tap gesture to close drawer when tapping overlay
1876
+ UITapGestureRecognizer *tapGesture =
1877
+ [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(overlayTapped:)];
1878
+ [self.centerContentOverlay addGestureRecognizer:tapGesture];
1879
+ }
1880
+
1881
+ // Update frame to match current center container bounds
1882
+ self.centerContentOverlay.frame = self.centerContainerView.bounds;
1883
+ }
1884
+
1885
+ - (void)overlayTapped:(UITapGestureRecognizer *)tapGesture {
1886
+ // Close drawer when overlay is tapped
1887
+ [self closeDrawerAnimated:YES completion:nil];
1888
+ }
1889
+
1273
1890
  #pragma mark - iOS 7 Status Bar Helpers
1274
1891
  - (UIViewController *)childViewControllerForStatusBarStyle {
1275
1892
  return [self childViewControllerForSide:self.openSide];
@@ -1367,23 +1984,46 @@ static NSString *MMDrawerOpenSideKey = @"MMDrawerOpenSide";
1367
1984
  }
1368
1985
  }
1369
1986
 
1987
+ - (void)side:(MMDrawerSide)drawerSide openMode:(MMDrawerOpenMode)openMode {
1988
+ if (drawerSide == MMDrawerSideLeft) {
1989
+ self.leftDrawerOpenMode = openMode;
1990
+ } else if (drawerSide == MMDrawerSideRight) {
1991
+ self.rightDrawerOpenMode = openMode;
1992
+ }
1993
+ }
1994
+
1370
1995
  - (void)applyOvershootScaleTransformForDrawerSide:(MMDrawerSide)drawerSide
1371
1996
  percentVisible:(CGFloat)percentVisible {
1372
-
1373
1997
  if (percentVisible >= 1.f) {
1374
1998
  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);
1999
+
2000
+ MMDrawerOpenMode openMode = [self drawerOpenModeForSide:drawerSide];
2001
+ UIViewController *sideDrawerViewController = [self sideDrawerViewControllerForSide:drawerSide];
2002
+
2003
+ if (openMode == MMDrawerOpenModePushContent) {
2004
+ CGFloat stretchFactor = MIN(percentVisible, 1.1);
2005
+
2006
+ if (drawerSide == MMDrawerSideLeft) {
2007
+ transform = CATransform3DMakeScale(stretchFactor, 1.f, 1.f);
2008
+ transform = CATransform3DTranslate(transform, self.maximumLeftDrawerWidth * (stretchFactor - 1.f) / 2, 0.f, 0.f);
2009
+ } else if (drawerSide == MMDrawerSideRight) {
2010
+ transform = CATransform3DMakeScale(stretchFactor, 1.f, 1.f);
2011
+ transform = CATransform3DTranslate(transform, -self.maximumRightDrawerWidth * (stretchFactor - 1.f) / 2, 0.f, 0.f);
2012
+ }
2013
+
2014
+ sideDrawerViewController.view.layer.transform = transform;
2015
+ }
2016
+ else if (openMode == MMDrawerOpenModeAboveContent) {
2017
+ if (drawerSide == MMDrawerSideLeft) {
2018
+ transform = CATransform3DMakeScale(percentVisible, 1.f, 1.f);
2019
+ transform = CATransform3DTranslate(transform, self.maximumLeftDrawerWidth * (percentVisible - 1.f) / 2, 0.f, 0.f);
2020
+ } else if (drawerSide == MMDrawerSideRight) {
2021
+ transform = CATransform3DMakeScale(percentVisible, 1.f, 1.f);
2022
+ transform = CATransform3DTranslate(transform, -self.maximumRightDrawerWidth * (percentVisible - 1.f) / 2, 0.f, 0.f);
2023
+ }
2024
+
2025
+ sideDrawerViewController.view.layer.transform = transform;
1385
2026
  }
1386
- sideDrawerViewController.view.layer.transform = transform;
1387
2027
  }
1388
2028
  }
1389
2029
 
@@ -1679,4 +2319,18 @@ static inline CGFloat originXForDrawerOriginAndTargetOriginOffset(CGFloat origin
1679
2319
  return (CGRectContainsPoint(rightBezelRect, point) &&
1680
2320
  [self isPointContainedWithinCenterViewContentRect:point]);
1681
2321
  }
2322
+
2323
+ - (MMDrawerOpenMode)drawerOpenModeForSide:(MMDrawerSide)drawerSide {
2324
+ return (drawerSide == MMDrawerSideLeft) ? self.leftDrawerOpenMode : self.rightDrawerOpenMode;
2325
+ }
2326
+
2327
+ - (CGFloat)maximumDrawerWidthForSide:(MMDrawerSide)drawerSide {
2328
+ return (drawerSide == MMDrawerSideLeft) ? self.maximumLeftDrawerWidth : self.maximumRightDrawerWidth;
2329
+ }
2330
+
2331
+ // Helper method to calculate animation duration based on distance and velocity
2332
+ - (NSTimeInterval)animationDurationForDistance:(CGFloat)distance velocity:(CGFloat)velocity {
2333
+ return MAX(distance / ABS(velocity), MMDrawerMinimumAnimationDuration);
2334
+ }
2335
+
1682
2336
  @end