TransitionWithView içinde rootViewController değiştirilirken görünüm sızdırıyor


97

Bir bellek sızıntısını araştırırken, setRootViewController:bir geçiş animasyonu bloğunun içinde arama tekniğiyle ilgili bir sorun keşfettim :

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

Eski görünüm denetleyicisi (değiştirilen) şu anda başka bir görünüm denetleyicisini sunuyorsa, yukarıdaki kod sunulan görünümü görünüm hiyerarşisinden kaldırmaz.

Yani, bu işlemler dizisi ...

  1. X, Kök Görünüm Denetleyicisi olur
  2. X, Y'yi gösterir, böylece Y'nin görünümü ekranda olur
  3. transitionWithView:Z'yi yeni Kök Görünüm Denetleyicisi yapmak için kullanma

... kullanıcıya iyi görünüyor, ancak Hata Ayıklama Görünümü Hiyerarşisi aracı, Y'nin görünümünün hala Z'nin görünümünün arkasında, a UITransitionView. Yani, yukarıdaki üç adımdan sonra görünüm hiyerarşisi şöyledir:

  • UIWindow
    • UITransitionView
      • UIView (Y'nin görünümü)
    • UIView (Z'nin görünümü)

Bunun bir sorun olduğundan şüpheleniyorum çünkü geçiş sırasında X'in görüşü aslında görünüm hiyerarşisinin bir parçası değil.

dismissViewControllerAnimated:NOHemen önce X'e gönderirsem transitionWithView:, ortaya çıkan görünüm hiyerarşisi şu şekilde olur:

  • UIWindow
    • UIView (X'in görünümü)
    • UIView (Z'nin görünümü)

dismissViewControllerAnimated:X'e (EVET veya HAYIR) gönderirsem , completion:blokta geçişi gerçekleştirirsem , görünüm hiyerarşisi doğrudur. Ne yazık ki, bu animasyona engel oluyor. İşten çıkarmayı canlandırırsanız, zaman kaybeder; animasyon yapmıyorsa, bozuk görünüyor.

Başka yaklaşımlar deniyorum (örneğin, kök görünüm denetleyicim olarak hizmet etmesi için yeni bir kapsayıcı görünümü denetleyici sınıfı yapmak) ancak işe yarayan hiçbir şey bulamadım. Bu soruyu ilerledikçe güncelleyeceğim.

Nihai amaç, sunulan görünümden yeni bir kök görünüm denetleyicisine doğrudan ve etrafta başıboş görünüm hiyerarşileri bırakmadan geçiş yapmaktır.


Şu anda aynı sorunu yaşıyorum
Alex

Az önce aynı sorunla karşılaştım
Jamal Zafar

Buna uygun bir çözüm bulma şansınız var mı? Aynı TAM problem burada.
David Baez

@DavidBaez Kökü değiştirmeden önce tüm görüntüleme denetleyicilerini agresif bir şekilde reddetmek için kod yazdım. Yine de benim uygulamama çok özel. Bunu yayınladığından beri, değiş tokuş UIWindowyapmanın yapılacak şey olup olmadığını merak ediyorum , ancak çok fazla deneyecek vaktim olmadı.
benzado 01

Yanıtlar:


119

Geçenlerde benzer bir sorun yaşadım. UITransitionViewSorunu çözmek için bunu pencereden manuel olarak kaldırmak zorunda kaldım , ardından ayrılmasının kaldırılmasını sağlamak için önceki kök görünüm denetleyicisinde dismiss çağrısı yaptım .

Düzeltme gerçekten çok hoş değil, ancak soruyu gönderdikten sonra daha iyi bir yol bulmadıysanız, işe yaradığını bulduğum tek şey bu! viewControllersadece newControllerorijinal sorunuzdan.

UIViewController *previousRootViewController = self.window.rootViewController;

self.window.rootViewController = viewController;

// Nasty hack to fix http://stackoverflow.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];
}];

Umarım bu da sorununuzu çözmenize yardımcı olur, tam bir baş belasıdır!

Swift 3.0

(Diğer Swift sürümleri için düzenleme geçmişine bakın)

UIWindowİsteğe bağlı bir geçişin geçmesine izin verme konusunda bir uzantı olarak daha hoş bir uygulama için.

extension UIWindow {

    /// Fix for http://stackoverflow.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()
        }

        if #available(iOS 13.0, *) {
            // In iOS 13 we don't want to remove the transition view as it'll create a blank screen
        } else {
            // 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()
            }
        }
    }
}

Kullanım:

window.set(rootViewController: viewController)

Veya

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

6
Teşekkürler. İşe yaradı. Daha iyi bir yaklaşım bulursanız lütfen paylaşın
Jamal Zafar

8
Görünüşe göre görünümler sunan bir kök görünüm denetleyicisini değiştirmek (veya hala görünüm denetleyicileri olan bir UIWindow'u serbest bırakmaya çalışmak) bellek sızıntısına neden olacaktır. Bana öyle geliyor ki, bir görünüm denetleyicisi sunmak, pencereyle birlikte bir tutma döngüsü oluşturuyor ve denetleyicileri devre dışı bırakmak, onu kırmak için bulduğum tek yol. Bazı dahili tamamlama bloklarının pencereye güçlü bir referansı olduğunu düşünüyorum.
Carl Lindberg

Hızlı 2.0'a dönüştürüldükten sonra NSClassFromString ("UITransitionView") ile ilgili bir sorun oluştu
Eugene Braginets

Hala iOS 9'da da oluyor :( Ayrıca Swift 2.0
Rich

1
@ user023 Bu tam çözümü, App Store'a gönderilen 2 veya 3 uygulamada sorunsuz bir şekilde kullandım! Sanırım sadece sınıfın türünü bir dizeye göre kontrol ettiğiniz için sorun değil (herhangi bir dize olabilir). Reddedilmeye neden olabilecek şey UITransitionView, uygulamanızda adlandırılmış bir sınıfa sahip olmaktır; bu, App Store'un kontrol etmek için kullandığını düşündüğüm uygulamanın sembollerinin bir parçası olarak alınır.
Zengin

5

Bu sorunla karşılaştım ve bütün gün beni kızdırdı. @ Rich'in obj-c çözümünü denedim ve bundan sonra başka bir viewController sunmak istediğimde boş bir UITransitionView ile engelleneceğim.

Sonunda bu şekilde anladım ve benim için işe yaradı.

- (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];
}

Pekala, şimdi yapmanız gereken tek şey [self setRootViewController:newViewController];kök görünüm denetleyicisini değiştirmek istediğinizde aramak .


İyi çalışıyor, ancak kök görünüm denetleyicisi açılmadan hemen önce sunum görünümü denetleyicisinin can sıkıcı bir flaşı var. Görünümleri canlandırmak dismissViewControllerAnimated:belki de hiç animasyon olmamasından biraz daha iyi. UITransitionViewYine de görünüm hiyerarşisindeki hayaletlerden kaçınıyor .
pkamb

5

İOs 9.3'te benim için çalışan basit bir şeyi deniyorum: dismissViewControllerAnimatedtamamlanma sırasında eski viewController görünümünü hiyerarşisinden kaldırın .

Benzado'nun açıkladığı gibi X, Y ve Z görünümü üzerinde çalışalım :

Yani, bu işlemler dizisi ...

  1. X, Kök Görünüm Denetleyicisi olur
  2. X, Y'yi gösterir, böylece Y'nin görünümü ekranda olur
  3. GeçişWithView kullanma: Z'yi yeni Kök Görünüm Denetleyicisi yapmak için

Hangi verir:

////
//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)

Benim durumumda, X ve Y iyi ayrıştırılmış ve onların görüşleri artık hiyerarşi içinde değil!


0

Benzer bir sorun vardı. Benim durumumda bir viewController hiyerarşim vardı ve çocuk görünüm denetleyicilerinden birinin sunulan bir görünüm denetleyicisi vardı. Daha sonra Windows kök görünüm denetleyicisini değiştirdiğimde, bir nedenden ötürü, sunulan görünüm denetleyicisi hala bellekteydi. Çözüm, Windows kök görünüm denetleyicisini değiştirmeden önce tüm görünüm denetleyicilerini kaldırmaktı.


-2

Bu kodu kullanırken bu konuya geldim:

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

    })
}

Bu kodu devre dışı bırakmak sorunu çözdü. Bu geçiş animasyonunu sadece canlandırılan filtre çubuğu başlatıldığında etkinleştirerek çalıştırmayı başardım.

Aslında aradığınız cevap bu değil, ancak çözümünüzü bulmak için sizi doğru pede getirebilir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.