popViewController의 완료 블록


113

를 사용하여 모달 뷰 컨트롤러를 닫을 때 dismissViewController완료 블록을 제공하는 옵션이 있습니다. 에 대해 비슷한 것이 popViewController있습니까?

완료 인수는 매우 편리합니다. 예를 들어, 모달이 화면에서 벗어날 때까지 테이블 뷰에서 행 제거를 보류하여 사용자가 행 애니메이션을 볼 수 있도록 할 수 있습니다. 푸시 뷰 컨트롤러에서 돌아올 때도 같은 기회를 원합니다.

내가 배치를 시도 popViewControllerUIView나는 완료 블록에 액세스 할 수 있습니까 애니메이션 블록. 그러나 이로 인해 팝업되는 뷰에 원치 않는 부작용이 발생합니다.

이러한 방법을 사용할 수없는 경우 몇 가지 해결 방법은 무엇입니까?


stackoverflow.com/a/33767837/2774520 나는이 방법은 가장 기본 하나라고 생각
Oleksii Nezhyborets


3
2018 년에는 이것은 매우 간단하고 표준 적입니다 : stackoverflow.com/a/43017103/294884
Fattie

답변:


199

2 년 전에 답변이 받아 들여진 것을 알고 있지만이 답변은 불완전합니다.

원하는 것을 즉시 수행 할 수있는 방법은 없습니다.

이는 UINavigationControllerAPI가 이에 대한 옵션을 제공하지 않기 때문에 기술적으로 정확 합니다. 그러나 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
나를 위해 작동하지 않는 것 같습니다 .dismissViewController에서 완료 핸들러를 수행하면 표시했던 뷰가 뷰 계층 구조의 일부입니다. CATransaction으로 동일한 작업을 수행 할 때 뷰가 뷰 계층의 일부가 아니라는 경고가 표시됩니다.
moger777

1
좋습니다. 시작 블록과 완료 블록을 반대로하면 작품처럼 보입니다. 아래 표에 대해 죄송하지만 스택 오버플로로 인해 변경되지 않습니다. :(
moger777

7
예, 이것은 굉장한 것처럼 보였지만 작동하지 않는 것 같습니다 (적어도 iOS 8에서는). 완료 블록이 즉시 호출됩니다. 핵심 애니메이션과 UIView 스타일 애니메이션이 혼합되어 있기 때문일 수 있습니다.
stuckj


51

들어 iOS9 SWIFT 버전 - (이전 버전에 대한 테스트를하지 않은) 매력처럼 작동합니다. 이 답변을 바탕으로

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 왜 다음 runloop에서?
Ben Sinclair

@Andy 내가 이것을 실험했던 기억에서, 그 시점에서 무언가가 아직 전파되지 않았습니다. 그것을 실험 해보고 그것이 어떻게 작동하는지 듣고 싶어하십시오.
rshev

@rshev 나는 전에 같은 방식으로 그것을 가지고 있다고 생각합니다, 나는 다시 확인해야합니다. 현재 테스트는 잘 실행됩니다.
Ben Sinclair

1
@LanceSamaria viewDidDisappear를 사용하는 것이 좋습니다. navbar를 사용할 수 있는지 확인하십시오. 그렇지 않은 경우 navbar에 표시되지 않으므로 팝업되었습니다. 경우 (self.navigationController == 전무) {액션을 트리거}
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()
    }
}

나에게 ObjC로 작성된 iOS 8.4에서 블록은 애니메이션의 절반 정도를 실행합니다. Swift (8.4)로 작성된 경우 실제로 올바른 순간에 실행됩니까?
Julian F. Weinert 2015-07-29

@Arbitur 완료 블록은 참으로 호출 한 후 호출 popViewController하거나 pushViewController,하지만 당신은 topViewController를 잘 작성 이후 무엇인지 확인하는 경우, 당신은이처럼 여전히 이전 하나 알 pop거나 push일이 결코 ...
보그 Razvan 보낸

@BogdanRazvan 바로 나중에 무엇? 애니메이션이 완료되면 완료 클로저가 호출됩니까?
Arbitur

17

SWIFT 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
이 솔루션이 정말 마음에 듭니다. 범주 및 관련 개체를 사용해 시도해 보겠습니다.
spstanley 2014

@spstanley 당신은이 포드를 게시해야합니다 :)
k06a


15

원하는 것을 즉시 수행 할 수있는 방법은 없습니다. 즉, 탐색 스택에서 뷰 컨트롤러를 팝업하는 완료 블록이있는 메서드가 없습니다.

내가 할 일은 논리를 viewDidAppear. 뷰가 화면에 표시되면 호출됩니다. 뷰 컨트롤러가 나타나는 모든 다른 시나리오에 대해 호출되지만 괜찮습니다.

또는이 UINavigationControllerDelegate방법 navigationController:didShowViewController:animated:을 사용하여 비슷한 작업을 수행 할 수 있습니다 . 내비게이션 컨트롤러가 뷰 컨트롤러를 밀거나 터 뜨렸을 때 호출됩니다.


나는 이것을 시도했다. 나는 '삭제 된 행 인덱스'의 배열을 저장하고 있었고 뷰가 나타날 때마다 제거해야 할 것이 있는지 확인했습니다. 빠르게 다루기 어려워졌지만 다시 시도해 볼 수 있습니다. 왜 Apple이 하나의 전환을 제공하지만 다른 전환을 제공하지 않는지 궁금합니다.
Ben Packard

1
.NET에서 매우 새로운 기능입니다 dismissViewController. 아마도 popViewController. 레이더 제출 :-).
mattjgalloway

진지하게, 레이더를 제출하십시오. 사람들이 요청하면 성공할 가능성이 더 큽니다.
mattjgalloway

1
그것이 그것을 요구할 올바른 장소입니다. 분류를 '기능'으로 지정하는 옵션이 있습니다.
mattjgalloway

3
이 대답은 완전히 정확하지 않습니다. on과 같이 새 스타일 블록 -dismissViewController:animated:completionBlock:을 설정할 수는 없지만 내비게이션 컨트롤러의 델리게이트를 통해 애니메이션을 얻을 수 있습니다. 애니메이션이 완료되면 -navigationController:didShowViewController:animated:델리게이트에서이 호출되고 필요한 모든 작업을 바로 수행 할 수 있습니다.
Jason Coco

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()비동기를 호출하는 특별한 이유가 있습니까?
leviathan

1
코디네이터를 사용한 애니메이션 completion이 동일한 런 루프에서 실행되지 않을 때 . 이렇게 completion하면 애니메이션을하지 않을 때 동일한 런 루프에서 실행되지 않습니다. 이런 종류의 불일치를 가지지 않는 것이 좋습니다.
rshev

11

@HotJard의 답변에 따르면 원하는 것은 몇 줄의 코드뿐입니다. 빠르고 쉽습니다.

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

완료 블록은 제시된 뷰 컨트롤러에서 viewDidDisappear 메서드가 호출 된 후 호출되므로 팝업 된 뷰 컨트롤러의 viewDidDisappear 메서드에 코드를 넣는 것은 완료 블록과 동일하게 작동해야합니다.


물론입니다. 다른 이유로 뷰가 사라지는 경우를 제외하고는 모든 경우를 처리해야합니다.
Ben Packard

1
@ BenPackard, 예, 수락 한 답변에 viewDidAppear에 넣는 경우에도 마찬가지입니다.
rdelmar

5

이 답변 덕분에 Swift 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

선택적 viewController 매개 변수가있는 Swift 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()
        }
    }
}

받아 들인 대답은 내가 가지고있는 모든 에뮬레이터 / 장치와 함께 내 개발 환경에서 작동하는 것처럼 보이지만 여전히 프로덕션 사용자로부터 버그보고를받습니다. 이것이 프로덕션 문제를 해결할 수 있는지 확실하지 않지만 수락 된 답변에서 동일한 문제가 발생하면 누군가가 시도해 볼 수 있도록 upvote합니다.
Sean

4

이 답변을 기반으로 Swift 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

2020 Swift 5.1 방식

이 솔루션은 popViewController가 완전히 완료된 후 완료가 실행되도록 보장합니다. 완료되면 NavigationController에서 다른 작업을 수행하여 테스트 할 수 있습니다. 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

완성을 위해 Objective-C 카테고리를 사용할 준비가되었습니다.

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

나는 블록을 사용하여 정확하게 이것을 달성했습니다. 가져온 결과 컨트롤러가 모달보기에 의해 추가 된 행을 표시하기를 원했습니다. 화면에서 완전히 나가면 사용자가 변경 사항을 볼 수 있습니다. 모달 뷰 컨트롤러 표시를 담당하는 segue에 대비하여 모달이 사라질 때 실행하고 싶은 블록을 설정했습니다. 그리고 모달 뷰 컨트롤러에서 viewDidDissapear를 재정의 한 다음 블록을 호출합니다. 모달이 나타날 때 업데이트를 시작하고 사라질 때 업데이트를 종료합니다.하지만 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

코드에서 다음 확장을 사용하세요 : (Swift 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()
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.