PopViewController için tamamlama bloğu


115

Bir modal görünüm denetleyicisini kullanarak kapatırken dismissViewController, bir tamamlama bloğu sağlama seçeneği vardır. İçin benzer bir eşdeğeri var mı popViewController?

Tamamlama argümanı oldukça kullanışlıdır. Örneğin, modal ekran dışına çıkıncaya kadar tablo görünümünden bir satırı kaldırmayı bekletmek için kullanabilirim, böylece kullanıcının satır animasyonunu görmesine izin verebilirim. İtilmiş bir görüntüleme denetleyicisinden döndüğümde, aynı fırsatı görmek isterim.

Bir tamamlama bloğuna erişimimin olduğu popViewControllerbir UIViewanimasyon bloğu yerleştirmeyi denedim . Ancak bu, açılan görünümde bazı istenmeyen yan etkilere neden olur.

Böyle bir yöntem yoksa, bazı geçici çözümler nelerdir?




3
2018 için bu çok basit ve standart: stackoverflow.com/a/43017103/294884
Fattie

Yanıtlar:


201

İki yıl önce bir cevabın kabul edildiğini biliyorum, ancak bu cevap eksik.

Kutudan çıkar çıkmaz yapmak istediğini yapmanın bir yolu yok

Bu teknik olarak doğrudur çünkü UINavigationControllerAPI bunun için herhangi bir seçenek sunmamaktadır. Ancak CoreAnimation çerçevesini kullanarak, temeldeki animasyona bir tamamlama bloğu eklemek mümkündür:

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

[self.navigationController popViewControllerAnimated:YES];

[CATransaction commit];

Tamamlama bloğu, popViewControllerAnimated:biter tarafından kullanılan animasyonun hemen ardından çağrılacaktır . Bu işlevsellik iOS 4'ten beri mevcuttur.


5
Bunu Swift'deki UINavigationController'ın bir uzantısına koydum:extension UINavigationController { func popViewControllerWithHandler(handler: ()->()) { CATransaction.begin() CATransaction.setCompletionBlock(handler) self.popViewControllerAnimated(true) CATransaction.commit() } }
Arbitur

1
DismissViewController'da completeHandler'ı yaptığımda, bunu sunan görünüm, görünüm hiyerarşisinin bir parçası gibi görünüyor. CATransaction ile aynı şeyi yaptığımda, görünümün görünüm hiyerarşisinin bir parçası olmadığına dair bir uyarı alıyorum.
moger777

1
Tamam, başlama ve tamamlama bloğunu ters çevirirseniz çalışmalarınız gibi görünüyor.
Olumsuz

7
Evet, bu harika gibi görünüyordu, ancak işe yaramıyor gibi görünüyor (en azından iOS 8'de). Tamamlama bloğu hemen çağrılıyor. Muhtemelen temel animasyonların UIView tarzı animasyonlarla karışımından dolayı.
sıkışmış

5
BU ÇALIŞMIYOR
durazno

52

İçin iOS9 SWIFT sürümü - (önceki sürümleri için test olmasaydı) bir cazibe gibi çalışır. Dayanarak bu cevap

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

Animasyonlu değilse çalışmaz, düzgün bir şekilde yapmak için bir sonraki runloop'ta tamamlanmalıdır.
rshev

@rshev neden sonraki runloop'ta?
Ben Sinclair

@Andy, bunu denediğimi hatırladığım kadarıyla, o noktada henüz bir şey yayılmamıştı. Onunla denemeyi deneyin, sizin için nasıl çalıştığını duymayı sevin.
rshev

@rshev Daha önce de aynı şekilde sahip olduğumu düşünüyorum, iki kez kontrol etmem gerekiyor. Mevcut testler iyi çalışıyor.
Ben Sinclair

1
@LanceSamaria viewDidDisappear'ı kullanmanızı öneririm. Gezinme çubuğunun kullanılabilir olup olmadığını kontrol edin, değilse - gezinme çubuğunda gösterilmediği için açılır. if (self.navigationController == nil) {eyleminizi tetikleyin}
HotJard

33

@JorisKluivers cevabı Swiftile uzantılara sahip bir versiyon yaptım .

Bu, hem pushve hem de animasyon tamamlandıktan sonra tamamlama kapanışı çağıracaktır 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()
    }
}

Benim için, ObjC ile yazılmış iOS 8.4'te blok, animasyonun yarısına kadar ateşleniyor. Swift (8.4) ile yazılırsa bu gerçekten doğru anda mı patlar?
Julian F.Weinert

@Arbitur tamamlama bloğu gerçekten arandıktan sonra çağrılır popViewControllerveya pushViewController, ancak hemen sonra topViewController'ın ne olduğunu kontrol ederseniz, aynı popveya pushhiç olmamış gibi hala eski olduğunu fark edeceksiniz ...
Bogdan Razvan

@BogdanRazvan hemen sonra ne olacak? Animasyon tamamlandıktan sonra tamamlama kapanışınız çağrılıyor mu?
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

Ben de aynı sorunu yaşadım. Ve onu birden çok durumda kullanmak zorunda olduğum için ve tamamlama bloklarının zincirleri içinde, bu genel çözümü bir UINavigationController alt sınıfında yarattım:

- (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];
}

varsayarsak

@interface NavigationController : UINavigationController <UINavigationControllerDelegate>

ve

@implementation NavigationController {
    void (^_completion)();
}

ve

- (id) initWithRootViewController:(UIViewController *) rootViewController {
    self = [super initWithRootViewController:rootViewController];
    if (self) {
        self.delegate = self;
    }
    return self;
}

1
Bu çözümü gerçekten beğendim, bir kategori ve ilişkili bir nesneyle deneyeceğim.
spstanley

@spstanley bu bölmeyi yayınlamanız gerekiyor :)
k06a


15

Kutudan çıkar çıkmaz istediğiniz şeyi yapmanın bir yolu yok. yani, bir nav yığınından bir görünüm denetleyicisini açmak için tamamlama bloğu içeren bir yöntem yoktur.

Yapacağım şey mantığı koymak viewDidAppear . Görüntü ekrana gelmeyi tamamladığında bu çağrılacaktır. Görünen görünüm denetleyicisinin tüm farklı senaryoları için çağrılacak, ancak bu iyi olmalı.

Veya benzer bir şey yapmak için bu UINavigationControllerDelegateyöntemi kullanabilirsiniz navigationController:didShowViewController:animated:. Bu, gezinti denetleyicisi bir görünüm denetleyicisini itmeyi veya fırlatmayı bitirdiğinde çağrılır.


Bunu denedim. Bir dizi 'silinmiş satır dizinleri' depoluyordum ve görünüm her göründüğünde, kaldırılması gerekip gerekmediğini kontrol ediyordum. Hızla hantal hale geldi ama bir şans daha verebilirim. Apple'ın neden bir geçiş için sağladığını ancak diğerini sağlamadığını merak ediyorum.
Ben Packard

1
Sadece çok yeni dismissViewController. Belki gelecektir popViewController. Bir radar dosyası :-).
mattjgalloway

Cidden, bir radar açın. İnsanlar isterse içeri girme olasılığı daha yüksektir.
mattjgalloway

1
Bunu istemek için doğru yer orası. Sınıflandırmanın 'Özellik' olması için bir seçenek vardır.
mattjgalloway

3
Bu cevap tam olarak doğru değil. Yeni stil bloğu açık gibi ayarlayamazsınız -dismissViewController:animated:completionBlock:, ancak animasyonu gezinme denetleyicisinin temsilcisi aracılığıyla alabilirsiniz. Animasyon tamamlandıktan sonra, -navigationController:didShowViewController:animated:temsilci çağrılır ve orada ihtiyacınız olan her şeyi yapabilirsiniz.
Jason Coco

13

Animasyonla veya animasyonsuz düzgün çalışma ve ayrıca şunları içerir 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()Async'i aramanızın belirli bir nedeni var mı ?
leviathan

1
koordinatörle animasyon yaparken completionasla aynı runloop'ta çalıştırılmaz. bu garanti completion, animasyon uygulanmadığında asla aynı runloop üzerinde çalışmaz. bu tür bir tutarsızlığın olmaması daha iyidir.
rshev

11

@ HotJard'ın cevabına göre, tek istediğiniz sadece birkaç satır kod olduğunda. Çabuk ve kolay.

Swift 4 :

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

6

2018 için ...

eğer buna sahipsen ...

    navigationController?.popViewController(animated: false)
    // I want this to happen next, help! ->
    nextStep()

ve bir tamamlama eklemek istiyorsunuz ...

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

bu kadar basit.

Kullanışlı ipucu ...

Pratik popToViewControllerarama için aynı anlaşma .

Tipik bir şey, bir zilyon ekrandan oluşan bir başlangıç ​​yığınına sahip olmanızdır. Sonunda işiniz bittiğinde, "temel" ekranınıza kadar geri dönersiniz ve sonunda uygulamayı çalıştırırsınız.

Dolayısıyla, "temel" ekranda "tamamen geriye" gitmek için, 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

Tamamlama bloğu, sunulan görünüm denetleyicisinde viewDidDisappear yöntemi çağrıldıktan sonra çağrılır, Bu nedenle, popped görünüm denetleyicisinin viewDidDisappear yöntemine kod koymak, bir tamamlama bloğuyla aynı şekilde çalışmalıdır.


Elbette - o zaman dışında, başka bir nedenle görünümün kaybolduğu tüm durumları ele almanız gerekir.
Ben Packard

1
@BenPackard, evet ve aynı şey kabul ettiğiniz cevaba viewDidAppear'a koymak için de geçerli.
rdelmar

5

Hızlı 3 cevap, bu cevap sayesinde: 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

Belirli bir tanesini açmak için isteğe bağlı viewController parametresine sahip Swift 4 sürümü.

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

Kabul edilen yanıt, geliştirici ortamımda sahip olduğum tüm öykünücüler / cihazlarla çalışıyor gibi görünüyor, ancak yine de üretim kullanıcılarından hata bildirimi alıyorum. Bunun üretim sorununu çözüp çözmeyeceğinden emin değilim, ancak kabul edilen yanıttan aynı sorunu alırsa birisinin deneyebilmesi için onu yükseltmeme izin verin.
Sean

4

Bu cevaba göre Swift 4 sürümü temizlendi .

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 yolu

Bu çözüm, tamamlamanın popViewController tamamen bittikten sonra gerçekleştirilmesini garanti eder. Tamamlandığında NavigationController üzerinde başka bir işlem yaparak test edebilirsiniz: UINavigationController üzerindeki diğer tüm çözümlerde hala popViewController işlemiyle meşgul ve yanıt vermiyor.

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

Tamlık adına, kullanıma hazır bir Objective-C kategorisi hazırladım:

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

Bunu tam olarak bir blok kullanarak hassasiyetle başardım. Getirilen sonuçlar denetleyicimin, modal görünüm tarafından eklenen satırı yalnızca ekrandan tamamen çıktıktan sonra göstermesini, böylece kullanıcının değişikliği görebilmesini istedim. Modal görünüm denetleyicisini göstermekten sorumlu olan segmente hazırlanırken, modal kaybolduğunda yürütmek istediğim bloğu ayarlıyorum. Ve modal görünüm denetleyicisinde viewDidDissapear'ı geçersiz kılıyorum ve ardından bloğu çağırıyorum. Modal göründüğünde ve kaybolduğunda güncellemeleri bitirdiğinde basitçe güncellemelere başlıyorum, ancak bunun nedeni bir NSFetchedResultsController kullanıyorum, ancak bloğun içinde istediğinizi yapabilirsiniz.

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

Kodunuzda sonraki uzantıyı kullanın: (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()
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.