PopViewController के लिए समापन ब्लॉक


114

उपयोग करते समय एक मोडल व्यू कंट्रोलर को खारिज करते हुए dismissViewController, पूर्ण ब्लॉक प्रदान करने का विकल्प होता है। क्या इसके लिए समान है popViewController?

पूरा होने का तर्क काफी आसान है। उदाहरण के लिए, मैं इसका उपयोग टेबलव्यू से एक पंक्ति को हटाने के लिए कर सकता हूं जब तक कि मोडल ऑफ स्क्रीन न हो, उपयोगकर्ता को पंक्ति एनीमेशन देखने देता है। धक्का दिए गए कंट्रोलर से लौटते समय मैं यही अवसर चाहूंगा।

मैंने popViewControllerएक UIViewएनीमेशन ब्लॉक में रखने की कोशिश की है , जहां मेरे पास एक पूर्ण ब्लॉक तक पहुंच है। हालाँकि, यह पॉप-अप होने वाले दृश्य पर कुछ अवांछित दुष्प्रभाव पैदा करता है।

यदि ऐसी कोई विधि उपलब्ध नहीं है, तो कुछ वर्कअराउंड क्या हैं?


stackoverflow.com/a/33767837/2774520 मुझे लगता है कि यह सबसे मूल एक है
ओलेक्सी नेझीबोरेट्स


3
2018 के लिए यह बहुत ही सरल और मानक है: stackoverflow.com/a/43017103/294884
Fattie

जवाबों:


200

मुझे पता है कि एक उत्तर दो साल पहले स्वीकार कर लिया गया है, हालांकि यह उत्तर अधूरा है।

ऐसा करने का कोई तरीका नहीं है जो आप आउट-ऑफ-द-बॉक्स चाहते हैं

यह तकनीकी रूप से सही है क्योंकि UINavigationControllerएपीआई इसके लिए कोई विकल्प नहीं देता है। हालाँकि CoreAnimation फ्रेमवर्क का उपयोग करके अंतर्निहित एनीमेशन में एक पूर्ण ब्लॉक जोड़ना संभव है:

[CATransaction begin];
[CATransaction setCompletionBlock:^{
    // handle completion here
}];

[self.navigationController popViewControllerAnimated:YES];

[CATransaction commit];

समाप्त ब्लॉक को एनीमेशन द्वारा popViewControllerAnimated:समाप्त होते ही कहा जाएगा । यह कार्यक्षमता iOS 4 के बाद से उपलब्ध है।


5
मैंने इसे Swift में UINavigationController के विस्तार में रखा:extension UINavigationController { func popViewControllerWithHandler(handler: ()->()) { CATransaction.begin() CATransaction.setCompletionBlock(handler) self.popViewControllerAnimated(true) CATransaction.commit() } }
Arbitur

1
मेरे लिए काम करने के लिए प्रतीत नहीं होता है, जब मैं बर्खास्तगी को पूरा करता हूं। ViewController पर, यह जो दृश्य पेश कर रहा था वह दृश्य पदानुक्रम का हिस्सा है। जब मैं कैटरशिप के साथ ऐसा करता हूं, तो मुझे एक चेतावनी मिलती है कि दृश्य पदानुक्रम का हिस्सा नहीं है।
moger777

1
यदि आप शुरुआत और पूर्णता ब्लॉक को उलटते हैं तो ठीक है, आपके कार्यों की तरह दिखता है। नीचे वोट के बारे में क्षमा करें, लेकिन स्टैक ओवरफ्लो मुझे बदलने नहीं देगा :(
moger777

7
हाँ, ऐसा लग रहा था कि यह भयानक होगा, लेकिन यह काम नहीं करता है (कम से कम iOS 8 पर)। पूरा होने वाले ब्लॉक को तुरंत बुलाया जा रहा है। यूआईवाईईवाई शैली के एनिमेशन के साथ मुख्य एनिमेशन के मिश्रण की वजह से।
21

5
यह काम नहीं करता है
दुराज़्नो

51

के लिए iOS9 स्विफ्ट संस्करण - (पहले के संस्करणों के लिए परीक्षण नहीं किया था) एक आकर्षण की तरह काम करता है। इस उत्तर के आधार पर

extension UINavigationController {    
    func pushViewController(viewController: UIViewController, animated: Bool, completion: () -> ()) {
        pushViewController(viewController, animated: animated)

        if let coordinator = transitionCoordinator() where animated {
            coordinator.animateAlongsideTransition(nil) { _ in
                completion()
            }
        } else {
            completion()
        }
    }

    func popViewController(animated: Bool, completion: () -> ()) {
        popViewControllerAnimated(animated)

        if let coordinator = transitionCoordinator() where animated {
            coordinator.animateAlongsideTransition(nil) { _ in
                completion()
            }
        } else {
            completion()
        }
    }
}

एनिमेटेड नहीं होने पर काम नहीं करेगा, इसे ठीक से करने के लिए अगले रनलूप पर पूरा करना चाहिए।
rshev

@ रिशेव अगले रनलूप पर क्यों?
बेन सिनक्लेयर

@ और जो मुझे इस पर प्रयोग करना याद है, उस बिंदु पर अभी तक कुछ प्रचारित नहीं किया गया था। इसके साथ प्रयोग करने की कोशिश करें, सुनने के लिए प्यार करें कि यह आपके लिए कैसे काम करता है।
rshev

@ अरशव मुझे लगता है कि मेरे पास पहले भी ऐसा ही था, मुझे दोहरी जांच करनी होगी। वर्तमान परीक्षण ठीक चलते हैं।
बेन सिंक्लेयर

1
@LanceSamaria मैं viewDidDisappear का उपयोग करने का सुझाव देता हूं। जाँच करें कि क्या नावबार उपलब्ध है, यदि नहीं - यह नावबार में नहीं दिखाया गया है, इसलिए यह पॉपअप किया गया था। अगर (self.navigationController == nil) {अपनी कार्रवाई को ट्रिगर करें}
HotJard

32

मैंने @JorisKluivers उत्तर के Swiftसाथ एक्सटेंशन के साथ एक संस्करण बनाया ।

यह एक पूरा होने के बंद होने के बाद एनीमेशन दोनों के लिए किया जाता है फोन करेगा pushऔर pop

extension UINavigationController {
    func popViewControllerWithHandler(completion: ()->()) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        self.popViewControllerAnimated(true)
        CATransaction.commit()
    }
    func pushViewController(viewController: UIViewController, completion: ()->()) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        self.pushViewController(viewController, animated: true)
        CATransaction.commit()
    }
}

मेरे लिए, iOS 8.4 में, ObjC में लिखा गया ब्लॉक एनीमेशन के आधे रास्ते में आग लगाता है। क्या यह वास्तव में सही क्षण में आग लगाता है यदि स्विफ्ट (8.4) में लिखा गया है?
जूलियन एफ वेइनर्ट

@Arbitur पूरा होने के ब्लॉक वास्तव में बुला के बाद कहा जाता है popViewControllerया pushViewController, लेकिन अगर आप की जाँच क्या topViewController सही बाद में है, तो आप इसे अब भी पुरानी है, जैसे देखेंगे popया pushकभी नहीं हुआ ...
Bogdan राज़्वान

@BogdanRazvan के बाद क्या? एनीमेशन पूरा होने के बाद क्या आपके पूर्ण समापन को बुलाया जाता है?
Arbitur

17

स्विफ्ट 4.1

extension UINavigationController {
func pushToViewController(_ viewController: UIViewController, animated:Bool = true, completion: @escaping ()->()) {
    CATransaction.begin()
    CATransaction.setCompletionBlock(completion)
    self.pushViewController(viewController, animated: animated)
    CATransaction.commit()
}

func popViewController(animated:Bool = true, completion: @escaping ()->()) {
    CATransaction.begin()
    CATransaction.setCompletionBlock(completion)
    self.popViewController(animated: animated)
    CATransaction.commit()
}

func popToViewController(_ viewController: UIViewController, animated:Bool = true, completion: @escaping ()->()) {
    CATransaction.begin()
    CATransaction.setCompletionBlock(completion)
    self.popToViewController(viewController, animated: animated)
    CATransaction.commit()
}

func popToRootViewController(animated:Bool = true, completion: @escaping ()->()) {
    CATransaction.begin()
    CATransaction.setCompletionBlock(completion)
    self.popToRootViewController(animated: animated)
    CATransaction.commit()
}
}

17

मेरी भी यही समस्या थी। और क्योंकि मुझे इसे कई अवसरों में उपयोग करना था, और पूरा होने वाले ब्लॉक की श्रृंखलाओं के भीतर, मैंने एक UINavigationController उपवर्ग में यह सामान्य समाधान बनाया:

- (void) navigationController:(UINavigationController *) navigationController didShowViewController:(UIViewController *) viewController animated:(BOOL) animated {
    if (_completion) {
        dispatch_async(dispatch_get_main_queue(),
        ^{
            _completion();
            _completion = nil;
         });
    }
}

- (UIViewController *) popViewControllerAnimated:(BOOL) animated completion:(void (^)()) completion {
    _completion = completion;
    return [super popViewControllerAnimated:animated];
}

यह मानते हुए

@interface NavigationController : UINavigationController <UINavigationControllerDelegate>

तथा

@implementation NavigationController {
    void (^_completion)();
}

तथा

- (id) initWithRootViewController:(UIViewController *) rootViewController {
    self = [super initWithRootViewController:rootViewController];
    if (self) {
        self.delegate = self;
    }
    return self;
}

1
मुझे वास्तव में यह समाधान पसंद है, मैं इसे एक श्रेणी और एक संबंधित वस्तु के साथ आज़माने जा रहा हूं।
23

@spstanley आपको इस पॉड को प्रकाशित करने की आवश्यकता है :)
k06a

स्विफ्ट संस्करण -> stackoverflow.com/a/60090678/4010725
K.

15

ऐसा करने का कोई तरीका नहीं है जो आप आउट-ऑफ-द-बॉक्स चाहते हैं। यानी नेवी स्टैक से व्यू कंट्रोलर को पॉप करने के लिए एक पूर्ण ब्लॉक के साथ कोई विधि नहीं है।

मैं जो करूंगा वह तर्क में डाल दिया है viewDidAppear। यह तब कहा जाएगा जब दृश्य स्क्रीन पर आ गया है। यह दिखने वाले नियंत्रक के सभी अलग-अलग परिदृश्यों के लिए बुलाया जाएगा, लेकिन यह ठीक होना चाहिए।

या आप एक समान कार्य करने के लिए UINavigationControllerDelegateविधि navigationController:didShowViewController:animated:का उपयोग कर सकते हैं । यह तब कहा जाता है जब नेविगेशन कंट्रोलर ने व्यू कंट्रोलर को पुश या पॉपअप किया है।


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

1
यह केवल बहुत नया है dismissViewController। शायद यह आ जाएगा popViewController। एक रडार दर्ज करें :-)।
२१:४ay

गंभीरता से, हालांकि, एक रडार दर्ज करें। अगर लोग इसके लिए कहें तो इसे बनाने की संभावना अधिक है।
मैटज्लोग्लाय

1
इसके लिए यह सही जगह है। वर्गीकरण के लिए 'फ़ीचर' होने का विकल्प है।
मटजग्लसय

3
यह उत्तर पूरी तरह से सही नहीं है। जब आप नई शैली के ब्लॉक को सेट नहीं कर सकते हैं -dismissViewController:animated:completionBlock:, लेकिन आप नेविगेशन नियंत्रक के प्रतिनिधि के माध्यम से एनीमेशन प्राप्त कर सकते हैं। एनीमेशन पूरा होने के बाद, -navigationController:didShowViewController:animated:प्रतिनिधि को बुलाया जाएगा और आप वहीं कर सकते हैं, जिसकी आपको आवश्यकता है।
जेसन कोको

13

एनीमेशन के साथ या बिना ठीक से काम करना, और इसमें शामिल हैं popToRootViewController:

 // updated for Swift 3.0
extension UINavigationController {

  private func doAfterAnimatingTransition(animated: Bool, completion: @escaping (() -> Void)) {
    if let coordinator = transitionCoordinator, animated {
      coordinator.animate(alongsideTransition: nil, completion: { _ in
        completion()
      })
    } else {
      DispatchQueue.main.async {
        completion()
      }
    }
  }

  func pushViewController(viewController: UIViewController, animated: Bool, completion: @escaping (() ->     Void)) {
    pushViewController(viewController, animated: animated)
    doAfterAnimatingTransition(animated: animated, completion: completion)
  }

  func popViewController(animated: Bool, completion: @escaping (() -> Void)) {
    popViewController(animated: animated)
    doAfterAnimatingTransition(animated: animated, completion: completion)
  }

  func popToRootViewController(animated: Bool, completion: @escaping (() -> Void)) {
    popToRootViewController(animated: animated)
    doAfterAnimatingTransition(animated: animated, completion: completion)
  }
}

किसी विशेष कारण से आप completion()एसिंक्रस क्यों कह रहे हैं ?
लेविथान

1
जब कोऑर्डिनेटर के साथ एनिमेट करना completionकभी भी एक ही रन-अप पर निष्पादित नहीं किया जाता है। यह गारंटी completionकभी भी एक ही रन-अप पर नहीं चलती है जब एनिमेशन नहीं होता है। इस तरह की असंगति न होना बेहतर है।
rshev

11

@ हॉटजार्ड के उत्तर के आधार पर, जब आप चाहते हैं तो कोड की एक जोड़ी लाइनें हैं। जल्द और आसान।

स्विफ्ट 4 :

_ = self.navigationController?.popViewController(animated: true)
self.navigationController?.transitionCoordinator.animate(alongsideTransition: nil) { _ in
    doWhatIWantAfterContollerHasPopped()
}

6

2018 के लिए ...

यदि आपके पास यह है ...

    navigationController?.popViewController(animated: false)
    // I want this to happen next, help! ->
    nextStep()

और आप एक पूर्णता जोड़ना चाहते हैं ...

    CATransaction.begin()
    navigationController?.popViewController(animated: true)
    CATransaction.setCompletionBlock({ [weak self] in
       self?.nextStep() })
    CATransaction.commit()

यह इत्ना आसान है।

आसान टिप ...

यह आसान popToViewControllerकॉल के लिए एक ही सौदा है ।

एक विशिष्ट बात यह है कि आपके पास एक ज़िलिन स्क्रीन का ऑनबोर्डिंग स्टैक है। जब अंत में किया जाता है, तो आप अपनी "बेस" स्क्रीन पर वापस जाते हैं, और फिर अंत में ऐप को आग लगा देते हैं।

तो "बेस" स्क्रीन में, "सभी तरह से वापस" जाने के लिए, popToViewController(self

func onboardingStackFinallyComplete() {
    
    CATransaction.begin()
    navigationController?.popToViewController(self, animated: false)
    CATransaction.setCompletionBlock({ [weak self] in
        guard let self = self else { return }
        .. actually launch the main part of the app
    })
    CATransaction.commit()
}

5

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


ज़रूर - इसके अलावा आपको उन सभी मामलों को संभालना होगा जहां किसी अन्य कारण से दृश्य गायब हो रहा है।
बेन पैकर्ड

1
@BenPackard, हाँ, और यह आपके द्वारा स्वीकार किए गए उत्तर में viewDidAppear में डालने के लिए सही है।
rdelmar

5

स्विफ्ट 3 उत्तर, इस उत्तर के लिए धन्यवाद: https://stackoverflow.com/a/28232570/3412567

    //MARK:UINavigationController Extension
extension UINavigationController {
    //Same function as "popViewController", but allow us to know when this function ends
    func popViewControllerWithHandler(completion: @escaping ()->()) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        self.popViewController(animated: true)
        CATransaction.commit()
    }
    func pushViewController(viewController: UIViewController, completion: @escaping ()->()) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        self.pushViewController(viewController, animated: true)
        CATransaction.commit()
    }
}

4

किसी विशिष्ट व्यक्ति को पॉप करने के लिए वैकल्पिक दृश्य नियंत्रक पैरामीटर के साथ 4 संस्करण स्विफ्ट करें।

extension UINavigationController {
    func pushViewController(viewController: UIViewController, animated: 
        Bool, completion: @escaping () -> ()) {

        pushViewController(viewController, animated: animated)

        if let coordinator = transitionCoordinator, animated {
            coordinator.animate(alongsideTransition: nil) { _ in
                completion()
            }
        } else {
            completion()
        }
}

func popViewController(viewController: UIViewController? = nil, 
    animated: Bool, completion: @escaping () -> ()) {
        if let viewController = viewController {
            popToViewController(viewController, animated: animated)
        } else {
            popViewController(animated: animated)
        }

        if let coordinator = transitionCoordinator, animated {
            coordinator.animate(alongsideTransition: nil) { _ in
                completion()
            }
        } else {
            completion()
        }
    }
}

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

4

इस उत्तर के आधार पर स्विफ्ट 4 संस्करण को साफ किया ।

extension UINavigationController {
    func pushViewController(_ viewController: UIViewController, animated: Bool, completion: @escaping () -> Void) {
        self.pushViewController(viewController, animated: animated)
        self.callCompletion(animated: animated, completion: completion)
    }

    func popViewController(animated: Bool, completion: @escaping () -> Void) -> UIViewController? {
        let viewController = self.popViewController(animated: animated)
        self.callCompletion(animated: animated, completion: completion)
        return viewController
    }

    private func callCompletion(animated: Bool, completion: @escaping () -> Void) {
        if animated, let coordinator = self.transitionCoordinator {
            coordinator.animate(alongsideTransition: nil) { _ in
                completion()
            }
        } else {
            completion()
        }
    }
}

2

UINavigationControllerWithCompletionBlock नामक एक पॉड है जो एक UINavigationController पर धक्का और पॉपिंग दोनों को पूरा करने वाले ब्लॉक के लिए समर्थन जोड़ता है।


2

2020 स्विफ्ट 5.1 रास्ता

इस समाधान की गारंटी है कि पूरा होने के बाद निष्पादित किया जाता है popViewController पूरी तरह से समाप्त हो गया है। आप इसे पूरा होने में एक और ऑपरेशन नेविगेशनकंट्रोलर पर कर सकते हैं: UINavigationController के ऊपर अन्य सभी समाधानों में अभी भी popViewController ऑपरेशन में व्यस्त है और कोई प्रतिक्रिया नहीं देता है।

public class NavigationController: UINavigationController, UINavigationControllerDelegate
{
    private var completion: (() -> Void)?

    override init(rootViewController: UIViewController) {
        super.init(rootViewController: rootViewController)
        delegate = self
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    public override func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool)
    {
        if self.completion != nil {
            DispatchQueue.main.async(execute: {
                self.completion?()
                self.completion = nil
            })
        }
    }

    func popViewController(animated: Bool, completion: @escaping () -> Void) -> UIViewController?
    {
        self.completion = completion
        return super.popViewController(animated: animated)
    }
}

1

पूर्णता के लिए, मैंने एक उद्देश्य-सी श्रेणी का उपयोग करने के लिए तैयार किया है:

// UINavigationController+CompletionBlock.h

#import <UIKit/UIKit.h>

@interface UINavigationController (CompletionBlock)

- (UIViewController *)popViewControllerAnimated:(BOOL)animated completion:(void (^)()) completion;

@end
// UINavigationController+CompletionBlock.m

#import "UINavigationController+CompletionBlock.h"

@implementation UINavigationController (CompletionBlock)

- (UIViewController *)popViewControllerAnimated:(BOOL)animated completion:(void (^)()) completion {
    [CATransaction begin];
    [CATransaction setCompletionBlock:^{
        completion();
    }];

    UIViewController *vc = [self popViewControllerAnimated:animated];

    [CATransaction commit];

    return vc;
}

@end

1

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

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
    if([segue.identifier isEqualToString:@"addPassword"]){

        UINavigationController* nav = (UINavigationController*)segue.destinationViewController;
        AddPasswordViewController* v = (AddPasswordViewController*)nav.topViewController;

...

        // makes row appear after modal is away.
        [self.tableView beginUpdates];
        [v setViewDidDissapear:^(BOOL animated) {
            [self.tableView endUpdates];
        }];
    }
}

@interface AddPasswordViewController : UITableViewController<UITextFieldDelegate>

...

@property (nonatomic, copy, nullable) void (^viewDidDissapear)(BOOL animated);

@end

@implementation AddPasswordViewController{

...

-(void)viewDidDisappear:(BOOL)animated{
    [super viewDidDisappear:animated];
    if(self.viewDidDissapear){
        self.viewDidDissapear(animated);
    }
}

@end

1

अपने कोड पर अगले एक्सटेंशन का उपयोग करें: (स्विफ्ट 4)

import UIKit

extension UINavigationController {

    func popViewController(animated: Bool = true, completion: @escaping () -> Void) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        popViewController(animated: animated)
        CATransaction.commit()
    }

    func pushViewController(_ viewController: UIViewController, animated: Bool = true, completion: @escaping () -> Void) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        pushViewController(viewController, animated: animated)
        CATransaction.commit()
    }
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.