क्रमानुसार अंदर का दृश्य बदल रहा है


97

मेमोरी लीक की जांच करते समय मैंने setRootViewController:एक संक्रमण एनीमेशन ब्लॉक के अंदर कॉलिंग की तकनीक से संबंधित एक समस्या की खोज की :

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

यदि पुराना दृश्य नियंत्रक (जिसे प्रतिस्थापित किया जा रहा है) वर्तमान में एक अन्य दृश्य नियंत्रक प्रस्तुत कर रहा है, तो उपरोक्त कोड दृश्य पदानुक्रम से प्रस्तुत दृश्य को नहीं हटाता है।

यही है, संचालन का यह क्रम ...

  1. एक्स रूट व्यू कंट्रोलर बन जाता है
  2. X, Y को प्रस्तुत करता है, ताकि Y का दृश्य स्क्रीन पर हो
  3. transitionWithView:Z को नया रूट व्यू कंट्रोलर बनाने के लिए उपयोग करना

... उपयोगकर्ता के लिए ठीक लग रहा है, लेकिन डीबग व्यू पदानुक्रम उपकरण से पता चलेगा कि वाई का दृश्य अभी भी जेड के दृश्य के पीछे है, अंदर UITransitionView। अर्थात्, ऊपर के तीन चरणों के बाद, दृश्य पदानुक्रम है:

  • UIWindow
    • UITransitionView
      • UIView (Y का दृश्य)
    • उविवि (जेड का विचार)

मुझे संदेह है कि यह एक समस्या है क्योंकि, संक्रमण के समय, X का दृश्य वास्तव में दृश्य पदानुक्रम का हिस्सा नहीं है।

अगर मैं dismissViewControllerAnimated:NOपहले एक्स को भेजता हूं transitionWithView:, तो परिणामी दृश्य पदानुक्रम है:

  • UIWindow
    • UIView (X का विचार)
    • उविवि (जेड का विचार)

यदि मैं dismissViewControllerAnimated:X को YES (NO या NO) भेजता हूं , तो completion:ब्लॉक में संक्रमण का प्रदर्शन करता हूं , तो दृश्य पदानुक्रम सही है। दुर्भाग्य से, यह एनीमेशन में हस्तक्षेप करता है। यदि बर्खास्तगी को एनिमेट करते हुए, यह समय बर्बाद करता है; यदि एनिमेशन नहीं है, तो यह टूटा हुआ दिखता है।

मैं कुछ अन्य तरीकों की कोशिश कर रहा हूं (जैसे, मेरे रूट व्यू कंट्रोलर के रूप में सेवा करने के लिए एक नया कंटेनर व्यू कंट्रोलर क्लास बना रहा हूं) लेकिन ऐसा कुछ भी नहीं मिला जो काम करता हो। जैसे ही मैं जाऊंगा मैं इस सवाल को अपडेट कर दूंगा।

अंतिम लक्ष्य प्रस्तुत दृश्य से सीधे एक नए रूट व्यू कंट्रोलर से संक्रमण करना है, और चारों ओर आवारा दृश्य पदानुक्रम को छोड़कर।


मेरे पास वर्तमान में यही मुद्दा है
एलेक्स

मुझे बस एक ही मुद्दे का सामना करना पड़ा
जमाल ज़फर

किसी भी किस्मत यह एक अच्छा समाधान खोजने? एक ही समस्या यहाँ पर।
डेविड बैज

@DavidBaez मैं रूट बदलने से पहले सभी व्यू कंट्रोलरों को आक्रामक रूप से खारिज करने के लिए कोड लिखना घाव करता हूं। यह मेरे ऐप के लिए बहुत विशिष्ट है, हालांकि। इसे पोस्ट करने के बाद से मैं सोच रहा था कि अगर स्वैप करना UIWindowहै तो क्या करना है, लेकिन ज्यादा प्रयोग करने का समय नहीं है।
बेन्ज़ादो

जवाबों:


119

मेरे पास हाल ही में इसी तरह का एक मुद्दा था। मुझे UITransitionViewइस समस्या को ठीक करने के लिए खिड़की से मैन्युअल रूप से निकालना पड़ा , फिर अपने डीलॉक्लेट को सुनिश्चित करने के लिए पिछले रूट व्यू कंट्रोलर पर खारिज करें।

फिक्स वास्तव में बहुत अच्छा नहीं है, लेकिन जब तक आप सवाल पोस्ट करने के बाद से बेहतर तरीका नहीं ढूंढते हैं, तब तक केवल एक चीज जो मुझे काम करने के लिए मिली है! viewControllerबस newControllerअपने मूल प्रश्न से है।

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

मुझे आशा है कि यह आपकी समस्या को भी ठीक करने में मदद करेगा, यह गधे में एक पूर्ण दर्द है!

स्विफ्ट 3.0

(अन्य स्विफ्ट संस्करणों के लिए संपादित इतिहास देखें)

UIWindowवैकल्पिक संक्रमण को पारित करने की अनुमति देने पर एक विस्तार के रूप में एक अच्छे कार्यान्वयन के लिए।

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

उपयोग:

window.set(rootViewController: viewController)

या

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

6
धन्यवाद। इसने काम कर दिया। कृपया साझा करें यदि आप एक बेहतर दृष्टिकोण पाते हैं
जमाल ज़फर

8
ऐसा प्रतीत होता है कि एक रूट व्यू कंट्रोलर की जगह जिसने विचार प्रस्तुत किए हैं (या एक UIWindow जो अभी भी व्यू कंट्रोलर प्रस्तुत कर रहा है) को हटाने की कोशिश करने के परिणामस्वरूप मेमोरी लीक हो जाएगी। यह मुझे लगता है कि एक दृश्य नियंत्रक पेश करना खिड़की के साथ एक लूप बनाए रखता है, और नियंत्रकों को खारिज करना एकमात्र तरीका है जिसे मैंने इसे तोड़ने के लिए पाया है। मुझे लगता है कि कुछ आंतरिक पूरा होने वाले ब्लॉकों में खिड़की का एक मजबूत संदर्भ है।
कार्ल लिंडबर्ग

NSClassFromString ("UITransitionView") के साथ एक मुद्दा था तेजी से 2.0 में परिवर्तित होने के बाद
यूजीन ब्रैगनेट्स

अभी भी iOS 9 में हो रहा है :( साथ ही मैंने Swift 2.0 के लिए भी अपडेट किया है
Rich

1
@ user023 मैंने 2 या 3 ऐप्स में इस सटीक समाधान का उपयोग एक समस्या के बिना ऐप स्टोर में प्रस्तुत किया है! मुझे लगता है कि आप केवल एक स्ट्रिंग के खिलाफ कक्षा के प्रकार की जांच कर रहे हैं यह ठीक है (यह किसी भी स्ट्रिंग हो सकता है)। क्या कारण हो सकता है कि एक अस्वीकृति UITransitionViewआपके ऐप में एक वर्ग का नाम हो , जो कि ऐप के प्रतीकों के हिस्से के रूप में उठाया गया हो, जो मुझे लगता है कि ऐप स्टोर चेक करने के लिए उपयोग करता है।
अमीर

5

मैंने इस मुद्दे का सामना किया और इसने मुझे पूरे दिन के लिए परेशान किया। मैंने @ रिच के obj-c समाधान की कोशिश की है और यह पता चला है कि जब मैं उसके बाद एक और viewController प्रस्तुत करना चाहता हूं, तो मैं एक खाली UITransitionView के साथ अवरुद्ध हो जाऊंगा।

अंत में, मैं इस तरह से समझ गया और इसने मेरे लिए काम किया।

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

ठीक है, अब आपको केवल [self setRootViewController:newViewController];तब कॉल करना है जब आप रूट व्यू कंट्रोलर को स्विच करना चाहते हैं।


अच्छी तरह से काम करता है, लेकिन रूट व्यू कंट्रोलर को स्विच करने से ठीक पहले प्रेजेंटिंग व्यू कंट्रोलर का एक कष्टप्रद फ्लैश होता है। dismissViewControllerAnimated:लुक्स को एनिमेटेड करना शायद बिना किसी एनीमेशन के थोड़ा बेहतर है। UITransitionViewहालांकि भूत पदानुक्रम में भूत एस से बचें ।
पीकेएम

5

मैं एक सरल बात की कोशिश करता हूं जो मेरे लिए iOs 9.3 पर काम करता है: बस dismissViewControllerAnimatedपूरा होने के दौरान अपने पदानुक्रम से पुराने दृश्य-नियंत्रक के दृष्टिकोण को हटा दें ।

बेन्ज़ैडो द्वारा समझाया गया एक्स, वाई और जेड दृश्य पर काम करते हैं :

यही है, संचालन का यह क्रम ...

  1. एक्स रूट व्यू कंट्रोलर बन जाता है
  2. X, Y को प्रस्तुत करता है, ताकि Y का दृश्य स्क्रीन पर हो
  3. TransWithView का उपयोग करना: Z को नया रूट व्यू कंट्रोलर बनाना

जो देते हैं:

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

मेरे मामले में, X और Y अच्छी तरह से निपट चुके हैं और उनके विचार पदानुक्रम में अधिक नहीं हैं!


0

एक समान मुद्दा था। मेरे मामले में मेरे पास एक दृश्य नियंत्रक पदानुक्रम था, और एक बच्चे के दृश्य नियंत्रकों में एक प्रस्तुत दृश्य नियंत्रक था। जब मैं बदल गया तब किसी कारण से, विंडोज़ रूट व्यू कंट्रोलर, प्रस्तुत दृश्य नियंत्रक अभी भी मेमोरी में था। इसलिए, मैं विंडो रूट व्यू कंट्रोलर को बदलने से पहले सभी व्यू कंट्रोलर्स को हल करता था।


-2

मैं इस कोड का उपयोग करते समय इस मुद्दे पर आया था:

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

    })
}

इस कोड को अक्षम करने से समस्या ठीक हो गई। मैं केवल इस संक्रमण एनीमेशन को सक्षम करके इस काम को प्राप्त करने में कामयाब रहा जब फ़िल्टरर एनिमेटेड जो आरंभ होता है।

यह वास्तव में वह उत्तर नहीं है जिसकी आप तलाश कर रहे हैं, बल्कि यह आपके समाधान खोजने के लिए आपको सही पैड पर ला सकता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.