Iphone SDK отклоняет модальные ViewControllers на ipad, щелкая за пределами - PullRequest
53 голосов
/ 12 апреля 2010

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

Ответы [ 14 ]

127 голосов
/ 31 мая 2011

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

Попросите модальный контроллер вида прикрепить распознаватель жестов к окну вида:

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

Вот и все. К черту, это полезное и часто интуитивное поведение.

11 голосов
/ 12 сентября 2014

Для 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;
}
11 голосов
/ 15 апреля 2010

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

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

См. IPadProgrammingGuide на сайте разработчика для получения дополнительной информации (Страница 46 - «Настройка стиля представления для модальных представлений»)

10 голосов
/ 19 октября 2012

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

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

9 голосов
/ 08 мая 2014

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

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


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

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

ПРИМЕЧАНИЕ: Существуют операторы 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];
}
8 голосов
/ 17 сентября 2012

Скопируйте и вставьте этот код в ваш 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];
        }
    }
}
3 голосов
/ 06 января 2014

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

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

2 голосов
/ 19 сентября 2011

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

1 голос
/ 10 октября 2014

Это работает для меня для 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];
        }
    }
}

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

@interface CommentTableViewController () <UIGestureRecognizerDelegate>

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

self.recognizer.delegate = self;

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

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
    return YES;
}
1 голос
/ 12 сентября 2014

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

...