Блок завершения для popViewController

При отклонении диспетчера модального представления с помощью dismissViewController существует возможность предоставить блок завершения. Существует ли аналогичный эквивалент для popViewController?

Аргумент завершения довольно удобен. Например, я могу использовать его, чтобы не удалять строку из таблицы, пока модальность не будет отключена, позволяя пользователю увидеть анимацию строки. Когда я возвращаюсь с контроллера толкаемого представления, мне нужна такая же возможность.

Я попытался разместить popViewController в блоке анимации UIView, где у меня есть доступ к блоку завершения. Однако это вызывает некоторые нежелательные побочные эффекты при просмотре представления.

Если такой метод недоступен, какие-то обходные пути?

Ответ 1

Я знаю, что ответ был принят более двух лет назад, однако этот ответ неполный.

Невозможно сделать то, что вам нужно, чтобы быть готовым к работе

Это технически правильно, потому что API UINavigationController не предлагает никаких вариантов для этого. Однако, используя каркас CoreAnimation, можно добавить блок завершения в базовую анимацию:

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

[self.navigationController popViewControllerAnimated:YES];

[CATransaction commit];

Блок завершения будет вызываться, как только анимация, используемая popViewControllerAnimated:, будет завершена. Эта функциональность доступна с iOS 4.

Ответ 2

Для версии 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()
        }
    }
}

Ответ 3

Я сделал версию Swift с расширениями с ответом @JorisKluivers.

Это вызовет закрытие завершения после того, как анимация будет выполнена как для 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()
    }
}

Ответ 4

У меня была такая же проблема. И поскольку мне приходилось использовать его несколько раз и в цепочках блоков завершения, я создал это общее решение в подклассе UINavigationController:

- (void) navigationController:(UINavigationController *) navigationController didShowViewController:(UIViewController *) viewController animated:(BOOL) animated {
    if (_completion) {
        _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;
}

Ответ 5

Невозможно делать то, что вам нужно из коробки. то есть нет метода с блоком завершения для того, чтобы вытащить контроллер вида из навигационного стека.

Я бы поставил логику в viewDidAppear. Это будет вызываться, когда представление закончится на экране. Он будет вызываться для всех сценариев появления контроллера представления, но это должно быть хорошо.

Или вы можете использовать метод UINavigationControllerDelegate navigationController:didShowViewController:animated:, чтобы сделать аналогичную вещь. Это вызывается, когда контроллер навигации закончил нажатие или нажатие контроллера вида.

Ответ 6

Работа с анимацией или без нее должным образом, а также включает 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)
  }
}

Ответ 7

Swift 3 ответ, благодаря этому ответу: fooobar.com/questions/76380/...

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

Ответ 8

Блок завершения вызывается после вызова метода viewDidDisappear на представленном контроллере представления. Таким образом, ввод кода в методе viewDidDisappear контроллера popped view должен работать так же, как и блок завершения.

Ответ 9

Я добился именно этого с точностью, используя блок. Я хотел, чтобы мой получатель результатов отображал строку, которая была добавлена ​​модальным представлением, только после того, как она полностью покинула экран, чтобы пользователь мог увидеть изменение. В процессе подготовки к 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

Ответ 10

Существует подкатегория UINavigationControllerWithCompletionBlock, которая добавляет поддержку блока завершения, когда и толкает, и выскакивает на UINavigationController.

Ответ 11

Только для полноты я создал класс 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