IPhone SDK отклоняет модальные ViewControllers на ipad, нажав за его пределами


Я хочу закрыть контроллер модального представления FormSheetPresentation, когда пользователь нажимает за пределами модального представления...Я видел кучу приложений, делающих это (например, ebay на ipad), но я не могу понять, как, поскольку нижние представления отключены от прикосновений, когда модальные представления отображаются так (возможно, они представляют его как поповер?)...у кого какие предложения?

14 53

14 ответов:

Я на год опоздал, но это довольно просто сделать.

пусть ваш контроллер модального вида прикрепит распознаватель жестов к окну вида:

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

вот и все. Хиг будь проклят, это полезное и часто интуитивное поведение.

другие приложения не используют модальные представления, если они позволяют отклонить представление, нажав за его пределами. UIModalPresentationFormSheets не может быть уволен таким образом. (также, действительно, может любой UIModal в SDK3.2). Только UIPopoverController можно отключить, нажав за пределами области. Это очень возможно (хотя и против iPad HIG от Apple) для разработчика приложения, чтобы затенить фоновый экран, а затем отображается UIPopoverController Так что это выглядит как UIModalPresentationFormSheets (или другой UIModal вид).

[...] Стиль UIModalPresentationCurrentContext позволяет контроллеру представления использовать стиль представления своего родителя. В каждом модальном представлении затемненные области показывают базовое содержимое, но не разрешают краны в этом содержимом. Поэтому, в отличие от popover, ваши модальные представления должны по-прежнему иметь элементы управления, которые позволяют пользователю отклонить модальное представление.

см. iPadProgrammingGuide на сайте разработчика для получения дополнительной информации (стр. 46 -- " настройка стиля презентации для модального Просмотров")

для 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;
}

приведенный выше код отлично работает, но я бы изменил оператор 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];
    }

Это гарантирует, что вы все еще можете взаимодействовать с панелью навигации, в противном случае нажатие на нее отклоняет модальное представление.

ответ обновлен для iOS 8

видимо, в iOS 8,UIDimmingView имеет распознаватель жестов крана, который мешает начальной реализации, поэтому мы игнорируем его и не требуем его сбоя.


Это возраст скорости, поэтому большинство из них, вероятно, просто копируют код выше.. Но, к сожалению, я страдаю от ОКР, когда дело доходит до кода.

вот модульное решение, которое использует ответ Данило Кампоса с категории. Это тоже разрешает важная ошибка, которая может возникнуть, если вы отклоняете свой модальный с помощью других средств,как уже упоминалось.

Примечание: операторы 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];
}

скопируйте вставить этот код в 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];
        }
    }
}

Very important: Если у вас есть другой способ закрыть ваш модальное окно всплывающее, не забудьте удалить распознаватель жестов крана!

Я забыл об этом, и получил сумасшедшие сбои позже, так как распознаватель крана все еще запускал события.

согласно iOS HIG от Apple, 1. модальное представление не имеет этой способности быть отклоненным без какого-либо ввода на себя; 2. используйте модальное представление в ситуации, когда требуется пользовательский ввод.

вместо этого используйте 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

это работает для меня для ios7 8 и навигационной панели.

Если вам не нужна панель навигации, просто удалите location2 и второе условие в операторе if после труб.

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

Edit: вы также можете быть делегатом распознавателя жестов для этого и других вышеуказанных решений для работы. Сделайте это так:

@interface CommentTableViewController () <UIGestureRecognizerDelegate>

установить себя в качестве делегата для распознавания:

self.recognizer.delegate = self;

и реализовать этот метод делегата:

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

Это вполне выполнимо.

посмотреть здесь

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

Это NavigationController (модальный), который автоматически отключается для ipad (когда вы нажимаете снаружи)

используйте свой viewcontroller внутри него.

надеюсь, что это помогает.

Я знаю, что уже поздно, но подумайте об использовании CleanModal (протестировано с iOS 7 и 8).

https://github.com/orafaelreis/CleanModal

можно использовать MZFormSheetController такой:

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

в Swift 2 / Xcode версии 7.2 (7C68) для меня работал следующий код.

внимание: этот код должен быть помещен в ViewController.swift файл представленного FormSheet или листа страницы, здесь: "PageSheetViewController.Свифт"

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