wake-up-neo.com

"From View Controller" verschwindet mit UIViewControllerContextTransitioning

Ich habe ein Problem und ich habe es unten beschrieben.

Ich verwende UIViewControllerContextTransitioning für benutzerdefinierte Übergänge.

Ich habe 2 View Controller, First View Controller und Second View Controller.

Jetzt möchte ich den Secondview-Controller mit Animation zum First View-Controller hinzufügen. Ich habe es geschafft, jetzt habe ich den secondview controller transparent, so dass wir den first view controller unter dem secondview controller sehen können.

Aber ich kann den First-View-Controller nicht sehen, und ich kann nur einen schwarzen Bildschirm unter dem Second-View-Controller sehen.

Hier ist der Code.

-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
    self.transitionContext = transitionContext;
    if(self.isPresenting){
        [self executePresentationAnimation:transitionContext];
    }
    else{
       [self executeDismissalAnimation:transitionContext];
    }
  }

-(void)executePresentationAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
     UIView* inView = [transitionContext containerView];
     UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

     UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];

     CGRect offScreenFrame = inView.frame;
     offScreenFrame.Origin.y = inView.frame.size.height;
     toViewController.view.frame = offScreenFrame;

    toViewController.view.backgroundColor = [UIColor clearColor];
    fromViewController.view.backgroundColor = [UIColor clearColor];
    inView.backgroundColor = [UIColor  clearColor];
    [inView insertSubview:toViewController.view aboveSubview:fromViewController.view];
     // [inView addSubview:toViewController.view];
    CFTimeInterval duration = self.presentationDuration;
    CFTimeInterval halfDuration = duration/2;

    CATransform3D t1 = [self firstTransform];
    CATransform3D t2 = [self secondTransformWithView:fromViewController.view];

    [UIView animateKeyframesWithDuration:halfDuration delay:0.0 options:UIViewKeyframeAnimationOptionCalculationModeLinear animations:^{

    [UIView addKeyframeWithRelativeStartTime:0.0f relativeDuration:0.5f animations:^{
        fromViewController.view.layer.transform = t1;
    }];

    [UIView addKeyframeWithRelativeStartTime:0.5f relativeDuration:0.5f animations:^{
        fromViewController.view.layer.transform = t2;
    }];
    } completion:^(BOOL finished) {
    }];


    [UIView animateWithDuration:duration delay:(halfDuration - (0.3*halfDuration)) usingSpringWithDamping:0.7f initialSpringVelocity:6.0f options:UIViewAnimationOptionCurveEaseIn animations:^{
        toViewController.view.frame = inView.frame;
    } completion:^(BOOL finished) {
        [self.transitionContext completeTransition:YES];
    }];
}

Wann [self.transitionContext completeTransition:YES]; aufgerufen wird, verschwindet plötzlich der erste Ansichts-Controller und unter dem zweiten Ansichts-Controller wird ein schwarzer Bildschirm angezeigt.

Hat jemand eine Idee? Vielen Dank.

100
NiravPatel

Ich hatte hier das gleiche Problem - sieht aus wie ein Fehler in iOS 8. Ich habe ein Radar abgelegt .

Ich habe Reveal verwendet, um die Ansichtshierarchie zu überprüfen, nachdem der Bildschirm schwarz geworden ist. Der Schlüssel UIWindow ist komplett leer - überhaupt keine Ansichtshierarchie!

Reveal'd

Ich habe ein bisschen rumgespielt und es sieht so aus, als gäbe es eine einfache Lösung für einfache Fälle. Sie können die Ansicht von toViewController einfach erneut als Unteransicht des Schlüsselfensters hinzufügen:

transitionContext.completeTransition(true)
UIApplication.sharedApplication().keyWindow!.addSubview(toViewController.view)

Ich habe es überprüft und das rootViewController des Schlüsselfensters ist immer noch richtig eingestellt, also ist das in Ordnung. Ich bin mir nicht sicher, was passieren würde, wenn Sie Ihren Controller aus einem bereits vorgestellten modalen Controller heraus präsentieren würden. In komplexeren Fällen müssen Sie also herumexperimentieren.

94
Ash Furrow

Ich bin der Meinung, dass die Gründe dafür besser erklärt werden sollten.

Die Ansicht wird ausgeblendet, weil Sie die Ansicht des präsentierenden Ansichtscontrollers aus der ursprünglichen Position (Ansichtshierarchie) herausnehmen und in die von Ihrem Animator bereitgestellte Containeransicht einfügen, diese jedoch nach Abschluss der Animation nicht mehr zurückgeben. Damit wird die Ansicht des View Controllers mit seiner Übersicht (containerView) vollständig aus dem Fenster entfernt.

In iOS 7 hat das System die Ansichten der View Controller, die an der Präsentation (Präsentieren und Präsentieren) beteiligt sind, immer an ihre ursprünglichen Stellen zurückgesetzt, nachdem die Animation des Übergangs automatisch beendet wurde. Bei einigen Präsentationsstilen unter iOS 8 ist dies nicht mehr der Fall.

Die Regel ist sehr einfach: Der Animator sollte die Ansicht des präsentierenden Ansichtscontrollers nur bearbeiten, wenn die Ansicht dieses Ansichtscontrollers durch Ende des Übergangs vollständig ausgeblendet (aus der Ansichtshierarchie entfernt) werden soll. Mit anderen Worten bedeutet dies, dass nach Abschluss der anfänglichen Präsentationsanimation nur die Ansicht des Controllers für die präsentierte Ansicht sichtbar ist und nicht die Ansicht des Controllers für die präsentierte Ansicht. Wenn Sie beispielsweise die Deckkraft der Ansicht des präsentierten Ansichtscontrollers auf 50% setzen und UIModalPresentationFullScreen verwenden, können Sie die Ansicht des präsentierten Ansichtscontrollers unter der präsentierten Ansicht nicht sehen. Wenn Sie jedoch UIModalPresentationOverFullscreen verwenden, ist die Methode shouldRemovePresentersView von UIPresentationController für die Angabe verantwortlich Das).

Warum kann der Animator nicht jederzeit die Ansicht des Presenting View Controllers ändern? Erstens, wenn die Ansicht des Presenting View Controllers nach Abschluss der Animation während des gesamten Präsentationslebenszyklus sichtbar bleibt, muss sie überhaupt nicht animiert werden - sie bleibt einfach dort, wo sie ist. Zweitens weiß der Präsentationscontroller höchstwahrscheinlich nicht, wie er die Ansicht dieses Ansichtscontrollers bei Bedarf anordnet, wenn sich beispielsweise die Ausrichtung ändert, wenn der Eigentümer dieses Ansichtscontrollers jedoch den ursprünglichen Eigentümer hat .

In iOS 8 viewForKey:-Methode wurde eingeführt, um Ansichten abzurufen, die der Animator bearbeitet. Erstens ist es hilfreich, die oben beschriebene Regel einzuhalten, indem Sie immer dann nil zurückgeben, wenn der Animator die Ansicht nicht berühren soll. Zweitens wird möglicherweise eine andere -Ansicht zurückgegeben, die der Animator animieren kann. Stellen Sie sich vor, Sie implementieren eine Präsentation ähnlich dem Formularblatt. In diesem Fall möchten Sie der Ansicht des präsentierten Ansichtscontrollers einen Schatten oder eine Dekoration hinzufügen. Der Animator animiert stattdessen diese Dekoration, und die Ansicht des präsentierten Ansichtscontrollers ist ein untergeordnetes Element der Dekoration.

viewControllerForKey: verschwindet nicht. Es kann weiterhin verwendet werden, wenn ein direkter Zugriff auf Ansichtssteuerungen erforderlich ist. Der Animator sollte jedoch keine Annahmen über die Ansichten treffen, die er zum Animieren benötigt.

Sie können verschiedene Dinge tun korrekt Beheben Sie ein Problem mit der Ansicht eines verschwindenden Controllers für Präsentationsansichten, wenn Sie diese explizit in die Containeransicht des Animators einfügen:

  1. Wenn Sie die Ansicht des Presenting View Controllers nicht animieren müssen, verwenden Sie viewForKey:, um Ansichten zu animieren, anstatt direkt auf die Ansichten des Controllers zuzugreifen. viewForKey: kann null oder sogar völlig andere Ansichten zurückgeben.

  2. Wenn Sie die Ansicht des Presenting View Controllers animieren möchten, sollten Sie den UIModalPresentationFullScreen -Stil verwenden oder mit UIModalPresentationCustom fortfahren und Ihre eigene Unterklasse von UIPresentationController mit shouldRemovePresentersView implementieren und YES zurückgeben. Tatsächlich ist die Implementierung dieser Methode der Hauptunterschied zwischen internen Präsentationscontrollern, die durch die Stile UIModalPresentationFullScreen und UIModalPresentationCustom definiert sind, abgesehen von der Tatsache, dass Sie mit letzterem benutzerdefinierte Präsentationscontroller verwenden können.

  3. In allen anderen seltenen Fällen müssen Sie die Ansicht des Presenting View Controllers an ihren ursprünglichen Speicherort zurücksetzen, wie in anderen Antworten vorgeschlagen.

73
egdmitry

In iOS 8 müssen Sie die von viewForKey: Zurückgegebenen Ansichten anstelle der Eigenschaft .view Der von viewControllerForKey: Zurückgegebenen Ansichtscontroller bearbeiten. Dies wird in der Betadokumentation nicht besonders deutlich, aber wenn Sie in den Quellcode für UIViewControllerTransitioning.h schauen, sehen Sie diesen Kommentar über viewControllerForKey::

// Currently only two keys are defined by the
// system - UITransitionContextToViewControllerKey, and
// UITransitionContextFromViewControllerKey.
// Animators should not directly manipulate a view controller's views and should
// use viewForKey: to get views instead.
- (UIViewController *)viewControllerForKey:(NSString *)key;

Verwenden Sie also den Rückgabewert von toViewController.view, Anstatt Frames usw. von [transitionContext viewForKey:UITransitionContextToViewKey] Anzupassen.

Wenn Ihre App iOS7 und/oder Xcode 5 unterstützen muss, können Sie in UIViewController eine einfache Kategoriemethode wie die folgende verwenden:

- (UIView *)viewForTransitionContext:(id<UIViewControllerContextTransitioning>)transitionContext
{
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
    if ([transitionContext respondsToSelector:@selector(viewForKey:)]) {
        NSString *key = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey] == self ? UITransitionContextFromViewKey : UITransitionContextToViewKey;
        return [transitionContext viewForKey:key];
    } else {
        return self.view;
    }
#else
    return self.view;
#endif
}

Dann holen Sie sich wie gewohnt Ihre toViewController und fromViewController, aber holen Sie sich die Views mit [toViewController viewForTransitionContext:transitionContext].

Bearbeiten: Es scheint einen Fehler zu geben, bei dem die Ansicht des Controllers für Präsentationsansichten bei der Rückgabe von viewForKey null ist, wodurch Sie daran gehindert werden, modale Übergänge vorzunehmen, die die Präsentationsansicht überhaupt animieren (z. B. abrutschen oder kippen). horizontal). Ich habe einen Fehler für iOS8 bei rdar: // 17961976 ( http://openradar.appspot.com/radar?id=5210815787433984 ) gemeldet. Siehe auch das Beispielprojekt unter http://github.com/bcherry/TransitionBug

Edit 2: Dank an graveley für den Vorschlag behebt die Verwendung von UIModalPresentationFullScreen das Problem. Vielleicht ist das kein Fehler. Apple kann vorsehen, dass UIModalPresentationCustom nur die Ansicht des eingehenden Modals ändert. Wenn Sie die ausgehende Ansicht ändern möchten, müssen Sie die Vollbilddarstellung der neuen Ansicht sicherstellen? In jedem Fall sollten Sie dies tun benutze viewForKey und UIModalPresentationFullScreen.

69
bcherry

Das Problem wurde behoben, indem modalPresentationStyle nicht auf UIModalPresentationCustom gesetzt wurde.

Mit anderen Worten, indem der Standardwert von UIModalPresentationFullScreen beibehalten wurde, anstatt UIModalPresentationCustom anzugeben, wurde das Problem mit der verschwindenden Ansicht behoben. Beachten Sie, dass das UIViewControllerTransitioningDelegate-Protokoll weiterhin befolgt wird, auch wenn dies auf dem Standardwert belassen wird. Wenn ich mich richtig erinnere, war UIModalPresentationCustom einmal eine Anforderung.

Funktioniert bisher nur bei nicht interaktiven Animationen.

24
graveley

Ich habe diese äußerst nützliche Antwort in einem verwandten Thread von Lefteris gefunden: https://stackoverflow.com/a/27165723/370917

Etwas zusammenfassen:

  1. setzen Sie modalPresentationStyle auf .Custom
  2. unterklasse UIPresentationController, überschreiben shouldRemovePresentersView (mit NO)
  3. überschreiben Sie presentationControllerForPresentedViewController in Ihrer TransitionDelegate-Klasse und geben Sie Ihren benutzerdefinierten UIPresentationController zurück

+1 Fügen Sie in Ihrem benutzerdefinierten Übergang kein toView hinzu, wenn die Entlassungsanimation ausgeführt wird.

Demonstriert hier:

https://www.dropbox.com/s/7rpkyamv9k9j18v/CustomModalTransition.zip?dl= ohne irgendwelche Hacks! Es ist wie Magie! :)

14

In iOS 8 müssen Sie einen UIPresentationController erstellen und die folgende Methode in der UIViewControllerTransitioningDelegate implementieren.

- (UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(UIViewController *)presenting sourceViewController:(UIViewController *)source;

Fordert Ihren Stellvertreter auf, den benutzerdefinierten Präsentationscontroller zum Verwalten der Ansichtshierarchie beim Präsentieren eines Ansichtscontrollers zu verwenden.

Rückgabewert:

Der benutzerdefinierte Präsentations-Controller zum Verwalten der modalen Präsentation.

Diskussion:

Wenn Sie einen Ansichtscontroller mit dem Präsentationsstil UIModalPresentationCustom präsentieren, ruft das System diese Methode auf und fragt nach dem Präsentationscontroller, der Ihren benutzerdefinierten Stil verwaltet. Wenn Sie diese Methode implementieren, erstellen Sie damit das benutzerdefinierte Presentation Controller-Objekt, mit dem Sie den Präsentationsprozess verwalten möchten, und geben es zurück.

Wenn Sie diese Methode nicht implementieren oder wenn Ihre Implementierung dieser Methode null zurückgibt, verwendet das System ein standardmäßiges Presentation Controller-Objekt. Der Standard-Präsentations-Controller fügt der Ansichtshierarchie keine Ansichten oder Inhalte hinzu.

Verfügbarkeit Verfügbar in iOS 8.0 und höher.

Weitere Informationen finden Sie im Video zur WWDC 2014:

https://developer.Apple.com/videos/wwdc/2014/?include=228

Es gibt auch einen Beispielcode vom WWDC mit dem Namen "LookInside: Presentation Controllers Adaptivity und Custom Animator Objects", den Sie von der WWDC 2014-Beispielcodeseite herunterladen können.

Möglicherweise müssen Sie den Beispielcode ein wenig ändern. Die init-Methode von UIPresentationController wurde folgendermaßen geändert:

initWithPresentedViewController:presented presentingViewController:presenting

Vorher wurde es präsentiert und dann präsentiert. Tauschen Sie sie einfach aus und es sollte funktionieren.

8
Paulo Faria

Hier ist eine Objective C-Version von Ashs Fix.

// my attempt at obj-c version of Ash's fix
UIView *theToView = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey].view;
[[[UIApplication sharedApplication] keyWindow] addSubview:theToView];
[transitionContext completeTransition:YES]

Nachdem ich die Ansicht wieder hinzugefügt hatte, musste ich die Reihenfolge vertauschen und die Methode [transitionContext completeTransition:] aufrufen, um einen neuen Ansichtscontroller aus dem Entlassungsabschlussblock eines anderen Ansichtscontrollers anzuzeigen, damit dieser ordnungsgemäß funktioniert.

Ich weiß nicht, ob das für alle Probleme behoben werden kann, aber es funktioniert in meiner App. Prost!

7
vichudson1

anstelle von [inView insertSubview: toViewController.view aboveSubview: fromViewController.view]; einfach hinzufügen: [inView addSubview: toViewController.view];

if (self.presenting) {

    [transitionContext.containerView addSubview:toViewController.view];
    // your code

} else {
    // your code
}

Sie können ein Beispiel hier sehen: Link und es funktioniert auf iOS 7 und iOS 8

7
CarlosGz

Ich habe das gefunden viewForKey:UITransitionContextToViewKey gibt auf ios8 nil zurück. Wenn es also null ist, greife ich nach der Ansicht vom Controller zum Anzeigen.

Dies scheint jedoch dazu zu führen, dass die An-Ansicht nicht aus dem Container in das Fenster verschoben wird, wenn completeTransition:YES wird genannt. Also, wenn viewForKey:UITransitionContextToViewKey gibt nil zurück, ich falle rüber zu toVC.view, und verfolge die Tatsache, dass es null zurückgegeben hat, und verschiebe es nach Abschluss in die anfängliche Übersicht des Containers (die zufällig das Fenster ist).

Dieser Code funktioniert also sowohl unter iOS7 als auch unter iOS8 und sollte auch unter iOS9, selbst wenn sie es reparieren oder nicht.

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
    // Get the 'from' and 'to' views/controllers.
    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    BOOL hasViewForKey = [transitionContext respondsToSelector:@selector(viewForKey:)]; // viewForKey is iOS8+.
    UIView *fromView = hasViewForKey ?
        [transitionContext viewForKey:UITransitionContextFromViewKey] :
        fromVC.view;
    UIView *toView = hasViewForKey ?
        [transitionContext viewForKey:UITransitionContextToViewKey] :
        toVC.view;

    // iOS8 has a bug where viewForKey:to is nil: http://stackoverflow.com/a/24589312/59198
    // The workaround is: A) get the 'toView' from 'toVC'; B) manually add the 'toView' to the container's
    // superview (eg the root window) after the completeTransition call.
    BOOL toViewNilBug = !toView;
    if (!toView) { // Workaround by getting it from the view.
        toView = toVC.view;
    }
    UIView *container = [transitionContext containerView];
    UIView *containerSuper = container.superview; // Used for the iOS8 bug workaround.

    // Perform the transition.
    toView.frame = container.bounds;
    [container insertSubview:toView belowSubview:fromView];
    [UIView animateWithDuration:kDuration delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
        fromView.frame = CGRectOffset(container.bounds, 0, CGRectGetHeight(container.bounds));
    } completion:^(BOOL finished) {
        [transitionContext completeTransition:YES];

        if (toViewNilBug) {
            [containerSuper addSubview:toView];
        }
    }];
}
5
Chris

Ich fand das gut für Obj-C:

    [transitionContext completeTransition:YES];
    if(![[UIApplication sharedApplication].keyWindow.subviews containsObject:toViewController.view]) {
        [[UIApplication sharedApplication].keyWindow addSubview:toViewController.view];
    }

Scheint auf ios7 und ios8 gut zu funktionieren.

5
Reedy

Ich habe festgestellt, dass dieser Bug (und viele mehr!) Verschwindet, wenn Sie modalPresentationStyle = UIModalPresentationFullScreen. Sie erhalten natürlich weiterhin Ihre benutzerdefinierte Übergangsanimation.

3
Chris

Nachdem ich auf dieses Problem gestoßen war, war ich sehr verwirrt, weil ich vor nicht allzu langer Zeit etwas fast Identisches geschrieben hatte, das gut funktionierte. Kam hierher und suchte nach Antworten, um Lösungen zu finden, die ziemlich kitschig aussehen und die Grundursache nicht zu verstehen scheinen ... es ist tatsächlich sehr einfach zu beheben.

Einige Antworten erwähnen die Änderung von modalPresentationStyle in .overFullScreen. Das ist richtig, .overCurrentContext würde auch funktionieren. Dies wird erwartet, und das Verhalten Apple Dokumente. Aber warum funktioniert das nicht für alle? Warum all der hackige Code und Kombinationen davon mit etwas anderem und verrückten Sachen, die Sie nicht sollten ' nicht tun?

Es stellt sich heraus, dass Sie den Präsentationsstil einstellen müssen VOR DEN VIEW-LASTEN. Nicht danach. Tun Sie es in init oder auf dem vorherigen Controller, oder wie Sie möchten - solange es noch nicht geladen ist.

2
Jordan Smith

Die Verwendung des neuen UIModalPresentationOverCurrentContext hat dies für mich behoben. Mein ursprünglicher Übergang auf iOS 7 bestand nur darin, einen unscharfen Hintergrund für die Ansicht unter dem Modal zu haben.

1
malhal

Auch in diesem Punkt bin ich stecken geblieben. Ich habe versucht, einen benutzerdefinierten Übergang mit einem halbtransparenten Hintergrund zu erstellen, in dem ich immer noch den View Controller sehen konnte, von dem ich stammte, aber nur einen schwarzen Hintergrund hatte. Ich habe festgestellt, dass Mark Arons Antwort in diesem Thread mir geholfen hat, aber es ist in Objective C geschrieben. Daher hier eine Swift 3 Version dieser Antwort, die ich für iOS 9 und iOS 10 getestet habe:

  1. Erstellen Sie eine Unterklasse von UIPresentationController. Überschreiben Sie die shouldRemovePresentersView wie folgt auf false:

    class ModalPresentationController: UIPresentationController {
    
    override var shouldRemovePresentersView: Bool {
    return false
    }
    
    override func containerViewWillLayoutSubviews() {
    presentedView?.frame = frameOfPresentedViewInContainerView
    }
    }
    
  2. Geben Sie an der Stelle, an der Sie den neuen Ansichtscontroller instanziieren und seinen Übergangsdelegaten festlegen, an, dass er einen benutzerdefinierten modalen Präsentationsstil anzeigen soll.

    let newVC = mainStoryboard.instantiateViewController(withIdentifier: "newVC") as! NewViewController 
    
    newVC.transitioningDelegate = self
    
    newVC.modalPresentationStyle = UIModalPresentationStyle.custom
    
    newVC.modalPresentationCapturesStatusBarAppearance = true //optional
    
    present(newVC, animated: true, completion: nil)
    
  3. Überschreiben Sie nun die presentationController-Methode Ihres UIViewControllerTransitioningDelegate und geben Sie Ihren benutzerdefinierten UIPresentationController zurück. Ich hatte meine als Erweiterung zu meiner aktuellen Klasse:

    extension CurrentViewController: UIViewControllerTransitioningDelegate {
    
    //this is where you implement animationController(forPresented) and animationController(forDismissed) methods
    
    func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
    
    return ModalPresentationController(presentedViewController: presented, presenting: source)
    
    }
    }
    

Eine andere Sache, die Sie beachten sollten, ist, dass Sie nicht versuchen sollten, Ihr fromView in Ihrer presentAnimator-Klasse zu referenzieren. Dies ist null und Sie erhalten zur Laufzeit eine Fehlermeldung. Wenn Sie Dinge wie Dinge implementieren, erhalten Sie einen benutzerdefinierten Übergang mit seiner Animation und einem halbtransparenten Hintergrund, falls Sie einen erstellen.

1
gwinyai