IPhone SDK rejetant Modal ViewControllers sur iPad en cliquant en dehors de celui-ci
-
26-09-2019 - |
Question
Je veux rejeter un contrôleur de vue modal FormSheetPresentation lorsque les robinets utilisateur en dehors de la vue modale ... J'ai vu un tas d'applications faisant cela (ebay sur iPad par exemple), mais je ne peux pas comprendre comment, depuis les vues de dessous sont personnes à mobilité réduite de touches lorsque les vues modales sont affichés comme celui-ci (sont peut-être? ils le présenter comme un popover) ... quelqu'un at-il des suggestions?
La solution
Je suis un an de retard, mais cela est assez simple à faire.
Demandez à votre contrôleur modal vue attacher un geste de reconnaissance à la fenêtre de la vue:
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];
Le code de gestion:
- (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];
}
}
}
C'est à ce sujet. HIG être damnés, c'est un comportement utile et souvent intuitive.
Autres conseils
Les autres applications n'utilisent pas Modal vues si elles permettent la vue d'être rejetée en cliquant en dehors de celui-ci. UIModalPresentationFormSheets
ne peut pas être rejeté cette façon. (Ni, peut en effet tout UIModal dans SDK3.2). Seul le UIPopoverController
peut être rejeté en cliquant en dehors de la zone. Il est très possible (bien que contre l'iPad d'Apple HIG) pour le développeur d'applications pour avoir l'ombre sur l'écran d'arrière-plan, puis affiché la UIPopoverController
pour qu'il ressemble à un UIModalPresentationFormSheets
(ou autre UIModal View).
[...] le style UIModalPresentationCurrentContext permet à un contrôleur de vue d'adopter le style de présentation de son parent. Dans chaque vue modale, les zones grisées indiquent le contenu sous-jacent, mais ne permettent pas de robinets dans ce contenu. Par conséquent, contrairement à un popover, votre point de vue modales doivent encore avoir des contrôles qui permettent à l'utilisateur de rejeter le point de vue modal.
Voir la iPadProgrammingGuide sur le site de développeur pour plus d'informations (Page 46 - "Configuration du style de présentation pour les vues modales")
Pour iOS 8, vous devez à la fois mettre en œuvre le UIGestureRecognizer
et remplacez (x, y) les coordonnées de l'emplacement taraudés lorsque l'orientation paysage. Je ne sais pas si cela est dû à un bug 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;
}
Le code ci-dessus fonctionne très bien, mais je changerais l'instruction 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];
}
Ceci assure que vous pouvez toujours interagir avec la barre de navigation, sinon taraudage il rejette le point de vue modal.
Réponse mise à jour pour iOS 8
Apparemment, dans iOS 8, le UIDimmingView
a un geste de reconnaissance du robinet, ce qui interfère avec la mise en œuvre initiale, de sorte que nous ignorons et ne nécessitent pas d'échouer.
Ceci est l'âge de la vitesse, donc la plupart sont probablement copiant le code ci-dessus .. Mais, je souffre de TOC en matière de code, malheureusement.
Voici une solution modulaire qui utilise la réponse de Danilo Campos avec des catégories . Il a également permet de résoudre un bug important qui peut se produire si vous Rejetant votre modal par d'autres moyens, comme mentionné .
NOTE:. si les déclarations sont là parce que j'utilise le contrôleur d'affichage pour iPhone et iPad, et que l'iPad doit inscrire / désinscrire
Very important:
Si vous avez une autre façon de fermer votre fenêtre modale , ne pas oublier d'enlever le robinet de reconnaissance de geste!
J'ai oublié cela, et se bloque fou plus tard, puisque la reconnaissance de robinet a été mise à feu encore des événements.
Selon iOS HIG d'Apple, 1. la vue modale n'a pas cette capacité à être licenciés sans aucune intervention sur elle-même; 2. utiliser la vue modale dans la situation d'une entrée de l'utilisateur est nécessaire.
Utilisez UIPresentationController au lieu:
- (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];
}
}
Modifié de l'exemple LookInside
Cela fonctionne pour moi pour iOS 7 une barre 8 et la navigation.
Si vous n'avez pas besoin de la barre de navigation juste supprimer location2 et deuxième condition dans l'instruction if après les tuyaux.
@MiQUEL cela devrait fonctionner pour vous aussi
- (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: Vous pouvez également besoin d'être un délégué de reconnaissance de geste pour cela et d'autres solutions ci-dessus au travail. Faites-le comme ceci:
@interface CommentTableViewController () <UIGestureRecognizerDelegate>
vous défini comme délégué pour la reconnaissance:
self.recognizer.delegate = self;
et mettre en œuvre cette méthode déléguée:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
Il est assez faisable.
Jetez un coup d'oeil ici
https://stackoverflow.com/a/26016458/4074557
Il est un NavigationController (modal) que l'auto pour ipad rejeter (lorsque vous appuyez sur l'extérieur)
Utilisez votre viewcontroller à l'intérieur.
it helps.
Je sais qu'il est tard, mais envisager d'utiliser CleanModal (testé avec iOS 7 et 8).
Vous pouvez utiliser MZFormSheetController comme ceci:
MZFormSheetController *formSheet = [[MZFormSheetController alloc] initWithSize:customSize viewController:presentedViewController];
formSheet.shouldDismissOnBackgroundViewTap = YES;
[presentingViewController mz_presentFormSheetController:formSheet animated:YES completionHandler:nil];
Swift 2 / Xcode version 7.2 (7C68) le code suivant a fonctionné pour moi.
Attention: ce code doit être mis dans le fichier ViewController.swift de la feuille FormSheet ou page présentée, ici: "PageSheetViewController.swift"
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)
}
}
}
}