Iphone SDK通过点击它来解除ipad上的Modal ViewControllers

时间:2010-04-12 15:58:59

标签: iphone ipad modal-view

当用户点击模态视图时,我想解雇一个FormSheetPresentation模式视图控制器...我已经看到一堆应用程序这样做(例如在ipad上的ebay)但我无法弄清楚如何从下面的视图是当模态视图显示为这样时,它们是否会触摸(他们是否可能将其作为弹出窗口显示?)......任何人都有任何建议吗?

14 个答案:

答案 0 :(得分:127)

我迟到了一年,但这非常简单。

让您的模态视图控制器将手势识别器附加到视图窗口:

UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapBehind:)];

[recognizer setNumberOfTapsRequired:1];
recognizer.cancelsTouchesInView = NO; //So the user can still interact with controls in the modal view
[self.view.window addGestureRecognizer:recognizer];
[recognizer release];

处理代码:

- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
    if (sender.state == UIGestureRecognizerStateEnded)
     {
       CGPoint location = [sender locationInView:nil]; //Passing nil gives us coordinates in the window

 //Then we convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.

        if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil]) 
        {
           // Remove the recognizer first so it's view.window is valid.
          [self.view.window removeGestureRecognizer:sender];
          [self dismissModalViewControllerAnimated:YES];
        }
     }
}

就是这样。 HIG被诅咒,这是一种有用且常常是直观的行为。

答案 1 :(得分:11)

如果其他应用允许通过点击其外部来解除视图,则其他应用不会使用模态视图。 UIModalPresentationFormSheets不能以这种方式被驳回。 (也不是,SDK3.2中的任何UIModal都可以)。只需点击该区域外即可解除UIPopoverController。应用程序开发人员很可能(虽然针对Apple的iPad HIG)遮蔽了背景屏幕,然后显示UIPopoverController,使其看起来像UIModalPresentationFormSheets(或其他UIModal View)。

  

[...] UIModalPresentationCurrentContext样式允许视图控制器采用其父级的表示样式。在每个模态视图中,灰色区域显示基础内容,但不允许在该内容中使用水龙头。因此,与弹出框不同,您的模态视图仍必须具有允许用户关闭模态视图的控件。

有关详细信息,请参阅开发人员网站上的iPad编程指南(第46页 - “为模态视图配置演示文稿样式”)

答案 2 :(得分:11)

对于iOS 8,您必须同时实现UIGestureRecognizer,并在横向方向上交换点击位置的(x,y)坐标。不确定这是否是由于iOS 8错误造成的。

- (void) viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    // add gesture recognizer to window

    UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapBehind:)];
    [recognizer setNumberOfTapsRequired:1];
    recognizer.cancelsTouchesInView = NO; //So the user can still interact with controls in the modal view
    [self.view.window addGestureRecognizer:recognizer];
    recognizer.delegate = self;
}

- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
    if (sender.state == UIGestureRecognizerStateEnded) {

        // passing nil gives us coordinates in the window
        CGPoint location = [sender locationInView:nil];

        // swap (x,y) on iOS 8 in landscape
        if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")) {
            if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) {
                location = CGPointMake(location.y, location.x);
            }
        }

        // convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.
        if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil]) {

            // remove the recognizer first so it's view.window is valid
            [self.view.window removeGestureRecognizer:sender];
            [self dismissViewControllerAnimated:YES completion:nil];
        }
    }
}


#pragma mark - UIGestureRecognizer Delegate

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
    return YES;
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
    return YES;
}

答案 3 :(得分:10)

上面的代码效果很好,但我会将if语句更改为

    if (!([self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil] || [self.navigationController.view pointInside:[self.navigationController.view convertPoint:location fromView:self.navigationController.view.window] withEvent:nil]))

    {
        // Remove the recognizer first so it's view.window is valid.
        [self.view.window removeGestureRecognizer:sender];
        [self dismissModalViewControllerAnimated:YES];
    }

这可以确保您仍然可以与导航栏进行交互,否则点击它会解除模态视图。

答案 4 :(得分:9)

针对iOS 8更新的答案

显然,在iOS 8中,UIDimmingView有一个点击手势识别器,会干扰初始实现,所以我们忽略它并且不要求它失败。


这是速度的时代,所以大多数人可能只是复制上面的代码..但是,不幸的是,在代码方面我遇到了强迫症。

Here is a modular solution that uses Danilo Campos's answer with categories。如果您通过其他方式as mentioned解雇您的模态,它还解决可能发生的重要错误。

注意: if语句存在,因为我使用iPhone和iPad的视图控制器,只有iPad需要注册/取消注册。

更新:要点已更新,因为它无法正常使用令人敬畏的FCOverlay代码,并且不允许在呈现的视图中识别手势。那些问题是固定的。 使用该类别非常简单:

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    if (self.presentingViewController) {
        [self registerForDismissOnTapOutside];
    }
}

- (void)viewWillDisappear:(BOOL)animated
{
    if (self.presentingViewController) {
        [self unregisterForDismissOnTapOutside];
    }

    [super viewWillDisappear:animated];
}

答案 5 :(得分:8)

将此代码复制粘贴到ModalViewController中:

- (void) viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    //Code for dissmissing this viewController by clicking outside it
    UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapBehind:)];
    [recognizer setNumberOfTapsRequired:1];
    recognizer.cancelsTouchesInView = NO; //So the user can still interact with controls in the modal view
    [self.view.window addGestureRecognizer:recognizer];

}

- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
    if (sender.state == UIGestureRecognizerStateEnded)
    {
        CGPoint location = [sender locationInView:nil]; //Passing nil gives us coordinates in the window

        //Then we convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.

        if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil])
        {
            // Remove the recognizer first so it's view.window is valid.
            [self.view.window removeGestureRecognizer:sender];
            [self dismissModalViewControllerAnimated:YES];
        }
    }
}

答案 6 :(得分:3)

Very important:如果您有其他方法可以关闭模态弹出窗口,请不要忘记删除点击手势识别器!

我忘记了这件事,后来发生了疯狂的撞车,因为点击识别器仍在发射事件。

答案 7 :(得分:2)

根据Apple的iOS HIG,1。模态视图没有能够在没有任何输入的情况下被解雇; 2.在需要用户输入的情况下使用模态视图。

答案 8 :(得分:1)

改为使用UIPresentationController:

- (void)presentationTransitionWillBegin
{
    [super presentationTransitionWillBegin];
    UITapGestureRecognizer *dismissGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(dismissGestureTapped:)];
    [self.containerView addGestureRecognizer:dismissGesture];

    [[[self presentedViewController] transitionCoordinator] animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
    } completion:nil];
}
- (void) dismissGestureTapped:(UITapGestureRecognizer *)sender{
    if (sender.state==UIGestureRecognizerStateEnded&&!CGRectContainsPoint([self frameOfPresentedViewInContainerView], [sender locationInView:sender.view])) {
        [self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
    }
}

从LookInside示例修改

答案 9 :(得分:1)

这适用于ios7和导航栏。

如果您不需要导航栏,只需删除管道后if语句中的location2和第二个条件。

@MiQUEL这对你也有用

- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
    if (sender.state == UIGestureRecognizerStateEnded)
    {
        CGPoint location1 =  [sender locationInView:self.view];
        CGPoint location2 = [sender locationInView:self.navigationController.view];

        if (!([self.view pointInside:location1 withEvent:nil] || [self.navigationController.view pointInside:location2 withEvent:nil])) {
            [self.view.window removeGestureRecognizer:self.recognizer];
            [self dismissViewControllerAnimated:YES completion:nil];
        }
    }
}

编辑:您可能还需要成为手势识别器代表才能使用此解决方案和其他上述解决方案。这样做:

@interface CommentTableViewController () <UIGestureRecognizerDelegate>

将自己设置为识别器的代理:

self.recognizer.delegate = self;

并实现此委托方法:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
    return YES;
}

答案 10 :(得分:0)

非常可行。

看看这里

https://stackoverflow.com/a/26016458/4074557

这是一个自动关闭ipad的NavigationController(模态)(当你点击外面时)

在里面使用你的viewcontroller。

希望它有所帮助。

答案 11 :(得分:0)

我知道它已经晚了但是考虑使用CleanModal(在iOS 7和8下测试过)。

https://github.com/orafaelreis/CleanModal

答案 12 :(得分:0)

您可以像这样使用MZFormSheetController

MZFormSheetController *formSheet = [[MZFormSheetController alloc] initWithSize:customSize viewController:presentedViewController];
formSheet.shouldDismissOnBackgroundViewTap = YES;
[presentingViewController mz_presentFormSheetController:formSheet animated:YES completionHandler:nil];

答案 13 :(得分:0)

在Swift 2 / Xcode版本7.2(7C68)中,以下代码适用于我。

注意:此代码应放在所呈现的FormSheet或Page Sheet的ViewController.swift文件中,此处:&#34; PageSheetViewController.swift&#34;

class PageSheetViewController: UIViewController, UIGestureRecognizerDelegate {

    override func viewDidAppear(animated: Bool) {
        let recognizer = UITapGestureRecognizer(target: self, action:Selector("handleTapBehind:"))
        recognizer.delegate = self
        recognizer.numberOfTapsRequired = 1
        recognizer.cancelsTouchesInView = false
        self.view.window?.addGestureRecognizer(recognizer)
    }

    func gestureRecognizer(sender: UIGestureRecognizer,
        shouldRecognizeSimultaneouslyWithGestureRecognizer:UIGestureRecognizer) -> Bool {
            return true
    }

    func handleTapBehind(sender:UIGestureRecognizer) {
        if(sender.state == UIGestureRecognizerState.Ended){
            var location:CGPoint = sender.locationInView(nil)

            // detect iOS Version 8.0 or greater
            let Device = UIDevice.currentDevice()
            let iosVersion = Double(Device.systemVersion) ?? 0
            let iOS8 = iosVersion >= 8

            if (iOS8) {
                // in landscape view you will have to swap the location coordinates
                if(UIInterfaceOrientationIsLandscape(UIApplication.sharedApplication().statusBarOrientation)){
                    location = CGPointMake(location.y, location.x);
                }
            }

            if(!self.view.pointInside(self.view.convertPoint(location, fromView: self.view.window), withEvent: nil)){
                self.view.window?.removeGestureRecognizer(sender)
                self.dismissViewControllerAnimated(true, completion: nil)
            }
        }
    }
}