質問

My situation is a little more complicated than the others listed.

I have a UITableView that takes up most of the screen. Each row pops up a subview that contains more profile information. When the screen is clicked again this subview disappears. This works perfectly.

In the Navigation Bar I have a button that will display a small menu.

- (IBAction)menuButtonClicked:(UIBarButtonItem *)sender {
    //If menuView exists and Menu button is clicked, remove it from view
    if (self.menuView) {
        self.tableView.userInteractionEnabled = true;
        [self.menuView removeFromSuperview];
        self.menuView = Nil;
    }
    //Menu View doesn't exist so create it
    else {
        // Create the Menu View and add it to the parent view
        self.menuView = [[[NSBundle mainBundle] loadNibNamed:@"MenuView" owner:self 
           options:nil] objectAtIndex:0];
        self.menuView.layer.cornerRadius = 20.0f;
        self.menuView.layer.borderWidth = 3.0f;
        self.menuView.layer.borderColor = [UIColor whiteColor].CGColor;
        self.menuView.frame = CGRectMake(0, 64, self.menuView.frame.size.width,
                                     self.menuView.frame.size.height);

        UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] 
            initWithTarget:self action:@selector(singleTapGestureCaptured:)];
        [self.menuView addGestureRecognizer:singleTap];

        //Disable Selection of Profiles while Menu is showing
        self.tableView.userInteractionEnabled = false;

        //Add MenuView to View
        [self.view addSubview: self.menuView];
    }
}

//Removed Sub Views from View when tapped
-(void)singleTapGestureCaptured:(UITapGestureRecognizer *)gesture{
    if(self.profileView){
        [self.profileView removeFromSuperview];
        self.profileView = Nil;
    }
    if(self.menuView) {
        self.tableView.userInteractionEnabled = true;
        [self.menuView removeFromSuperview];
        self.menuView = Nil;
    }
}

Now I want to dismiss this menus if the menu button is clicked again (working in above code) but also when the user touches out of the menu and on the tableView or navbar. If the menu is displayed, I don't want the tableView to display it's profile subview (working in above code) but just remove the menuView. I can't get the menuView to go away if I touch the tableView.

Can anyone point me in the right direction?

役に立ちましたか?

解決

Make a new transparent overlay view sized to cover the entire screen. Add your menuView as a subview of the overlay, then add the overlay as a subview of your main window. Put a tap gesture recognizer on the overlay that will dismiss it when tapped.

You may need to set cancelsTouchesInView to NO on your gesture recognizer if buttons on your menu view are not working.

Roughly this (please excuse typos, I haven't compiled this):

- (void)showMenu
{
  self.overlay = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
  overlay.backgroundColor = [UIColor clearColor];

  self.menuView = /* code to load menuView */;

  [overlay addSubview:self.menuView];

  UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self 
                                                                        action:@selector(onSingleTap:)];
  tap.cancelsTouchesInView = NO;
  [overlay addGestureRecognizer:tap];

  [self.tableView.window addSubview:overlay];
}

- (void)handleSingleTap:(UITapGestureRecognizer *)sender
{
  [self.overlay removeFromSuperview];
}

You might also want to add a swipe gesture recognizer to also dismiss the overlay, as someone may attempt to scroll the table expecting the menu to be dismissed.

他のヒント

While trying to make my own custom topdown-slide menu using my own custom NIB file, I found that this can be achieved by many techniques. I would like to suggest and share a different solution which is very similar but is created with a custom button on the background. I've been looking around but could not find answers mentioning this. This is very similar to the tap recogniser except for one thing - tap recogniser spreads all over the layout (including subviews), while using a layer of custom button allows you to interact with the top view and dismiss/ remove it from superview when clicking on lower layer (when lower layer is the background button). This is how I did it:

  1. You create the layout
  2. You add a UIButton with type UIButtonTypeCustom to the layout
  3. You frame this layout over the view you wish to be responsive to that tap/click
  4. You add your menu view on top of that layout and animate your menu to appear

    - (void)showMenuViewWithBackgroundButtonOverlay
        {
            self.backgroundButton = ({
                UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
                button.frame = self.view.frame;
                [button addTarget:self action:@selector(toggleAppMenu) forControlEvents:UIControlEventTouchUpInside];
                button;
            });
    
            if (!self.menu) {
                self.menu = [self createMenu]; // <-- get your own custom menu UIView
            }
            if (!self.overlay) {
                self.overlay = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    
                self.overlay.backgroundColor = [UIColor clearColor];
    
                [self.overlay addSubview:self.backgroundButton];
    
                [self.overlay addSubview:self.menu];
    
                [self.view addSubview:self.overlay];
            }
    
            [self toggleAppMenu];
        }
    

And the toggleAppMenu:

- (void)toggleAppMenu
{
    CGRect nowFrame = [self.menu frame];
    CGRect toBeFrame = nowFrame;
    CGFloat navHeight = self.navigationController.navigationBar.frame.size.height;
    CGFloat statusBarHeight = [UIApplication sharedApplication].statusBarFrame.size.height;

    if (self.showingMenu) {
        toBeFrame.origin.y = toBeFrame.origin.y-nowFrame.size.height-navHeight-statusBarHeight;
        [UIView animateWithDuration:0.5 animations:^{
            [self.menu setFrame: toBeFrame];
        }completion:^(BOOL finished) {
            self.showingMenu = !self.showingMenu;
            [self.view endEditing:YES];

            [self.overlay removeFromSuperview];
            self.overlay = nil;
            NSLog(@"menu is NOT showing");
        }];
    }
    else{
        toBeFrame.origin.y = navHeight+statusBarHeight;

        [UIView animateWithDuration:0.5 animations:^{
            [self.menu setFrame: toBeFrame];
        }completion:^(BOOL finished) {
            self.showingMenu = !self.showingMenu;
            NSLog(@"menu is showing");
        }];

    }
}

I hope this will be helpful for someone.

Works on Swift 5

I create custom view and I want it hide by tap outside subview. Maybe it can help for someone or anybody can suggest a better way :)

// create tap for view
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(animateOut))
        self.addGestureRecognizer(tapGesture)
        
 // create tap for subview
        let tapGesture2 = UITapGestureRecognizer(target: self, action: nil)
        container.addGestureRecognizer(tapGesture2)
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top