IPhone SDK отклоняет модальные ViewControllers на ipad, нажав за его пределами
Я хочу закрыть контроллер модального представления FormSheetPresentation, когда пользователь нажимает за пределами модального представления...Я видел кучу приложений, делающих это (например, ebay на ipad), но я не могу понять, как, поскольку нижние представления отключены от прикосновений, когда модальные представления отображаются так (возможно, они представляют его как поповер?)...у кого какие предложения?
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 внутри него.
надеюсь, что это помогает.
можно использовать 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) } } } }