Membocorkan tampilan saat mengubah rootViewController di dalam transisiWithView


97

Saat menyelidiki kebocoran memori, saya menemukan masalah yang berkaitan dengan teknik panggilan setRootViewController:di dalam blok animasi transisi:

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

Jika pengontrol tampilan lama (yang sedang diganti) saat ini menampilkan pengontrol tampilan lain, kode di atas tidak menghapus tampilan yang disajikan dari hierarki tampilan.

Artinya, urutan operasi ini ...

  1. X menjadi Root View Controller
  2. X menampilkan Y, sehingga tampilan Y ada di layar
  3. Menggunakan transitionWithView:untuk menjadikan Z sebagai Pengontrol Tampilan Root baru

... tampak OK bagi pengguna, tetapi alat Debug View Hierarchy akan mengungkapkan bahwa tampilan Y masih ada di belakang tampilan Z, di dalam a UITransitionView. Artinya, setelah tiga langkah di atas, hierarki tampilan adalah:

  • UIWindow
    • UITransitionView
      • UIView (tampilan Y)
    • UIView (tampilan Z)

Saya menduga ini menjadi masalah karena, pada saat transisi, tampilan X sebenarnya bukan bagian dari hierarki tampilan.

Jika saya mengirim dismissViewControllerAnimated:NOke X segera sebelumnya transitionWithView:, hierarki tampilan yang dihasilkan adalah:

  • UIWindow
    • UIView (tampilan X)
    • UIView (tampilan Z)

Jika saya mengirim dismissViewControllerAnimated:(YA atau TIDAK) ke X, lalu melakukan transisi di completion:blok, maka hierarki tampilan sudah benar. Sayangnya, itu mengganggu animasi. Jika menganimasikan pemecatan, itu membuang-buang waktu; jika tidak dianimasikan, akan terlihat rusak.

Saya mencoba beberapa pendekatan lain (misalnya, membuat kelas pengontrol tampilan penampung baru untuk berfungsi sebagai pengontrol tampilan root saya) tetapi belum menemukan apa pun yang berfungsi. Saya akan memperbarui pertanyaan ini saat saya melanjutkan.

Tujuan utamanya adalah untuk beralih dari tampilan yang disajikan ke pengontrol tampilan root baru secara langsung, dan tanpa meninggalkan hierarki tampilan yang menyimpang.


Saya memiliki masalah yang sama saat ini
Alex

Saya baru saja menghadapi masalah yang sama
Jamal Zafar

Beruntung menemukan solusi yang layak untuk ini? Masalah yang sama TEPATNYA di sini.
David Baez

@DavidBaez Saya akhirnya menulis kode untuk secara agresif menutup semua pengontrol tampilan sebelum mengubah root. Ini sangat spesifik untuk aplikasi saya. Sejak memposting ini saya bertanya-tanya apakah menukar UIWindowadalah hal yang harus dilakukan, tetapi belum punya waktu untuk bereksperimen banyak.
benzado

Jawaban:


119

Saya mengalami masalah serupa baru-baru ini. Saya harus menghapusnya secara manual UITransitionViewdari jendela untuk memperbaiki masalah, lalu memanggil tutup pada pengontrol tampilan root sebelumnya untuk memastikan dialokasikan.

Perbaikannya tidak terlalu bagus tetapi kecuali Anda telah menemukan cara yang lebih baik sejak memposting pertanyaan, itu satu-satunya hal yang saya temukan untuk berfungsi! viewControllerhanya newControllerdari pertanyaan awal Anda.

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

Saya harap ini membantu Anda memperbaiki masalah Anda juga, itu benar-benar menyebalkan!

Swift 3.0.0

(Lihat riwayat edit untuk versi Swift lainnya)

Untuk implementasi yang lebih baik sebagai ekstensi yang UIWindowmengizinkan transisi opsional untuk diteruskan.

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()
            }
        }
    }
}

Pemakaian:

window.set(rootViewController: viewController)

Atau

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

6
Terima kasih. Berhasil. Silakan berbagi jika Anda menemukan pendekatan yang lebih baik
Jamal Zafar

8
Tampaknya mengganti pengontrol tampilan root yang telah menyajikan tampilan (atau mencoba untuk membatalkan alokasi UIWindow yang masih menampilkan pengontrol tampilan) akan mengakibatkan kebocoran memori. Tampaknya bagi saya bahwa menghadirkan pengontrol tampilan menciptakan loop penahan dengan jendela, dan menutup pengontrol adalah satu-satunya cara yang saya temukan untuk memecahkannya. Saya pikir beberapa blok penyelesaian internal memiliki referensi yang kuat ke jendela.
Carl Lindberg

Mengalami masalah dengan NSClassFromString ("UITransitionView") setelah mengonversi ke swift 2.0
Eugene Braginets

Masih terjadi di iOS 9 juga :( Juga saya telah memperbarui untuk Swift 2.0
Kaya

1
@ user023 Saya telah menggunakan solusi yang tepat ini di 2 atau 3 aplikasi yang dikirimkan ke App Store tanpa masalah! Saya kira karena Anda hanya memeriksa jenis kelas terhadap string, tidak masalah (bisa berupa string apa saja). Apa yang mungkin menyebabkan penolakan adalah memiliki kelas yang dinamai UITransitionViewdi aplikasi Anda seperti yang diambil sebagai bagian dari simbol aplikasi yang menurut saya akan digunakan App Store untuk memeriksa.
Kaya

5

Saya menghadapi masalah ini dan itu mengganggu saya sepanjang hari. Saya sudah mencoba solusi obj-c @ Rich dan ternyata ketika saya ingin menyajikan viewController lain setelah itu, saya akan diblokir dengan UITransitionView kosong.

Akhirnya, saya menemukan cara ini dan itu berhasil untuk saya.

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

Baiklah, sekarang yang harus Anda lakukan adalah menelepon [self setRootViewController:newViewController];saat Anda ingin beralih pengontrol tampilan root.


Berfungsi dengan baik, tetapi ada flash yang mengganggu dari pengontrol tampilan presentasi tepat sebelum pengontrol tampilan root diaktifkan. Menganimasikan dismissViewControllerAnimated:tampilan mungkin sedikit lebih baik daripada tidak ada animasi. Apakah menghindari hantu UITransitionViewdalam hierarki tampilan.
pkamb

5

Saya mencoba hal sederhana yang bekerja untuk saya di iOs 9.3: hapus saja tampilan viewController lama dari hierarki selama dismissViewControllerAnimatedpenyelesaian.

Mari kita kerjakan tampilan X, Y, dan Z seperti yang dijelaskan oleh benzado :

Artinya, urutan operasi ini ...

  1. X menjadi Root View Controller
  2. X menampilkan Y, sehingga tampilan Y ada di layar
  3. Menggunakan transisiWithView: untuk menjadikan Z sebagai Pengontrol Tampilan Root baru

Yang memberi:

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

Dalam kasus saya, X dan Y adalah dealloc yang baik dan pandangan mereka tidak lagi dalam hierarki!


0

Punya masalah serupa. Dalam kasus saya, saya memiliki hierarki viewController, dan salah satu pengontrol tampilan anak memiliki pengontrol tampilan yang disajikan. Ketika saya mengubah pengontrol tampilan root windows, untuk beberapa alasan, pengontrol tampilan yang disajikan masih ada di memori. Jadi, solusinya adalah menutup semua pengontrol tampilan sebelum saya mengubah pengontrol tampilan root windows.


-2

Saya sampai pada masalah ini saat menggunakan kode ini:

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

    })
}

Menonaktifkan kode ini, memperbaiki masalah. Saya berhasil membuat ini berfungsi dengan hanya mengaktifkan animasi transisi ini ketika filterbar yang dianimasikan diinisialisasi.

Ini sebenarnya bukan jawaban yang Anda cari, tetapi dapat membawa Anda ke landasan yang tepat untuk menemukan solusi Anda.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.