ios files - Fuite de vues lors de la modification de rootViewController à l'intérieur de transitionWithView




app icloud (5)

J'ai fait face à ce problème et cela m'a ennuyé pendant toute une journée. J'ai essayé la solution obj-c de @ Rich et il s'avère que quand je veux présenter un autre viewController après cela, je serai bloqué avec un UITransitionView vide.

Finalement, j'ai compris de cette façon et cela a fonctionné pour moi.

- (void)setRootViewController:(UIViewController *)rootViewController {
    // dismiss presented view controllers before switch rootViewController to avoid messed up view hierarchy, or even crash
    UIViewController *presentedViewController = [self findPresentedViewControllerStartingFrom:self.window.rootViewController];
    [self dismissPresentedViewController:presentedViewController completionBlock:^{
        [self.window setRootViewController:rootViewController];
    }];
}

- (void)dismissPresentedViewController:(UIViewController *)vc completionBlock:(void(^)())completionBlock {
    // if vc is presented by other view controller, dismiss it.
    if ([vc presentingViewController]) {
        __block UIViewController* nextVC = vc.presentingViewController;
        [vc dismissViewControllerAnimated:NO completion:^ {
            // if the view controller which is presenting vc is also presented by other view controller, dismiss it
            if ([nextVC presentingViewController]) {
                [self dismissPresentedViewController:nextVC completionBlock:completionBlock];
            } else {
                if (completionBlock != nil) {
                    completionBlock();
                }
            }
        }];
    } else {
        if (completionBlock != nil) {
            completionBlock();
        }
    }
}

+ (UIViewController *)findPresentedViewControllerStartingFrom:(UIViewController *)start {
    if ([start isKindOfClass:[UINavigationController class]]) {
        return [self findPresentedViewControllerStartingFrom:[(UINavigationController *)start topViewController]];
    }

    if ([start isKindOfClass:[UITabBarController class]]) {
        return [self findPresentedViewControllerStartingFrom:[(UITabBarController *)start selectedViewController]];
    }

    if (start.presentedViewController == nil || start.presentedViewController.isBeingDismissed) {
        return start;
    }

    return [self findPresentedViewControllerStartingFrom:start.presentedViewController];
}

Très bien, maintenant tout ce que vous avez à faire est d'appeler [self setRootViewController:newViewController]; lorsque vous voulez changer le contrôleur de vue racine.

En examinant une fuite de mémoire, j'ai découvert un problème lié à la technique d'appel de setRootViewController: intérieur d'un bloc d'animation de transition:

[UIView transitionWithView:self.window
                  duration:0.5
                   options:UIViewAnimationOptionTransitionFlipFromLeft
                animations:^{ self.window.rootViewController = newController; }
                completion:nil];

Si l'ancien contrôleur de vue (celui qui est remplacé) présente actuellement un autre contrôleur de vue, le code ci-dessus ne supprime pas la vue présentée de la hiérarchie de vues.

Autrement dit, cette séquence d'opérations ...

  1. X devient le contrôleur de vue racine
  2. X présente Y, de sorte que la vue de Y est à l'écran
  3. Utilisation de transitionWithView: pour faire de Z le nouveau contrôleur de vue racine

... semble OK pour l'utilisateur, mais l'outil Debug View Hierarchy révèlera que la vue de Y est toujours derrière la vue de Z, à l'intérieur d'un UITransitionView . C'est-à-dire, après les trois étapes ci-dessus, la hiérarchie de vue est:

  • UIWindow
    • UITransitionView
      • UIView (vue de Y)
    • UIView (vue de Z)

Je pense que c'est un problème parce que, au moment de la transition, la vue de X ne fait pas partie de la hiérarchie des vues.

Si j'envoie dismissViewControllerAnimated:NO à X immédiatement avant transitionWithView: dismissViewControllerAnimated:NO la hiérarchie d'affichage résultante est:

  • UIWindow
    • UIView (vue de X)
    • UIView (vue de Z)

Si j'envoie dismissViewControllerAnimated: (YES ou NO) à X, alors effectuez la transition dans le bloc completion: alors la hiérarchie de vue est correcte. Malheureusement, cela interfère avec l'animation. Si animant le renvoi, il perd du temps; Si ce n'est pas animé, ça a l'air cassé.

J'essaie d'autres approches (par exemple, faire une nouvelle classe de contrôleur de vue de conteneur pour servir de contrôleur de vue racine) mais je n'ai rien trouvé qui fonctionne. Je vais mettre à jour cette question au fur et à mesure.

Le but ultime est de passer directement de la vue présentée à un nouveau contrôleur de vue racine, sans laisser de hiérarchies de vues parasites.


J'essaie une chose simple qui fonctionne pour moi sur iOs 9.3: il suffit de supprimer la vue de l'ancien viewController de sa hiérarchie lors de l'achèvement de la commande dismissViewControllerAnimated .

Travaillons sur les vues X, Y et Z comme expliqué par :

Autrement dit, cette séquence d'opérations ...

  1. X devient le contrôleur de vue racine
  2. X présente Y, de sorte que la vue de Y est à l'écran
  3. Utilisation de transitionWithView: pour faire de Z le nouveau contrôleur de vue racine

Qui donnent :

////
//Start point :

let X = UIViewController ()
let Y = UIViewController ()
let Z = UIViewController ()

window.rootViewController = X
X.presentViewController (Y, animated:true, completion: nil)

////
//Transition :

UIView.transitionWithView(window,
                          duration: 0.25,
                          options: UIViewAnimationOptions.TransitionFlipFromRight,
                          animations: { () -> Void in
                                X.dismissViewControllerAnimated(false, completion: {
                                        X.view.removeFromSuperview()
                                    })
                                window.rootViewController = Z
                           },
                           completion: nil)

Dans mon cas, X et Y sont bien dealloc et leur vue n'est plus dans la hiérarchie!


Je suis arrivé à ce problème en utilisant ce code:

if var tc = self.transitionCoordinator() {

    var animation = tc.animateAlongsideTransitionInView((self.navigationController as VDLNavigationController).filtersVCContainerView, animation: { (context:UIViewControllerTransitionCoordinatorContext!) -> Void in
        var toVC = tc.viewControllerForKey(UITransitionContextToViewControllerKey) as BaseViewController
        (self.navigationController as VDLNavigationController).setFilterBarHiddenWithInteractivity(!toVC.filterable(), animated: true, interactive: true)
    }, completion: { (context:UIViewControllerTransitionCoordinatorContext!) -> Void in

    })
}

Désactiver ce code, a résolu le problème. J'ai réussi à faire fonctionner ceci en activant seulement cette animation de transition quand la barre de filtre qui s'anime est initialisée.

Ce n'est pas vraiment la réponse que vous cherchez, mais cela pourrait vous apporter le bon tampon pour trouver votre solution.


J'ai eu un problème similaire récemment. J'ai dû supprimer manuellement ce UITransitionView de la fenêtre pour résoudre le problème, puis appeler ignorer sur le contrôleur de vue racine précédent pour assurer son désalloué.

Le correctif n'est pas vraiment très sympa mais à moins que vous ayez trouvé une meilleure solution depuis la publication de la question, c'est la seule chose que j'ai trouvée pour fonctionner! viewController est juste le nouveau newController de votre question initiale.

UIViewController *previousRootViewController = self.window.rootViewController;

self.window.rootViewController = viewController;

// Nasty hack to fix http://.com/questions/26763020/leaking-views-when-changing-rootviewcontroller-inside-transitionwithview
// The presenting view controllers view doesn't get removed from the window as its currently transistioning and presenting a view controller
for (UIView *subview in self.window.subviews) {
    if ([subview isKindOfClass:NSClassFromString(@"UITransitionView")]) {
        [subview removeFromSuperview];
    }
}
// Allow the view controller to be deallocated
[previousRootViewController dismissViewControllerAnimated:NO completion:^{
    // Remove the root view in case its still showing
    [previousRootViewController.view removeFromSuperview];
}];

J'espère que cela vous aidera à résoudre votre problème aussi, c'est une douleur absolue dans le cul!

Swift 3.0

(Voir l'historique des modifications pour les autres versions de Swift)

Pour une implémentation plus agréable en tant qu'extension sur UIWindow permettant une transition facultative à passer.

extension UIWindow {

    /// Fix for http://.com/a/27153956/849645
    func set(rootViewController newRootViewController: UIViewController, withTransition transition: CATransition? = nil) {

        let previousViewController = rootViewController

        if let transition = transition {
            // Add the transition
            layer.add(transition, forKey: kCATransition)
        }

        rootViewController = newRootViewController

        // Update status bar appearance using the new view controllers appearance - animate if needed
        if UIView.areAnimationsEnabled {
            UIView.animate(withDuration: CATransaction.animationDuration()) {
                newRootViewController.setNeedsStatusBarAppearanceUpdate()
            }
        } else {
            newRootViewController.setNeedsStatusBarAppearanceUpdate()
        }

        /// The presenting view controllers view doesn't get removed from the window as its currently transistioning and presenting a view controller
        if let transitionViewClass = NSClassFromString("UITransitionView") {
            for subview in subviews where subview.isKind(of: transitionViewClass) {
                subview.removeFromSuperview()
            }
        }
        if let previousViewController = previousViewController {
            // Allow the view controller to be deallocated
            previousViewController.dismiss(animated: false) {
                // Remove the root view in case its still showing
                previousViewController.view.removeFromSuperview()
            }
        }
    }
}

Usage:

window.set(rootViewController: viewController)

Ou

let transition = CATransition()
transition.type = kCATransitionFade
window.set(rootViewController: viewController, withTransition: transition)

La mise en cache des images peut être réalisée aussi simplement que cela.

ImageService.m

@implementation ImageService{
    NSCache * Cache;
}

const NSString * imageCacheKeyPrefix = @"Image-";

-(id) init {
    self = [super init];
    if(self) {
        Cache = [[NSCache alloc] init];
    }
    return self;
}

/** 
 * Get Image from cache first and if not then get from server
 * 
 **/

- (void) getImage: (NSString *) key
        imagePath: (NSString *) imagePath
       completion: (void (^)(UIImage * image)) handler
    {
    UIImage * image = [Cache objectForKey: key];

    if( ! image || imagePath == nil || ! [imagePath length])
    {
        image = NOIMAGE;  // Macro (UIImage*) for no image 

        [Cache setObject:image forKey: key];

        dispatch_async(dispatch_get_main_queue(), ^(void){
            handler(image);
        });
    }
    else
    {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0ul ),^(void){

            UIImage * image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[imagePath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]]];

            if( !image)
            {
                image = NOIMAGE;
            }

            [Cache setObject:image forKey: key];

            dispatch_async(dispatch_get_main_queue(), ^(void){
                handler(image);
            });
        });
    }
}


- (void) getUserImage: (NSString *) userId
           completion: (void (^)(UIImage * image)) handler
{
    [self getImage: [NSString stringWithFormat: @"%@user-%@", imageCacheKeyPrefix, userId]
         imagePath: [NSString stringWithFormat: @"http://graph.facebook.com/%@/picture?type=square", userId]
        completion: handler];
}

SomeViewController.m

    [imageService getUserImage: userId
                    completion: ^(UIImage *image) {
            annotationImage.image = image;
    }];




ios cocoa-touch uiviewcontroller core-animation