UINavigationController के लिए समापन हैंडलर "pushViewController: एनिमेटेड"?


110

मैं UINavigationControllerअगले दृश्य नियंत्रकों को पेश करने के लिए एक ऐप बनाने के बारे में हूं । IOS5 प्रस्तुत करने के लिए एक नई विधि के साथ UIViewControllers:

presentViewController:animated:completion:

अब मैं मुझसे पूछता हूँ कि वहाँ के लिए एक पूरा हैंडलर क्यों नहीं है UINavigationController? बस हैं

pushViewController:animated:

क्या नए की तरह अपना पूरा हैंडलर बनाना संभव है presentViewController:animated:completion:?


2
एक पूर्ण हैंडलर के रूप में बिल्कुल वैसा ही नहीं, लेकिन viewDidAppear:animated:चलो आप कोड को हर बार निष्पादित करते हैं जब आपका व्यू कंट्रोलर स्क्रीन पर दिखाई देता है ( viewDidLoadकेवल पहली बार आपका व्यू कंट्रोलर लोड होता है)
Moxy

@ मॉक्सी, क्या आपका मतलब है-(void)viewDidAppear:(BOOL)animated
जॉर्ज

2
के लिए 2018 : ... वास्तव में यह सिर्फ इस है stackoverflow.com/a/43017103/294884
fattie

जवाबों:


139

दूसरे के लिए और अधिक समाधान के लिए बराबर जवाब देखें

UINavigationControllerएनिमेशन के साथ चलाया जाता है CoreAnimation, इसलिए यह कोड को इनकैप्सुलेट करने के लिए समझ में आता है CATransactionऔर इस तरह एक पूरा ब्लॉक सेट करता है।

स्विफ्ट :

स्विफ्ट के लिए मैं सुझाव देता हूं कि इस तरह से एक एक्सटेंशन बनाया जाए

extension UINavigationController {

  public func pushViewController(viewController: UIViewController,
                                 animated: Bool,
                                 completion: @escaping (() -> Void)?) {
    CATransaction.begin()
    CATransaction.setCompletionBlock(completion)
    pushViewController(viewController, animated: animated)
    CATransaction.commit()
  }

}

उपयोग:

navigationController?.pushViewController(vc, animated: true) {
  // Animation done
}

उद्देश्य सी

हैडर:

#import <UIKit/UIKit.h>

@interface UINavigationController (CompletionHandler)

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

@end

कार्यान्वयन:

#import "UINavigationController+CompletionHandler.h"
#import <QuartzCore/QuartzCore.h>

@implementation UINavigationController (CompletionHandler)

- (void)completionhandler_pushViewController:(UIViewController *)viewController 
                                    animated:(BOOL)animated 
                                  completion:(void (^)(void))completion 
{
    [CATransaction begin];
    [CATransaction setCompletionBlock:completion];
    [self pushViewController:viewController animated:animated];
    [CATransaction commit];
}

@end

1
मेरा मानना ​​है कि (परीक्षण नहीं किया गया है) यह गलत परिणाम प्रदान कर सकता है अगर प्रस्तुत दृश्य नियंत्रक इसे देखने के लिए एनिमेशन को ट्रिगर करता है या व्यूप्लेयर कार्यान्वयन। मुझे लगता है कि पुश एनिमेशन कॉन्ट्रोलर: एनिमेटेड: रिटर्न - से पहले उन एनिमेशन को शुरू किया जाएगा, इस प्रकार, पूर्ण हैंडलर को तब तक नहीं बुलाया जाएगा जब तक कि नए-नए ट्रिगर एनिमेशन समाप्त नहीं हो जाते।
मैट एच।

1
@MattH। क्या इस शाम को एक जोड़े ने परीक्षण किया था और इसका उपयोग करते समय pushViewController:animated:या जैसा दिखता है popViewController:animated, viewDidLoadऔर viewDidAppearकॉल बाद के रनलूप चक्रों में होता है। इसलिए मेरी धारणा यह है कि भले ही वे विधियाँ एनिमेशन करती हों, वे कोड उदाहरण में प्रदान किए गए लेन-देन का हिस्सा नहीं होंगे। क्या यह आपकी चिंता थी? क्योंकि यह समाधान fabulously सरल है।
लेफेलमेनिया

1
इस सवाल को देखते हुए, मुझे लगता है कि सामान्य तौर पर @MattH द्वारा बताई गई चिंताएँ हैं। और @LeffelMania इस समाधान के साथ एक वैध समस्या को उजागर करता है - यह अंततः मानता है कि धक्का पूरा होने के बाद लेनदेन पूरा हो जाएगा, लेकिन रूपरेखा इस व्यवहार की गारंटी नहीं देती है। didShowViewControllerयद्यपि यह प्रश्न में दिखाया गया है, यह दृश्य नियंत्रक से गारंटीकृत है । जबकि यह समाधान काल्पनिक रूप से सरल है, मैं इसके "भविष्य-प्रूफ-नेस" पर सवाल उठाऊंगा। विशेष रूप से ios7 / 8 के साथ आए जीवनचक्र कॉलबैक को देखने के लिए बदलाव दिए गए
सैम

8
यह iOS 9 उपकरणों पर मज़बूती से काम नहीं करता है। नीचे एक विकल्प के लिए मेरे या @ पार के जवाब देखें
माइक स्प्रैग

1
@ZevEisenberg निश्चित रूप से। मेरा जवाब इस दुनिया में डायनासोर कोड ~~ 2 साल पुराना है
chrs

95

iOS 7+ स्विफ्ट

स्विफ्ट 4:

// 2018.10.30 par:
//   I've updated this answer with an asynchronous dispatch to the main queue
//   when we're called without animation. This really should have been in the
//   previous solutions I gave but I forgot to add it.
extension UINavigationController {
    public func pushViewController(
        _ viewController: UIViewController,
        animated: Bool,
        completion: @escaping () -> Void)
    {
        pushViewController(viewController, animated: animated)

        guard animated, let coordinator = transitionCoordinator else {
            DispatchQueue.main.async { completion() }
            return
        }

        coordinator.animate(alongsideTransition: nil) { _ in completion() }
    }

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

        guard animated, let coordinator = transitionCoordinator else {
            DispatchQueue.main.async { completion() }
            return
        }

        coordinator.animate(alongsideTransition: nil) { _ in completion() }
    }
}

संपादित करें: मैंने अपने मूल उत्तर का एक स्विफ्ट 3 संस्करण जोड़ा है। इस संस्करण में मैंने स्विफ्ट 2 संस्करण में दिखाए गए उदाहरण के सह-एनीमेशन को हटा दिया है क्योंकि ऐसा लगता है कि बहुत सारे लोगों को भ्रमित किया गया है।

स्विफ्ट 3:

import UIKit

// Swift 3 version, no co-animation (alongsideTransition parameter is nil)
extension UINavigationController {
    public func pushViewController(
        _ viewController: UIViewController,
        animated: Bool,
        completion: @escaping (Void) -> Void)
    {
        pushViewController(viewController, animated: animated)

        guard animated, let coordinator = transitionCoordinator else {
            completion()
            return
        }

        coordinator.animate(alongsideTransition: nil) { _ in completion() }
    }
}

स्विफ्ट 2:

import UIKit

// Swift 2 Version, shows example co-animation (status bar update)
extension UINavigationController {
    public func pushViewController(
        viewController: UIViewController,
        animated: Bool,
        completion: Void -> Void)
    {
        pushViewController(viewController, animated: animated)

        guard animated, let coordinator = transitionCoordinator() else {
            completion()
            return
        }

        coordinator.animateAlongsideTransition(
            // pass nil here or do something animated if you'd like, e.g.:
            { context in
                viewController.setNeedsStatusBarAppearanceUpdate()
            },
            completion: { context in
                completion()
            }
        )
    }
}

1
क्या कोई विशेष कारण है कि आप vc को अपडेट करने के लिए कह रहे हैं कि यह स्टेटस बार है? यह nilएनीमेशन ब्लॉक के रूप में ठीक काम करने के लिए लगता है ।
माइक स्प्रैग

2
यह एक ऐसी चीज का उदाहरण है जिसे आप एक समानांतर एनीमेशन के रूप में कर सकते हैं (इसके तुरंत बाद की टिप्पणी यह ​​वैकल्पिक है)। पास nilकरना भी पूरी तरह से मान्य चीज है।
बराबर

1
@par, क्या आपको अधिक रक्षात्मक होना चाहिए और transitionCoordinatorशून्य होने पर कॉल को पूरा करना चाहिए ?
ऑरेलियन पोर्टे

@AurelienPorte यह एक शानदार कैच है और मैं कहूंगा कि हां, आपको चाहिए। मैं जवाब अपडेट कर दूंगा।
बराबर

1
@cbowns मैं इस बारे में 100% निश्चित नहीं हूं क्योंकि मैंने ऐसा नहीं देखा है, लेकिन यदि आप इसे नहीं देखते हैं, transitionCoordinatorतो संभावना है कि आप इस फ़ंक्शन को नेविगेशन नियंत्रक के जीवनचक्र में बहुत जल्दी बुला रहे हैं। viewWillAppear()एनीमेशन के साथ व्यू कंट्रोलर को पुश करने की कोशिश करने से पहले कम से कम तब तक प्रतीक्षा करें ।
बराबर

28

परा के उत्तर के आधार पर (जो iOS9 के साथ काम करने वाला एकमात्र था), लेकिन सरल और एक और लापता के साथ (जिसके कारण कभी भी पूरा नहीं किया जा सकता है):

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

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

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

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

मेरे लिए काम नहीं करता है। ट्रांज़िशनकॉर्डिनेटर मेरे लिए शून्य है।
tcurdt

मेरे लिये कार्य करता है। यह भी एक बेहतर है तो एक को स्वीकार किया जाता है क्योंकि एनीमेशन पूर्णता हमेशा पुश समापन के समान नहीं होती है।
एंटोन प्लीबनोविच

आप गैर एनिमेटेड मामले के लिए एक DispatchQueue.main.async याद कर रहे हैं। इस पद्धति का अनुबंध यह है कि पूर्ण हैंडलर को अतुल्यकालिक रूप से कहा जाता है, आपको इसका उल्लंघन नहीं करना चाहिए क्योंकि इससे सूक्ष्म कीड़े हो सकते हैं।
वर्नर अल्टिवैचर

24

वर्तमान में UINavigationControllerयह समर्थन नहीं करता है। लेकिन वहाँ है UINavigationControllerDelegateकि आप का उपयोग कर सकते हैं।

इसे पूरा करने का एक आसान तरीका उप-वर्ग को पूरा करना UINavigationControllerऔर एक पूर्ण ब्लॉक संपत्ति जोड़ना है:

@interface PbNavigationController : UINavigationController <UINavigationControllerDelegate>

@property (nonatomic,copy) dispatch_block_t completionBlock;

@end


@implementation PbNavigationController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        self.delegate = self;
    }
    return self;
}

- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
    NSLog(@"didShowViewController:%@", viewController);

    if (self.completionBlock) {
        self.completionBlock();
        self.completionBlock = nil;
    }
}

@end

नए व्यू कंट्रोलर को पुश करने से पहले आपको पूरा ब्लॉक सेट करना होगा:

UIViewController *vc = ...;
((PbNavigationController *)self.navigationController).completionBlock = ^ {
    NSLog(@"COMPLETED");
};
[self.navigationController pushViewController:vc animated:YES];

यह नया उपवर्ग या तो इंटरफ़ेस बिल्डर में सौंपा जा सकता है या इस तरह प्रोग्रामेटिक रूप से उपयोग किया जा सकता है:

PbNavigationController *nc = [[PbNavigationController alloc]initWithRootViewController:yourRootViewController];

8
नियंत्रकों को देखने के लिए मैप किए गए पूर्ण ब्लॉकों की एक सूची को जोड़ना संभवतः इसे सबसे उपयोगी बना देगा, और एक नई विधि, जिसे शायद कहा जाता है, pushViewController:animated:completion:यह एक सुंदर समाधान बना देगा।
हाइपरबोले

1
2018 के लिए NB यह वास्तव में सिर्फ यही है ... stackoverflow.com/a/43017103/294884
Fattie

8

यहाँ पॉप के साथ स्विफ्ट 4 संस्करण है।

extension UINavigationController {
    public func pushViewController(viewController: UIViewController,
                                   animated: Bool,
                                   completion: (() -> Void)?) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        pushViewController(viewController, animated: animated)
        CATransaction.commit()
    }

    public func popViewController(animated: Bool,
                                  completion: (() -> Void)?) {
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        popViewController(animated: animated)
        CATransaction.commit()
    }
}

बस मामले में किसी और को इसकी जरूरत है।


यदि आप इस पर एक साधारण परीक्षण चलाते हैं, तो आप पाएंगे कि एनीमेशन समाप्त होने से पहले पूरा ब्लॉक आग। तो यह शायद प्रदान नहीं करता है जो कई के लिए देख रहे हैं।
घोड़े की नाल

7

@Klaas के उत्तर पर विस्तार करने के लिए (और इस प्रश्न के परिणामस्वरूप ) मैंने पूर्ण ब्लॉक को सीधे पुश विधि में जोड़ा है:

@interface PbNavigationController : UINavigationController <UINavigationControllerDelegate>

@property (nonatomic,copy) dispatch_block_t completionBlock;
@property (nonatomic,strong) UIViewController * pushedVC;

@end


@implementation PbNavigationController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        self.delegate = self;
    }
    return self;
}

- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
    NSLog(@"didShowViewController:%@", viewController);

    if (self.completionBlock && self.pushedVC == viewController) {
        self.completionBlock();
    }
    self.completionBlock = nil;
    self.pushedVC = nil;
}

-(void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
    if (self.pushedVC != viewController) {
        self.pushedVC = nil;
        self.completionBlock = nil;
    }
}

-(void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated completion:(dispatch_block_t)completion {
    self.pushedVC = viewController;
    self.completionBlock = completion;
    [self pushViewController:viewController animated:animated];
}

@end

निम्नानुसार उपयोग किया जाना है:

UIViewController *vc = ...;
[(PbNavigationController *)self.navigationController pushViewController:vc animated:YES completion:^ {
    NSLog(@"COMPLETED");
}];

प्रतिभाशाली। बहुत बहुत धन्यवाद
पेटार

if... (self.pushedVC == viewController) {गलत है। आपको वस्तुओं के बीच समानता का परीक्षण करने की आवश्यकता है isEqual:, अर्थात,[self.pushedVC isEqual:viewController]
इवान आर

@ EvanR जो शायद अधिक तकनीकी रूप से सही है हाँ। क्या आपने उदाहरणों को दूसरे तरीके से तुलना करने में त्रुटि देखी है?
सैम

@ इस उदाहरण के साथ विशेष रूप से नहीं (इसे लागू नहीं किया), लेकिन निश्चित रूप से अन्य वस्तुओं के साथ समानता का परीक्षण करने में - इस पर Apple के डॉक्स देखें: developer.apple.com/library/ios/documentation/General/… । क्या आपकी तुलना का तरीका हमेशा इस मामले में काम करता है?
इवान आर

मैंने नहीं देखा कि यह काम नहीं कर रहा है या मैंने अपना उत्तर बदल दिया है। जहाँ तक मुझे पता है कि iOS गतिविधियों के साथ एंड्रॉइड की तरह व्यू कंट्रोलर्स को फिर से बनाने के लिए कुछ भी चालाक नहीं करता है। लेकिन हां, isEqualशायद वे कभी भी तकनीकी रूप से सही हो जाएंगे।
सैम

5

IOS 7.0 के बाद से, आप UIViewControllerTransitionCoordinatorपुश पूरा करने वाले ब्लॉक को जोड़ने के लिए उपयोग कर सकते हैं :

UINavigationController *nav = self.navigationController;
[nav pushViewController:vc animated:YES];

id<UIViewControllerTransitionCoordinator> coordinator = vc.transitionCoordinator;
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {

} completion:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
    NSLog(@"push completed");
}];

1
यह UINavigationController धक्का, पॉप, आदि के रूप में एक ही बात नहीं है
जॉन विलीस

3

स्विफ्ट 2.0

extension UINavigationController : UINavigationControllerDelegate {
    private struct AssociatedKeys {
        static var currentCompletioObjectHandle = "currentCompletioObjectHandle"
    }
    typealias Completion = @convention(block) (UIViewController)->()
    var completionBlock:Completion?{
        get{
            let chBlock = unsafeBitCast(objc_getAssociatedObject(self, &AssociatedKeys.currentCompletioObjectHandle), Completion.self)
            return chBlock as Completion
        }set{
            if let newValue = newValue {
                let newValueObj : AnyObject = unsafeBitCast(newValue, AnyObject.self)
                objc_setAssociatedObject(self, &AssociatedKeys.currentCompletioObjectHandle, newValueObj, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            }
        }
    }
    func popToViewController(animated: Bool,comp:Completion){
        if (self.delegate == nil){
            self.delegate = self
        }
        completionBlock = comp
        self.popViewControllerAnimated(true)
    }
    func pushViewController(viewController: UIViewController, comp:Completion) {
        if (self.delegate == nil){
            self.delegate = self
        }
        completionBlock = comp
        self.pushViewController(viewController, animated: true)
    }

    public func navigationController(navigationController: UINavigationController, didShowViewController viewController: UIViewController, animated: Bool){
        if let comp = completionBlock{
            comp(viewController)
            completionBlock = nil
            self.delegate = nil
        }
    }
}

2

इस व्यवहार को जोड़ने और एक बाहरी प्रतिनिधि को स्थापित करने की क्षमता को बनाए रखने के लिए थोड़ा अधिक पाइपवर्क होता है।

यहां एक प्रलेखित कार्यान्वयन है जो प्रतिनिधि कार्यक्षमता को बनाए रखता है:

LBXCompletingNavigationController

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