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


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

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

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

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

17 88

17 ответов:

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

нет никакого способа сделать то, что вы хотите из коробки

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

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

[self.navigationController popViewControllerAnimated:YES];

[CATransaction commit];

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

на iOS 9 охота SWIFT version-работает как шарм (не тестировался для более ранних версий). На основе ответ

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

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

у меня была та же проблема. И поскольку мне приходилось использовать его в нескольких случаях и в цепочках блоков завершения, я создал это общее решение в подклассе 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;
}

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

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

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

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

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

на основе ответа @HotJard, когда все, что вам нужно, это всего лишь несколько строк кода. Быстро и легко.

Swift 4:

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

блок завершения вызывается после вызова метода viewDidDisappear на представленном контроллере представления, поэтому ввод кода в метод viewDidDisappear всплывающего контроллера представления должен работать так же, как и блок завершения.

есть под названием UINavigationControllerWithCompletionblock который добавляет поддержку для блока завершения при нажатии и выскакивают на UINavigationController.

на 2018 год ...

если у вас есть это ...

    navigationController?.popViewController(animated: false)
    nextStep()

и вы хотите добавить в завершение ...

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

это очень просто.

просто для полноты, я сделал 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

Я достиг именно этого с точностью, используя блок. Я хотел, чтобы мой контроллер результатов выборки показывал строку, которая была добавлена модальным представлением, только после того, как она полностью покинула экран, чтобы пользователь мог видеть происходящее изменение. В prepare for segue, который отвечает за отображение контроллера модального вида, я устанавливаю блок, который я хочу выполнить, когда модальный исчезает. И в модальном контроллере представления я переопределяю viewDidDissapear, а затем вызываю блок. Я просто начинаю обновления, когда modal появится и завершит обновления, когда он исчезнет, но это потому, что я использую 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

используйте следующее расширение в коде: (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()
    }
}

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

версия Swift 4 с дополнительным параметром viewController для перехода к определенному.

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

подчистили 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()
        }
    }
}