Deaktivieren Sie die Geste, um die modale Präsentation des Formulars / Seitenblatts aufzurufen


75

In iOS 13 können modale Präsentationen, die den Formular- und Seitenblattstil verwenden, mit einer Schwenkbewegung verworfen werden. Dies ist in einem meiner Formularblätter problematisch, da der Benutzer in dieses Feld zieht, was die Geste stört. Es zieht den Bildschirm nach unten, anstatt eine vertikale Linie zu zeichnen.

Wie können Sie das vertikale Wischen deaktivieren, um die Geste in einem als Blatt dargestellten Controller für die modale Ansicht zu schließen?

Durch die Einstellung isModalInPresentation = truekann das Blatt weiterhin heruntergezogen werden, es wird jedoch nicht geschlossen.


1
Es gibt ein gut erklärtes Dokument zum Apple Developer: developer.apple.com/documentation/uikit/view_controllers/…
Stleamist

Antworten:


82

Im Allgemeinen sollten Sie nicht versuchen, das Wischen zu deaktivieren, um die Funktionalität zu schließen, da Benutzer erwarten, dass sich alle Formular- / Seitenblätter in allen Apps gleich verhalten. Stattdessen möchten Sie möglicherweise einen Präsentationsstil im Vollbildmodus verwenden. Wenn Sie ein Blatt verwenden möchten, das nicht durch Streichen entfernt werden kann, stellen Sie isModalInPresentation = truees ein. Beachten Sie jedoch, dass das Blatt weiterhin vertikal nach unten gezogen werden kann und beim Loslassen der Berührung wieder nach oben springt. Schauen Sie sich die aus UIAdaptivePresentationControllerDelegate Dokumentation zu reagieren , wenn der Benutzer es über Swipe abzutun versucht, neben anderen Maßnahmen.

Wenn Sie ein Szenario haben, in dem die Gesten- oder Berührungsbehandlung Ihrer App durch die Funktion zum Streichen zum Streichen beeinflusst wird, habe ich von einem Apple-Techniker einige Ratschläge erhalten, wie dies behoben werden kann.

Wenn Sie verhindern können, dass die Pan-Gestenerkennung des Systems beginnt, wird die gestische Entlassung verhindert. Einige Möglichkeiten, dies zu tun:

  1. Wenn Ihre Leinwandzeichnung mit einer Gestenerkennung wie Ihrer eigenen UIGestureRecognizerUnterklasse erstellt wurde, geben Sie die beganPhase vor der Entlassungsgeste des Blattes ein. Wenn Sie so schnell wie möglich erkennen UIPanGestureRecognizer, gewinnen Sie und die Entlassungsgeste des Blattes wird untergraben.

  2. Wenn Ihre Leinwandzeichnung mit einem Gestenerkenner erstellt wurde, richten Sie mit -shouldBeRequiredToFailByGestureRecognizer:(oder der zugehörigen Delegatenmethode) eine dynamische Fehleranforderung ein , bei der Sie zurückkehren, NOwenn der übergebene Gestenerkenner a ist UIPanGestureRecognizer.

  3. Wenn Ihre Leinwandzeichnung mit manuellem Touch-Handling (z. B. touchesBegan:) erstellt wurde, überschreiben Sie -gestureRecognizerShouldBeginIhre Touch-Handling-Ansicht und kehren Sie zurück, NOwenn der übergebene Gestenerkenner a ist UIPanGestureRecognizer.

Mit meinem Setup hat sich # 3 als sehr gut erwiesen. Auf diese Weise kann der Benutzer außerhalb der Zeichenfläche nach unten wischen, um sie zu schließen (z. B. in der Navigationsleiste), während der Benutzer zeichnen kann, ohne das Blatt zu bewegen, wie es zu erwarten wäre.

Ich kann nicht empfehlen, nach der Geste zu suchen, um sie zu deaktivieren, da sie ziemlich dynamisch zu sein scheint und sich beispielsweise beim Wechsel zwischen verschiedenen Größenklassen wieder aktivieren kann. Dies könnte sich in zukünftigen Versionen ändern.


1
Ich folgte # 2, verwendete jedoch die Delegate-Methode gestureRecognizer(_:,shouldRecognizeSimultaneouslyWith:), damit einige Erkenner zusammenarbeiten konnten und andere nicht.
Doug

1
Ich kann anscheinend nichts davon zum Laufen bringen? Irgendwelche Ratschläge für mein Szenario? Ich präsentiere einen Bildschirm mit einem Fotoauswahlsystem, indem ich durch Ziehen die von der iOS-Foto-App verwendete Standardauswahl verwende. Dies verwendet einen Pan-Gesten-Erkenner und einen UICollectionView. Verzeihen Sie mir, wenn ich falsch liege, aber müssten wir nicht der Delegierte des Entlassungserkenners werden, um eine dieser Methoden anwenden zu können?
Simonthumper

3
# 3 funktionierte hervorragend für meine App, mit der der Benutzer eine Ansicht zeichnen kann.
Rory Prior

2
@ Jordan H Könnten Sie besser # 3 mit etwas Code erklären?
Giorgio

3
Gut gemacht! Sehr interessant. # 3 funktioniert perfekt für mich.
Artem Kirillov

28

Diese Geste befindet sich in der presentedViewEigenschaft des Modal View Controllers . Beim Debuggen enthält das gestureRecognizersArray dieser Eigenschaft nur ein Element, und das Drucken führte zu etwa dem folgenden Ergebnis:

UIPanGestureRecognizer: 0x7fd3b8401aa0 (_UISheetInteractionBackgroundDismissRecognizer);

Um diese Geste zu deaktivieren, können Sie wie folgt vorgehen:

let vc = UIViewController()

self.present(vc, animated: true, completion: {
  vc.presentationController?.presentedView?.gestureRecognizers?[0].isEnabled = false
})

Um es wieder zu aktivieren, setzen Sie es einfach isEnabledzurück auf true:

vc.presentationController?.presentedView?.gestureRecognizers?[0].isEnabled = true

Beachten Sie, dass sich iOS 13 noch in der Beta befindet, sodass in einer kommenden Version möglicherweise ein einfacherer Ansatz hinzugefügt wird.

Obwohl diese Lösung im Moment zu funktionieren scheint, würde ich sie nicht empfehlen, da sie in bestimmten Situationen möglicherweise nicht funktioniert oder in zukünftigen iOS-Versionen geändert wird und möglicherweise Auswirkungen auf Ihre App hat.


1
Das funktioniert gut! Kann auch in viewWillAppearden angezeigten View Controller eingefügt werden - nützlich für Segues. 🤗
Jordan H

1
Beachten Sie, dass Sie, wenn Sie eine Bildlaufansicht in diesem Ansichts-Controller haben, diese nach wie vor herunterziehen können, um sie zu schließen, sobald Sie oben angekommen sind. Aber wenn Sie keine Bildlaufansicht haben, ist dies großartig.
Jordan H

1
@ JordanH Das stimmt! Es scheint, dass eine andere Geste aus der Bildlaufansicht auch die modale Entlassung irgendwie handhabt. Ich habe die Gesten der Bildlaufansicht gedruckt und es gibt eine UISwipeDismissalGestureRecognizer. Dies könnte das Problem sein.
M Reza

7
UITableViewschafft _UISwipeDismissalGestureRecognizerauch. Wenn Sie einen Navigationscontroller mit einem Root-Ansichtscontroller erstellen, den Stapel modal als Seiten- / Formularblatt darstellen und einen anderen Ansichtscontroller darüber schieben, wird der gesamte Stapel durch die Geste zum Streichen nach unten über a verworfen Der Gestenerkenner wurde irgendwo höher in der UIViewHierarchie erstellt. Ohne die ausdrückliche Unterstützung von Apple für das Deaktivieren der Verarbeitung von Touch-Ereignissen durch Wischen nach unten, um sie zu schließen, besteht die einzige zuverlässige Lösung (ab Xcode 11 Beta 3) in der Verwendung UIModalPresentationStylevon UIModalPresentationFullScreen.
Gary

3
Sie können sogar nach Name oder Typ suchen, wenn Sie sicherer sein möchten, welche Geste Sie deaktivieren. for gesture in guestures where gesture.name == "_UISheetInteractionBackgroundDismissRecognizer" { gesture.isEnabled = false }
spfursich

16

Verwenden Sie im vorgestellten ViewController dies in viewDidLoad:

if #available(iOS 13.0, *) {
    self.isModalInPresentation = true
}

7
Wie in der Frage erwähnt, isModalInPresentation = truekann das Blatt immer noch heruntergezogen werden, es wird jedoch nicht entlassen, was genau das ist, was Sie benötigen, oder es kann je nach Anwendungsfall problematisch sein, wie es für meine Zeichenfläche war.
Jordan H

3
Du hast recht. Ich denke, die einfachste Lösung ist die Verwendung des alten modalen Vollbildstils:self.modalPresentationStyle = .fullScreen
Zoltan Vinkler

1
viewController.isModalInPresentation = truearbeitete für mich
BharathRao

1
Dies ist die richtige Antwort. Sie behalten immer noch die Animation des Herunterziehens bei, schließen die Ansicht jedoch nie. Vielen Dank!
Radu Ursache

1
Funktioniert bei mir. Besser als das Entfernen von Gestenerkennern.
Benoit Deldicque

10

In meinem Fall habe ich einen modalen Bildschirm mit einer Ansicht, die Berührungen zum Erfassen von Kundensignaturen erhält.

Das Deaktivieren der Gestenerkennung im Navigationscontroller löste das Problem und verhinderte, dass die modale interaktive Entlassung überhaupt ausgelöst wurde.

Die folgenden Methoden sind in unserem Modal View Controller implementiert und werden über einen Delegaten aus unserer benutzerdefinierten Signaturansicht aufgerufen.

Angerufen von touchesBegan:

private func disableDismissalRecognizers() {
    navigationController?.presentationController?.presentedView?.gestureRecognizers?.forEach {
        $0.isEnabled = false
    }
}

Angerufen von touchesEnded:

private func enableDismissalRecognizers() {
    navigationController?.presentationController?.presentedView?.gestureRecognizers?.forEach {
        $0.isEnabled = true
    }
}

Hier ist ein GIF, das das Verhalten zeigt: Geben Sie hier die Bildbeschreibung ein

Diese als doppelt gekennzeichnete Frage beschreibt das Problem, das ich hatte, besser: Deaktivieren der interaktiven Entlassung des dargestellten Ansichtscontrollers unter iOS 13 beim Ziehen aus der Hauptansicht


8

Sie können den Präsentationsstil ändern. Wenn der Vollbildmodus im Vollbildmodus angezeigt wird, ist er deaktiviert

navigationCont.modalPresentationStyle = .fullScreen

1
Ich denke das ist die richtige Antwort. Ich habe dies tatsächlich zusammen mit isModalInPresentation verwendet, nur um sicherzugehen, und es funktioniert perfekt. Der Schlüssel für mich war, diese im Elternteil festzulegen. Als ich versuchte, viewDidLoad im dargestellten Controller einzustellen, funktionierte es NICHT.
Biomiker

3

Sie können die UIAdaptivePresentationControllerDelegate-Methode PresentationControllerDidAttemptToDismiss verwenden und den gestureRecognizer in der präsentierten Ansicht deaktivieren. Etwas wie das:

func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {      
    presentationController.presentedView?.gestureRecognizers?.first?.isEnabled = false
}

3

Für jeden Körper, der Probleme mit Jordans Lösung Nr. 3 hat.

Sie müssen nach dem ROOT-Ansichtscontroller suchen, der angezeigt wird. Abhängig von Ihrem Ansichtsstapel ist dies möglicherweise nicht Ihre aktuelle Ansicht.

Ich musste nach meinen Navigationscontrollern PresentationViewController suchen.

BTW @Jordam: Danke!

UIGestureRecognizer *gesture = [[self.navigationController.presentationController.presentedView gestureRecognizers] firstObject];
if ([gesture isKindOfClass:[UIPanGestureRecognizer class]]) {
    UIPanGestureRecognizer * pan = (UIPanGestureRecognizer *)gesture;
    pan.delegate = self;
}

1

Ich benutze das:

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

for(UIGestureRecognizer *gr in self.presentationController.presentedView.gestureRecognizers) {
    if (@available(iOS 11.0, *)) {
        if([gr.name isEqualToString:@"_UISheetInteractionBackgroundDismissRecognizer"]) {
            gr.enabled = false;
        }
    }
}

0

Ich werde versuchen, die von @Jordan H bereits vorgeschlagene Methode 2 genauer zu beschreiben:

1) Um die Schwenkgeste des Modalblatts zu erfassen und Entscheidungen darüber zu treffen, fügen Sie diese in die Ansichtssteuerung ein viewDidLoad:

navigationController?.presentationController?.presentedView?.gestureRecognizers?.forEach {
   $0.delegate = self
}

2) Aktivieren Sie die Möglichkeit, die Schwenkgeste zusammen mit Ihren eigenen Gesten zu erfassen gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:)

3) Die eigentliche Entscheidung kann eingehen gestureRecognizer(_:shouldBeRequiredToFailBy:)

Beispielcode, mit dem die Wischgeste der Schwenkgeste des Blattes vorgezogen wird, wenn beide vorhanden sind. Es wirkt sich nicht auf die ursprüngliche Schwenkgeste in Bereichen aus, in denen keine Wischgestenerkennung vorhanden ist, und daher kann das ursprüngliche "Wischen zum Entlassen" weiterhin wie vorgesehen funktionieren.

extension PeopleViewController: UIGestureRecognizerDelegate {

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        if gestureRecognizer === UIPanGestureRecognizer.self && otherGestureRecognizer === UISwipeGestureRecognizer.self {
            return true
        }
        return false
    }

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
}

In meinem Fall habe ich nur wenige Swipe-Gestenerkenner, daher reicht es mir, Typen zu vergleichen. Wenn jedoch mehr vorhanden sind, kann es sinnvoll sein, die gestureRecognizers selbst zu vergleichen (entweder programmgesteuert hinzugefügte oder als Ausgänge vom Interface Builder), wie in beschrieben Dieses Dokument: https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/coordinating_multiple_gesture_recognizers/preferring_one_gesture_over_another

So funktioniert der Code in meinem Fall. Ohne sie wurde die Wischgeste größtenteils ignoriert und funktionierte nur gelegentlich.

Geben Sie hier die Bildbeschreibung ein


Hey, ich habe deine Lösung ausprobiert, aber ich habe immer noch ein Problem. Wenn ich einen Delegierten einschalte viewDidAppear(weil mein presentationControllerNull ist, präsentiere ich nur moralisch einen VC). Und ich wiederhole die Überwachung der Ansicht, um herauszufinden, ob die Ansicht eine hat PanGesture, und setze ihren Delegierten auf self. Dann kann mein VC nicht nach unten wischen, um zu entlassen. Gibt es eine andere Möglichkeit, mein Problem zu lösen? Bitte helfen Sie
Weslie

@Weslie, es sind die Erkenner der präsentierten Ansicht, die Sie abfangen müssen, nicht die Erkenner der Übersicht. Versuchen Sie, vorübergehend die delegate-Methode gestureRecognizerShouldBegin hinzuzufügen, um festzustellen, welche Gesten tatsächlich abgefangen und debuggt werden.
Vitalii

0

in IOS 13

if #available(iOS 13.0, *) {
    obj.isModalInPresentation = true
} else {
    // Fallback on earlier versions
}

0

In dem Fall, in dem eine UITableViewoder UICollectionViewdie Seitenblatt-Entlassungsgeste initiiert wird, wenn der Benutzer versucht, über das obere Ende der Bildlaufansicht hinaus zu scrollen, kann diese Geste deaktiviert werden, indem eine unsichtbare Geste hinzugefügt wird UIRefreshControl, die endRefreshingsofort aufruft .

Siehe auch https://stackoverflow.com/a/58676756/2419404


0

In Vorbereitung (für: Absender :):

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == viewControllerSegueID {
        let controller = segue.destination as! YourViewController
        controller.modalPresentationStyle = .fullScreen
    }
}

oder, nachdem Sie Ihren Controller initialisiert haben:

let controller = YourViewController()
controller.modalPresentationStyle = .fullScreen

0

Möglicherweise erhalten Sie zuerst einen Verweis auf den UIPanGestureRecognizer, der die Seitenblattentlassung in der viewDidAppear () -Methode behandelt. Beachten Sie, dass diese Referenz in viewWillAppear () oder viewDidLoad () gleich Null ist. Dann deaktivieren Sie es einfach.

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    presentationController?.presentedView?.gestureRecognizers?.first.isEnabled = false
}

Wenn Sie mehr Anpassungen wünschen, anstatt sie vollständig zu deaktivieren, z. B. wenn Sie eine Navigationsleiste im Seitenblatt verwenden, legen Sie den Delegaten dieses UIPanGestureRecognizer auf Ihren eigenen Ansichtscontroller fest. Auf diese Weise können Sie den Gestenerkenner ausschließlich in Ihrer ContentView deaktivieren, während Sie ihn durch Implementierung in Ihrer navBar-Region aktiv halten

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {}

-2

Für den Navigations-Controller können wir Folgendes verwenden, um eine Swipe-Interaktion für die dargestellte Ansicht zu vermeiden:

if #available(iOS 13.0, *) {navController.isModalInPresentation = true}

2
Nein, aber das hindert die Geste nicht , worum es bei der Frage geht.
Matt
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.