iOS-Slide-Menu icon indicating copy to clipboard operation
iOS-Slide-Menu copied to clipboard

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

Open VittoriDavide opened this issue 4 years ago • 12 comments

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

VittoriDavide avatar Sep 25 '19 11:09 VittoriDavide

Im having the same problem. Anybody found any solutions ?

assembleMHN avatar Sep 27 '19 06:09 assembleMHN

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.

assembleMHN avatar Sep 27 '19 11:09 assembleMHN

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.

DiAvisoo avatar Oct 13 '19 16:10 DiAvisoo

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

kilirushi avatar Nov 18 '19 02:11 kilirushi

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 avatar Feb 08 '20 22:02 codaman

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;
                        }
                    }
                }
            }
        }
    }

codaman avatar Feb 08 '20 22:02 codaman

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 !!! :)

outsourcestudio avatar Apr 07 '20 16:04 outsourcestudio

@codaman self.lastValidDeviceInterfaceOrientation. How did you declare it

kilirushi avatar Jun 23 '20 09:06 kilirushi

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

lmt avatar Jun 27 '20 15:06 lmt

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

patriksharma avatar Mar 10 '22 05:03 patriksharma

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

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

change window to superview

tbodt avatar Apr 21 '24 05:04 tbodt

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!

DiAvisoo avatar Apr 26 '24 07:04 DiAvisoo