code

UINavigationController "pushViewController : animated"에 대한 완료 처리기?

codestyles 2020. 8. 17. 08:58
반응형

UINavigationController "pushViewController : animated"에 대한 완료 처리기?


UINavigationController다음 뷰 컨트롤러 를 표시하기 위해를 사용하여 앱을 만드는 중 입니다. iOS5에는 새로운 프레젠테이션 방법이 있습니다 UIViewControllers.

presentViewController:animated:completion:

이제 완료 처리기가없는 이유를 묻습니다 UINavigationController. 그냥

pushViewController:animated:

새로운 것과 같은 내 자신의 완료 처리기를 만들 수 presentViewController:animated:completion:있습니까?


다른 최신 솔루션에 대한 par의 답변 참조

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
}

목표 -C

헤더:

#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

iOS 7 이상 Swift

스위프트 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() }
    }
}

편집 : 원래 답변의 Swift 3 버전을 추가했습니다. 이 버전에서는 많은 사람들을 혼란스럽게하는 것처럼 보이기 때문에 Swift 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()
            }
        )
    }
}

를 기반으로 파의 대답은 하지만, 간단하고와 (유일한 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()
        }
    }
}

현재는 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];

이 새 하위 클래스는 Interface Builder에서 할당하거나 다음과 같이 프로그래밍 방식으로 사용할 수 있습니다.

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

To expand on @Klaas' answer (and as a result of this question) I've added completion blocks directly to the push method:

@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

To be used as follows:

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

Since iOS 7.0,you can use UIViewControllerTransitionCoordinator to add a push completion block:

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

Here is the Swift 4 version with the Pop.

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

Just in case someone else needs this.


Swift 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
        }
    }
}

It takes a little more pipework to add this behavior and retain the ability to set an external delegate.

Here's a documented implementation that maintains delegate functionality:

LBXCompletingNavigationController

참고URL : https://stackoverflow.com/questions/9906966/completion-handler-for-uinavigationcontroller-pushviewcontrolleranimated

반응형