aryaxt / iOS-Slide-Menu

iOS Slide Menu with ability to add both left and right menu, and built in gesture recognizer. Similar to Path and Facebook
Other
1.53k stars 359 forks source link

The slide menu on the left, in iPadOS is not working properly #266

Open VittoriDavide opened 4 years ago

VittoriDavide commented 4 years ago

When you open the left slide menu you can see all the View Controller but you cannot click on the button, like if there were an invisible layer preventing to click. Does anybody know why this could be happening on the new OS of the iPad? On iOS 12 and on iOS 13 in the iPhone, it is working perfectly

Seems like it is adding an UITransitionView, upper of everything, the problems continues even if setUserInteractionEnabled is set to no in the transition view

Screenshot 2019-09-25 at 19 57 16
assembleMHN commented 4 years ago

Im having the same problem. Anybody found any solutions ?

assembleMHN commented 4 years ago

I did work out a temporarily solution, i would not recommend it for long term but its a hotfix untill a better solution is presented.

Register two of the notifications do it where ever it makes sense to you like so:

    if #available(iOS 13.0, *) {

            NotificationCenter.default.addObserver(
                self,
                selector: #selector(self.menuDidOpeniOS13Fix),
                name: NSNotification.Name(rawValue: "SlideNavigationControllerDidOpen"),
                object: nil)

            NotificationCenter.default.addObserver(
                self,
                selector: #selector(self.menuDidCloseiOS13Fix),
                name: NSNotification.Name(rawValue: "SlideNavigationControllerDidClose"),
                object: nil)
        }
    }

Implement two new functions to handle the open and close of the sidemenu witch is called from the above notifications. Please make sure that you have set your menu to not open, meaning that its witdth should be 0 pixels after the animation so that it do not open at all, these two new functions will take care of that:

@objc private func menuDidOpeniOS13Fix(){
    let subviews = self.view.window?.subviews
    if let view = subviews?[1]{
        if let frame = self.navigationController?.view.frame{
            UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseOut, animations: {
                view.frame = CGRect(x: 400, y: 0, width: frame.size.width, height: frame.size.height)
            }) { (finished) in

            }
        }
    }
}

@objc private func menuDidCloseiOS13Fix(){
    let subviews = self.view.window?.subviews
    if let view = subviews?[1]{
        if let frame = self.navigationController?.view.frame{
            UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseOut, animations: {
                view.frame = CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height)
            }) { (finished) in

            }
        }
    }
}

Setting the size of the side menu, for ios13 i make sure that the sidemenu wont open, as the new hotfix will animate the transitionview insted. For iOS <12 do as you always did.

    if #available(iOS 13.0, *) {
        SlideNavigationController.sharedInstance()?.landscapeSlideOffset = pointHeight
        SlideNavigationController.sharedInstance()?.portraitSlideOffset = pointWidth
    } else {
        SlideNavigationController.sharedInstance()?.landscapeSlideOffset = pointHeight-LeftMenuViewController.widthOfLeftMenu
        SlideNavigationController.sharedInstance()?.portraitSlideOffset = pointWidth-LeftMenuViewController.widthOfLeftMenu
    }

there is alot of small issues with this mainly associated with rotating the device, i dont have time to solved these small issues for now, but its a starting point and a temp fix untill the problem is solved in the pod, if ever......

Please post any improvements that you may add.

DiAvisoo commented 4 years ago

I solved this by changing the order of the views when opening and closing the menu, then I added some tap-through code to the main view of the menu.

When the menu is open, it will actually cover the entire screen, but it's only the menu part that has any subviews. The rest will be transparent. When tapping on the transparent part, it will send the tap through to the underlying layer that closes the menu.

In your storyboard, change the width of everything you can on your menu to be exactly 190 since that's the default width of the menu. Don't use stretching width or anything or it will not look good on different devices.

Screenshot 2019-10-13 at 17 46 58

Also change the background-color of the main menu view to be "clear color".

Screenshot 2019-10-13 at 18 03 38

Create a new class that inherits from UIView and override pointInside and use this for your main view in the menu (set it in the storyboard). It basically just says that the main view will let taps go through to the underlying layer while taps on the subviews (the menu) will be caught. I called my class "ClickThroughView":

Screenshot 2019-10-13 at 18 02 33
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    for (UIView *view in self.subviews) {
        if (!view.hidden && view.userInteractionEnabled && [view pointInside:[self convertPoint:point toView:view] withEvent:event])
            return YES;
    }
    return NO;
}

Code from https://stackoverflow.com/a/4010809/3727143

Now in SlideNavigationController.m, make the following change at around line 600:

[removingMenuViewController.view removeFromSuperview];
menuViewController.view.tag = 200; // The actual menu
self.view.window.subviews.lastObject.tag = 100; // The problematic view
[self.view.window insertSubview:menuViewController.view atIndex:0];

Also change the openMenu and closeMenu methods, adding view rearrange code. Just a single line in both methods.

(void)openMenu: [self.view.window sendSubviewToBack:[self.view.window viewWithTag:100]];

(void)closeMenuWithDuration: [self.view.window sendSubviewToBack:[self.view.window viewWithTag:200]];

- (void)openMenu:(Menu)menu withDuration:(float)duration andCompletion:(void (^)())completion
{
    [self enableTapGestureToCloseMenu:YES];

    [self prepareMenuForReveal:menu forcePrepare:NO];

    [UIView animateWithDuration:duration
                          delay:0
                        options:UIViewAnimationOptionCurveEaseOut
                     animations:^{
                         CGRect rect = self.view.frame;
                         CGFloat width = self.horizontalSize;
                         rect.origin.x = (menu == MenuLeft) ? (self.slideOffset) : ((self.slideOffset )* -1);
                         [self moveHorizontallyToLocation:rect.origin.x];
                     }
                     completion:^(BOOL finished) {

                        [self.view.window sendSubviewToBack:[self.view.window viewWithTag:100]];

                         if (completion)
                             completion();
                     }];
}
- (void)closeMenuWithDuration:(float)duration andCompletion:(void (^)())completion
{
    [self enableTapGestureToCloseMenu:NO];

    [self.view.window sendSubviewToBack:[self.view.window viewWithTag:200]];

    [UIView animateWithDuration:duration
                          delay:0
                        options:UIViewAnimationOptionCurveEaseOut
                     animations:^{
                         CGRect rect = self.view.frame;
                         rect.origin.x = 0;
                         [self moveHorizontallyToLocation:rect.origin.x];
                     }
                     completion:^(BOOL finished) {

                         if (completion)
                             completion();
                     }];
}

The shadow will not work properly after this, it will only be visible during transition so I just removed it:

#define MENU_SHADOW_OPACITY 0 This way the slide menu works on all devices, both iOS 12 and iOS 13.

kilirushi commented 4 years ago

I solved this by changing the order of the views when opening and closing the menu, then I added some tap-through code to the main view of the menu.

When the menu is open, it will actually cover the entire screen, but it's only the menu part that has any subviews. The rest will be transparent. When tapping on the transparent part, it will send the tap through to the underlying layer that closes the menu.

In your storyboard, change the width of everything you can on your menu to be exactly 190 since that's the default width of the menu. Don't use stretching width or anything or it will not look good on different devices.

Screenshot 2019-10-13 at 17 46 58

Also change the background-color of the main menu view to be "clear color".

Screenshot 2019-10-13 at 18 03 38

Create a new class that inherits from UIView and override pointInside and use this for your main view in the menu (set it in the storyboard). It basically just says that the main view will let taps go through to the underlying layer while taps on the subviews (the menu) will be caught. I called my class "ClickThroughView":

Screenshot 2019-10-13 at 18 02 33
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    for (UIView *view in self.subviews) {
        if (!view.hidden && view.userInteractionEnabled && [view pointInside:[self convertPoint:point toView:view] withEvent:event])
            return YES;
    }
    return NO;
}

Code from https://stackoverflow.com/a/4010809/3727143

Now in SlideNavigationController.m, make the following change at around line 600:

[removingMenuViewController.view removeFromSuperview];
menuViewController.view.tag = 200; // The actual menu
self.view.window.subviews.lastObject.tag = 100; // The problematic view
[self.view.window insertSubview:menuViewController.view atIndex:0];

Also change the openMenu and closeMenu methods, adding view rearrange code. Just a single line in both methods.

(void)openMenu: [self.view.window sendSubviewToBack:[self.view.window viewWithTag:100]];

(void)closeMenuWithDuration: [self.view.window sendSubviewToBack:[self.view.window viewWithTag:200]];

- (void)openMenu:(Menu)menu withDuration:(float)duration andCompletion:(void (^)())completion
{
  [self enableTapGestureToCloseMenu:YES];

  [self prepareMenuForReveal:menu forcePrepare:NO];

  [UIView animateWithDuration:duration
                        delay:0
                      options:UIViewAnimationOptionCurveEaseOut
                   animations:^{
                       CGRect rect = self.view.frame;
                       CGFloat width = self.horizontalSize;
                       rect.origin.x = (menu == MenuLeft) ? (self.slideOffset) : ((self.slideOffset )* -1);
                       [self moveHorizontallyToLocation:rect.origin.x];
                   }
                   completion:^(BOOL finished) {

                        [self.view.window sendSubviewToBack:[self.view.window viewWithTag:100]];

                       if (completion)
                           completion();
                   }];
}
- (void)closeMenuWithDuration:(float)duration andCompletion:(void (^)())completion
{
  [self enableTapGestureToCloseMenu:NO];

    [self.view.window sendSubviewToBack:[self.view.window viewWithTag:200]];

  [UIView animateWithDuration:duration
                        delay:0
                      options:UIViewAnimationOptionCurveEaseOut
                   animations:^{
                       CGRect rect = self.view.frame;
                       rect.origin.x = 0;
                       [self moveHorizontallyToLocation:rect.origin.x];
                   }
                   completion:^(BOOL finished) {

                       if (completion)
                           completion();
                   }];
}

The shadow will not work properly after this, it will only be visible during transition so I just removed it:

#define MENU_SHADOW_OPACITY 0 This way the slide menu works on all devices, both iOS 12 and iOS 13.

After following your code. My application no longer has the above error . How to swipe left to return to the main screen of the application

codaman commented 4 years ago

I have a different solution

I declared a problemView

@implementation SlideNavigationController{
    .
    .
    .
    UIView *problemView;
}

- (void)prepareMenuForReveal:(Menu)menu
{
    // Only prepare menu if it has changed (ex: from MenuLeft to MenuRight or vice versa)
    //if (self.lastRevealedMenu && menu == self.lastRevealedMenu)
    //    return;

    UIViewController *menuViewController = (menu == MenuLeft) ? self.leftMenu : self.rightMenu;
    UIViewController *removingMenuViewController = (menu == MenuLeft) ? self.rightMenu : self.leftMenu;

    self.lastRevealedMenu = menu;

    [removingMenuViewController.view removeFromSuperview];
    //ipad ios13   
    for (UIView *subview in [[[UIApplication sharedApplication] delegate] window].subviews) {
        if ([subview isKindOfClass:NSClassFromString(@"UITransitionView")]) {
            for (UIView *subview2 in subview.subviews) {
                if ([subview2 isKindOfClass:NSClassFromString(@"UIDropShadowView")]) {
                    for (UIView *subview3 in subview2.subviews) {
                        if ([subview3 isKindOfClass:NSClassFromString(@"UIView")]) {
                            problemView = subview3;
                        }
                    }
                }
            }
        }
    }
- (CGFloat)horizontalLocation
{
    CGRect rect = self.view.frame;

    if (problemView) {
        rect = problemView.frame;
    }

- (void)moveHorizontallyToLocation:(CGFloat)location
{
    CGRect rect = self.view.frame;
    Menu menu = (self.horizontalLocation >= 0 && location >= 0) ? MenuLeft : MenuRight;

    if ((location > 0 && self.horizontalLocation <= 0) || (location < 0 && self.horizontalLocation >= 0)) {
        [self postNotificationWithName:SlideNavigationControllerDidReveal forMenu:(location > 0) ? MenuLeft : MenuRight];
    }

    if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0"))
    {
        rect.origin.x = location;
        rect.origin.y = 0;
    }
    else
    {
        if (UIDeviceOrientationIsLandscape(self.lastValidDeviceInterfaceOrientation))
        {
            rect.origin.x = 0;
            rect.origin.y = (self.lastValidDeviceInterfaceOrientation == UIDeviceOrientationLandscapeRight) ? location*-1 : location;
        }
        else
        {
            rect.origin.x = (self.lastValidDeviceInterfaceOrientation == UIDeviceOrientationPortrait) ? location : location*-1;
            rect.origin.y = 0;
        }
    }

    //[[self.view.window viewWithTag:100] setFrame:rect];

    if (problemView) {
        [problemView setFrame:rect];
        self.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
    }
    else{
        self.view.frame = rect;
    }
    [self updateMenuAnimation:menu];
}

I tested on iPad and iPhone both ios13 and ios12. It workings fine. And When menu opened right side not disappeared

codaman commented 4 years ago

Ohh sorry identifying problemView must be like this

for (UIView *subview in [[[UIApplication sharedApplication] delegate] window].subviews) {
        if ([subview isKindOfClass:NSClassFromString(@"UITransitionView")]) {
            for (UIView *subview2 in subview.subviews) {
                if ([subview2 isKindOfClass:NSClassFromString(@"UIDropShadowView")]) {
                    for (UIView *subview3 in subview2.subviews) {
                        if ([subview3 isKindOfClass:NSClassFromString(@"UIView")] && ![subview3 isKindOfClass:NSClassFromString(@"UILayoutContainerView")]) {
                            problemView = subview3;
                        }
                    }
                }
            }
        }
    }
outsourcestudio commented 4 years ago

Ohh sorry identifying problemView must be like this

for (UIView *subview in [[[UIApplication sharedApplication] delegate] window].subviews) {
        if ([subview isKindOfClass:NSClassFromString(@"UITransitionView")]) {
            for (UIView *subview2 in subview.subviews) {
                if ([subview2 isKindOfClass:NSClassFromString(@"UIDropShadowView")]) {
                    for (UIView *subview3 in subview2.subviews) {
                        if ([subview3 isKindOfClass:NSClassFromString(@"UIView")] && ![subview3 isKindOfClass:NSClassFromString(@"UILayoutContainerView")]) {
                            problemView = subview3;
                        }
                    }
                }
            }
        }
    }

Thanks @codaman !!! :)

kilirushi commented 4 years ago

@codaman self.lastValidDeviceInterfaceOrientation. How did you declare it

lmt commented 4 years ago

@kilirushi you could do something like: @property (nonatomic, assign) UIDeviceOrientation lastValidDeviceInterfaceOrientation; just bellow the menuNeedsLayout property.

patriksharma commented 2 years ago

I have a different solution

I declared a problemView

@implementation SlideNavigationController{
    .
    .
    .
    UIView *problemView;
}

- (void)prepareMenuForReveal:(Menu)menu
{
    // Only prepare menu if it has changed (ex: from MenuLeft to MenuRight or vice versa)
    //if (self.lastRevealedMenu && menu == self.lastRevealedMenu)
    //    return;

    UIViewController *menuViewController = (menu == MenuLeft) ? self.leftMenu : self.rightMenu;
    UIViewController *removingMenuViewController = (menu == MenuLeft) ? self.rightMenu : self.leftMenu;

    self.lastRevealedMenu = menu;

    [removingMenuViewController.view removeFromSuperview];
    //ipad ios13   
    for (UIView *subview in [[[UIApplication sharedApplication] delegate] window].subviews) {
        if ([subview isKindOfClass:NSClassFromString(@"UITransitionView")]) {
            for (UIView *subview2 in subview.subviews) {
                if ([subview2 isKindOfClass:NSClassFromString(@"UIDropShadowView")]) {
                    for (UIView *subview3 in subview2.subviews) {
                        if ([subview3 isKindOfClass:NSClassFromString(@"UIView")]) {
                            problemView = subview3;
                        }
                    }
                }
            }
        }
    }
- (CGFloat)horizontalLocation
{
    CGRect rect = self.view.frame;

    if (problemView) {
        rect = problemView.frame;
    }

- (void)moveHorizontallyToLocation:(CGFloat)location
{
    CGRect rect = self.view.frame;
    Menu menu = (self.horizontalLocation >= 0 && location >= 0) ? MenuLeft : MenuRight;

    if ((location > 0 && self.horizontalLocation <= 0) || (location < 0 && self.horizontalLocation >= 0)) {
        [self postNotificationWithName:SlideNavigationControllerDidReveal forMenu:(location > 0) ? MenuLeft : MenuRight];
    }

    if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0"))
    {
        rect.origin.x = location;
        rect.origin.y = 0;
    }
    else
    {
        if (UIDeviceOrientationIsLandscape(self.lastValidDeviceInterfaceOrientation))
        {
            rect.origin.x = 0;
            rect.origin.y = (self.lastValidDeviceInterfaceOrientation == UIDeviceOrientationLandscapeRight) ? location*-1 : location;
        }
        else
        {
            rect.origin.x = (self.lastValidDeviceInterfaceOrientation == UIDeviceOrientationPortrait) ? location : location*-1;
            rect.origin.y = 0;
        }
    }

    //[[self.view.window viewWithTag:100] setFrame:rect];

    if (problemView) {
        [problemView setFrame:rect];
        self.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
    }
    else{
        self.view.frame = rect;
    }
    [self updateMenuAnimation:menu];
}

I tested on iPad and iPhone both ios13 and ios12. It workings fine. And When menu opened right side not disappeared

This solution worked perfectly for me. Tested in IOS 15, Ipad

tbodt commented 4 months ago

You can also change one line of code in SlideNavigationController.m

    [self.view.window insertSubview:menuViewController.view atIndex:0];

change window to superview

DiAvisoo commented 4 months ago

You can also change one line of code in SlideNavigationController.m

  [self.view.window insertSubview:menuViewController.view atIndex:0];

change window to superview

Mind. Blown. 🤯

Thanks!