Blok penyelesaian untuk popViewController

113

Saat dismissViewControllermenutup pengontrol tampilan modal menggunakan , ada opsi untuk menyediakan blok penyelesaian. Apakah ada persamaan yang serupa untuk popViewController?

Argumen penyelesaian cukup berguna. Misalnya, saya dapat menggunakannya untuk menahan penghapusan baris dari tampilan tabel hingga modal dimatikan layar, membiarkan pengguna melihat animasi baris. Saat kembali dari pengontrol tampilan yang didorong, saya menginginkan kesempatan yang sama.

Saya telah mencoba menempatkan popViewControllerdi UIViewblok animasi, di mana saya memiliki akses ke blok penyelesaian. Namun, ini menghasilkan beberapa efek samping yang tidak diinginkan pada tampilan yang dimunculkan.

Jika metode seperti itu tidak tersedia, apa saja solusinya?

Ben Packard
sumber
stackoverflow.com/a/33767837/2774520 saya pikir cara ini adalah yang paling asli
Oleksii Nezhyborets
3
Untuk 2018 ini sangat sederhana dan standar: stackoverflow.com/a/43017103/294884
Fattie

Jawaban:

199

Saya tahu jawaban telah diterima lebih dari dua tahun lalu, namun jawaban ini tidak lengkap.

Tidak ada cara untuk melakukan apa yang Anda inginkan di luar kotak

Ini benar secara teknis karena UINavigationControllerAPI tidak menawarkan opsi apa pun untuk ini. Namun dengan menggunakan kerangka kerja CoreAnimation, dimungkinkan untuk menambahkan blok penyelesaian ke animasi yang mendasarinya:

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

[self.navigationController popViewControllerAnimated:YES];

[CATransaction commit];

Blok penyelesaian akan dipanggil segera setelah animasi digunakan oleh popViewControllerAnimated:akhir. Fungsionalitas ini telah tersedia sejak iOS 4.

Joris Kluivers
sumber
5
Saya memasukkan ini ke dalam perpanjangan UINavigationController di Swift:extension UINavigationController { func popViewControllerWithHandler(handler: ()->()) { CATransaction.begin() CATransaction.setCompletionBlock(handler) self.popViewControllerAnimated(true) CATransaction.commit() } }
Arbitur
1
Sepertinya tidak berhasil untuk saya, saat saya melakukan penyelesaianHandler di rejectViewController, tampilan yang menampilkannya adalah bagian dari hierarki tampilan. Ketika saya melakukan hal yang sama dengan CATransaction, saya mendapat peringatan bahwa tampilan bukan bagian dari hierarki tampilan.
moger777
1
Oke, sepertinya Anda berhasil jika Anda membalik blok awal dan penyelesaian. Maaf tentang suara yang tidak diberikan tetapi stack overflow tidak mengizinkan saya berubah :(
moger777
7
Ya, ini sepertinya akan luar biasa, tetapi tampaknya tidak berfungsi (setidaknya di iOS 8). Blok penyelesaian dipanggil segera. Kemungkinan karena campuran animasi inti dengan animasi gaya UIView.
stuckj
5
INI TIDAK BEKERJA
durazno
51

Untuk iOS9 versi SWIFT - berfungsi seperti pesona (belum diuji untuk versi sebelumnya). Berdasarkan jawaban ini

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()
        }
    }
}
HotJard
sumber
Tidak akan berfungsi jika tidak dianimasikan, harus dilakukan penyelesaian pada runloop berikutnya untuk melakukannya dengan benar.
rshev
@rshev mengapa di runloop berikutnya?
Ben Sinclair
@Andy dari apa yang saya ingat pernah bereksperimen dengan ini, ada sesuatu yang belum disebarkan pada saat itu. Cobalah bereksperimen dengannya, senang mendengar cara kerjanya untuk Anda.
rshev
@rshev Saya rasa saya memiliki cara yang sama sebelumnya, saya harus memeriksa ulang. Tes saat ini berjalan dengan baik.
Ben Sinclair
1
@LanceSamaria Saya sarankan untuk menggunakan viewDidDisappear. Periksa apakah navbar tersedia, jika tidak - tidak ditampilkan di navbar, jadi itu muncul. if (self.navigationController == nil) {trigger your action}
HotJard
32

Saya membuat Swiftversi dengan ekstensi dengan jawaban @JorisKluivers .

Ini akan memanggil penutupan penyelesaian setelah animasi selesai untuk pushdan 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()
    }
}
Arbitur
sumber
Bagi saya, di iOS 8.4, yang ditulis di ObjC, blok menembak setengah jalan ke bawah animasi. Apakah ini benar-benar menyala di saat yang tepat jika ditulis di Swift (8.4)?
Julian F. Weinert
@Arbitur blok penyelesaian memang dipanggil setelah memanggil popViewControlleratau pushViewController, tetapi jika Anda memeriksa apa topViewController tepat setelah itu, Anda akan melihat itu masih yang lama, sama seperti popatau pushtidak pernah terjadi ...
Bogdan Razvan
@BogdanRazvan setelah itu apa? Apakah penutupan penyelesaian Anda dipanggil setelah animasi selesai?
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()
}
}
Muhammad Waqas
sumber
17

Saya memiliki masalah yang sama. Dan karena saya harus menggunakannya dalam beberapa kesempatan, dan dalam rantai blok penyelesaian, saya membuat solusi umum ini dalam subkelas 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];
}

Asumsi

@interface NavigationController : UINavigationController <UINavigationControllerDelegate>

dan

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

dan

- (id) initWithRootViewController:(UIViewController *) rootViewController {
    self = [super initWithRootViewController:rootViewController];
    if (self) {
        self.delegate = self;
    }
    return self;
}
Jos Jong
sumber
1
Saya sangat menyukai solusi ini, saya akan mencobanya dengan kategori dan objek terkait.
spstanley
@spstanley Anda perlu mempublikasikan pod ini :)
k06a
Versi Swift -> stackoverflow.com/a/60090678/4010725
AKAN K.
15

Tidak ada cara untuk melakukan apa yang Anda inginkan di luar kotak. yaitu tidak ada metode dengan blok penyelesaian untuk memunculkan pengontrol tampilan dari tumpukan navigasi.

Apa yang akan saya lakukan adalah memasukkan logika viewDidAppear. Itu akan dipanggil setelah tampilan selesai muncul di layar. Ini akan dipanggil untuk semua skenario berbeda dari pengontrol tampilan yang muncul, tetapi itu seharusnya baik-baik saja.

Atau Anda bisa menggunakan UINavigationControllerDelegatemetode ini navigationController:didShowViewController:animated:untuk melakukan hal serupa. Ini dipanggil saat pengontrol navigasi selesai mendorong atau memunculkan pengontrol tampilan.

mattjgalloway
sumber
Saya mencoba ini. Saya sedang menyimpan array 'indeks baris yang dihapus' dan setiap kali tampilan muncul, memeriksa untuk melihat apakah ada yang perlu dihapus. Ini dengan cepat menjadi berat tapi saya mungkin akan mencobanya lagi. Saya bertanya-tanya mengapa Apple menyediakannya untuk satu transisi tetapi tidak untuk yang lain?
Ben Packard
1
Ini hanya sangat baru di dismissViewController. Mungkin itu akan terjadi popViewController. Ajukan radar :-).
mattjgalloway
Serius, ajukan radar. Kemungkinan besar akan berhasil jika orang memintanya.
mattjgalloway
1
Itu tempat yang tepat untuk memintanya. Ada opsi untuk klasifikasi menjadi 'Fitur'.
mattjgalloway
3
Jawaban ini tidak sepenuhnya benar. Meskipun Anda tidak dapat menyetel blok gaya baru seperti aktif -dismissViewController:animated:completionBlock:, tetapi Anda bisa mendapatkan animasi melalui delegasi pengontrol navigasi. Setelah animasi selesai, -navigationController:didShowViewController:animated:delegasi akan dipanggil dan Anda dapat melakukan apa pun yang Anda perlukan di sana.
Jason Coco
13

Bekerja dengan atau tanpa animasi dengan benar, dan juga termasuk 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)
  }
}
rshev
sumber
Adakah alasan khusus mengapa Anda memanggil completion()async?
leviathan
1
saat menganimasikan dengan koordinator completiontidak pernah dijalankan pada runloop yang sama. jaminan ini completiontidak akan pernah berjalan di runloop yang sama saat tidak dianimasikan. lebih baik tidak memiliki ketidakkonsistenan seperti ini.
rshev
11

Berdasarkan jawaban @ HotJard, saat yang Anda inginkan hanyalah beberapa baris kode. Cepat dan mudah.

Cepat 4 :

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

Untuk 2018 ...

jika Anda memiliki ini ...

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

dan Anda ingin menambahkan penyelesaian ...

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

sesederhana itu.

Tip praktis ...

Ini kesepakatan yang sama untuk popToViewControllerpanggilan praktis .

Hal yang khas adalah Anda memiliki tumpukan onboarding dari jutaan layar. Ketika akhirnya selesai, Anda kembali ke layar "dasar" Anda, dan akhirnya menjalankan aplikasi.

Jadi di layar "dasar", untuk "kembali", 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()
}
Gendut
sumber
5

Blok penyelesaian dipanggil setelah metode viewDidDisappear dipanggil pada pengontrol tampilan yang disajikan, Jadi, menempatkan kode dalam metode viewDidDisappear dari pengontrol tampilan yang muncul harus bekerja sama seperti blok penyelesaian.

rdelmar.dll
sumber
Tentu - kecuali Anda harus menangani semua kasus di mana tampilan menghilang karena alasan lain.
Ben Packard
1
@BenPackard, ya, dan hal yang sama berlaku untuk meletakkannya di viewDidAppear dalam jawaban yang Anda terima.
rdelmar
5

Jawaban Swift 3, berkat jawaban ini: 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()
    }
}
Benobab
sumber
4

Versi Swift 4 dengan parameter viewController opsional untuk dimunculkan ke versi tertentu.

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()
        }
    }
}
TejAces
sumber
Jawaban yang diterima tampaknya berfungsi di lingkungan dev saya dengan semua emulator / perangkat yang saya miliki, tetapi saya masih mendapatkan laporan bug dari pengguna produksi. Tidak yakin apakah ini akan menyelesaikan masalah produksi, tetapi izinkan saya memberi suara positif agar seseorang dapat mencobanya jika mendapatkan masalah yang sama dari jawaban yang diterima.
Sean
4

Membersihkan versi Swift 4 berdasarkan jawaban ini .

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()
        }
    }
}
d4Rk
sumber
2

Ada sebuah pod bernama UINavigationControllerWithCompletionBlock yang menambahkan dukungan untuk blok penyelesaian saat mendorong dan muncul pada UINavigationController.

duncanc4
sumber
2

2020 Swift 5.1 cara

Solusi ini menjamin bahwa penyelesaian dijalankan setelah popViewController selesai sepenuhnya. Anda bisa mengujinya dengan melakukan operasi lain pada NavigationController setelah selesai: Dalam semua solusi lain di atas UINavigationController masih sibuk dengan operasi popViewController dan tidak merespons.

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)
    }
}
AKAN K.
sumber
1

Hanya untuk kelengkapan, saya telah membuat kategori Objective-C siap digunakan:

// 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
Diego Freniche
sumber
1

Saya mencapai hal ini dengan presisi menggunakan balok. Saya ingin pengontrol hasil yang saya ambil menunjukkan baris yang ditambahkan oleh tampilan modal, hanya setelah baris tersebut sepenuhnya meninggalkan layar, sehingga pengguna dapat melihat perubahan yang terjadi. Dalam mempersiapkan segue yang bertanggung jawab untuk menampilkan pengontrol tampilan modal, saya mengatur blok yang ingin saya jalankan ketika modal menghilang. Dan di pengontrol tampilan modal, saya menimpa viewDidDissapear dan kemudian memanggil blok. Saya hanya memulai pembaruan ketika modal akan muncul dan mengakhiri pembaruan ketika menghilang, tetapi itu karena saya menggunakan NSFetchedResultsController namun Anda dapat melakukan apa pun yang Anda suka di dalam blok.

-(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
malhal
sumber
1

Gunakan ekstensi berikutnya pada kode Anda: (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()
    }
}
Rigoberto Sáenz Imbacuán
sumber