tombol kembali callback di navigationController di iOS

102

Saya telah mendorong tampilan ke pengontrol navigasi dan ketika saya menekan tombol kembali, itu beralih ke tampilan sebelumnya secara otomatis. Saya ingin melakukan beberapa hal saat tombol kembali ditekan sebelum memunculkan tampilan dari tumpukan. Apa fungsi panggilan balik tombol kembali?

Namratha
sumber
Lihat [solusi] [1] ini yang juga mempertahankan gaya tombol kembali. [1]: stackoverflow.com/a/29943156/3839641
Sarasranglt

Jawaban:

162

William Jockusch ini jawaban mengatasi masalah ini dengan trik mudah.

-(void) viewWillDisappear:(BOOL)animated {
    if ([self.navigationController.viewControllers indexOfObject:self]==NSNotFound) {
       // back button was pressed.  We know this is true because self is no longer
       // in the navigation stack.  
    }
    [super viewWillDisappear:animated];
}
ymutlu
sumber
32
Kode ini tidak hanya dieksekusi ketika pengguna mengetuk tombol kembali, tetapi dalam setiap kejadian tampilan akan muncul (misalnya ketika selesai atau tombol simpan di sisi kanan).
arti-penting
7
Atau saat maju ke tampilan baru.
GuybrushThreepwood
Ini juga dipanggil saat pengguna menggeser dari tepi kiri (interactivePopGestureRecognizer). Dalam kasus saya, saya secara khusus mencari saat pengguna menekan kembali sementara TIDAK menggeser dari tepi kiri.
Kyle Clegg
2
Bukan berarti tombol kembali adalah penyebabnya. Bisa jadi segue santai misalnya.
smileBot
1
Saya ragu, Mengapa kita tidak melakukan ini di viewDidDisappear?
JohnVanDijk
85

Menurut saya solusi terbaik.

- (void)didMoveToParentViewController:(UIViewController *)parent
{
    if (![parent isEqual:self.parentViewController]) {
         NSLog(@"Back pressed");
    }
}

Tapi itu hanya bekerja dengan iOS5 +

Kosong
sumber
3
Teknik ini tidak dapat membedakan antara ketukan tombol kembali dan gerakan santai.
smileBot
Metode willMoveToParentViewController dan viewWillDisappear tidak menjelaskan bahwa pengontrol harus dimusnahkan, didMoveToParentViewController benar
Hank
27

mungkin lebih baik untuk mengganti tombol kembali sehingga Anda bisa menangani acara sebelum tampilan dimunculkan untuk hal-hal seperti konfirmasi pengguna.

di viewDidLoad buat UIBarButtonItem dan setel self.navigationItem.leftBarButtonItem ke dalamnya dengan meneruskan sel

- (void) viewDidLoad
{
// change the back button to cancel and add an event handler
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:@”back
style:UIBarButtonItemStyleBordered
target:self
action:@selector(handleBack:)];

self.navigationItem.leftBarButtonItem = backButton;
[backButton release];

}
- (void) handleBack:(id)sender
{
// pop to root view controller
[self.navigationController popToRootViewControllerAnimated:YES];

}

Kemudian Anda dapat melakukan hal-hal seperti menaikkan UIAlertView untuk mengonfirmasi tindakan, lalu memunculkan pengontrol tampilan, dll.

Atau daripada membuat backbutton baru, Anda dapat menyesuaikan metode delegasi UINavigationController untuk melakukan tindakan saat tombol kembali ditekan.

roocell
sumber
Tidak UINavigationControllerDelegatememiliki metode yang dipanggil saat tombol kembali diketuk.
arti-hal
Teknik ini memungkinkan validasi data pengontrol tampilan dan pengembalian bersyarat dari tombol kembali pengontrol navigasi.
gjpc
Solusi ini mematahkan fitur gesek tepi iOS 7+
Liron Yahdav
9

Ini adalah cara yang benar untuk mendeteksi ini.

- (void)willMoveToParentViewController:(UIViewController *)parent{
    if (parent == nil){
        //do stuff

    }
}

metode ini dipanggil saat view didorong juga. Jadi memeriksa induk == nil adalah untuk memunculkan pengontrol tampilan dari tumpukan

Saad
sumber
9

Saya berakhir dengan solusi ini. Seperti yang kita ketuk metode viewDidDisappear tombol kembali disebut. kita bisa memeriksa dengan memanggil pemilih isMovingFromParentViewController yang mengembalikan nilai true. kami dapat mengirimkan data kembali (Menggunakan Delegasi). semoga ini membantu seseorang.

-(void)viewDidDisappear:(BOOL)animated{

    if (self.isMovingToParentViewController) {

    }
    if (self.isMovingFromParentViewController) {
       //moving back
        //pass to viewCollection delegate and update UI
        [self.delegateObject passBackSavedData:self.dataModel];

    }
}
Avijit Nagare
sumber
Jangan lupa[super viewDidDisappear:animated]
SamB
9

Mungkin agak terlambat, tapi saya juga menginginkan perilaku yang sama sebelumnya. Dan solusi yang saya gunakan berfungsi dengan cukup baik di salah satu aplikasi yang saat ini ada di App Store. Karena saya belum melihat ada orang yang menggunakan metode serupa, saya ingin membagikannya di sini. Kelemahan dari solusi ini adalah membutuhkan subclassing UINavigationController. Meskipun menggunakan Metode Swizzling dapat membantu menghindari itu, saya tidak melangkah sejauh itu.

Jadi, tombol kembali default sebenarnya dikelola oleh UINavigationBar. Saat pengguna mengetuk tombol kembali, UINavigationBartanyakan delegasinya apakah harus memunculkan bagian atas UINavigationItemdengan menelepon navigationBar(_:shouldPop:). UINavigationControllersebenarnya menerapkan ini, tetapi tidak secara terbuka menyatakan bahwa ia mengadopsi UINavigationBarDelegate(mengapa !?). Untuk menghentikan acara ini, buat subkelas dari UINavigationController, nyatakan kesesuaiannya, UINavigationBarDelegatedan implementasikan navigationBar(_:shouldPop:). Kembalikan truejika item teratas harus muncul. Kembalikan falsejika harus tinggal.

Ada dua masalah. Yang pertama adalah Anda harus memanggil UINavigationControllerversi navigationBar(_:shouldPop:)di beberapa titik. Tetapi UINavigationBarControllertidak secara terbuka menyatakan kesesuaiannya UINavigationBarDelegate, mencoba memanggilnya akan menghasilkan kesalahan waktu kompilasi. Solusi yang saya gunakan adalah dengan menggunakan runtime Objective-C untuk mendapatkan implementasi secara langsung dan memanggilnya. Tolong beritahu saya jika ada yang punya solusi yang lebih baik.

Masalah lainnya adalah yang navigationBar(_:shouldPop:)dipanggil pertama diikuti oleh popViewController(animated:)jika pengguna mengetuk tombol kembali. Urutan berbalik jika pengontrol tampilan dimunculkan dengan memanggil popViewController(animated:). Dalam hal ini, saya menggunakan boolean untuk mendeteksi jika popViewController(animated:)dipanggil sebelumnya navigationBar(_:shouldPop:)yang berarti pengguna telah mengetuk tombol kembali.

Juga, saya membuat perpanjangan UIViewControlleruntuk membiarkan pengontrol navigasi menanyakan pengontrol tampilan apakah itu harus muncul jika pengguna mengetuk tombol kembali. Pengontrol tampilan dapat kembali falsedan melakukan tindakan yang diperlukan dan menelepon popViewController(animated:)nanti.

class InterceptableNavigationController: UINavigationController, UINavigationBarDelegate {
    // If a view controller is popped by tapping on the back button, `navigationBar(_:, shouldPop:)` is called first follows by `popViewController(animated:)`.
    // If it is popped by calling to `popViewController(animated:)`, the order reverses and we need this flag to check that.
    private var didCallPopViewController = false

    override func popViewController(animated: Bool) -> UIViewController? {
        didCallPopViewController = true
        return super.popViewController(animated: animated)
    }

    func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
        // If this is a subsequence call after `popViewController(animated:)`, we should just pop the view controller right away.
        if didCallPopViewController {
            return originalImplementationOfNavigationBar(navigationBar, shouldPop: item)
        }

        // The following code is called only when the user taps on the back button.

        guard let vc = topViewController, item == vc.navigationItem else {
            return false
        }

        if vc.shouldBePopped(self) {
            return originalImplementationOfNavigationBar(navigationBar, shouldPop: item)
        } else {
            return false
        }
    }

    func navigationBar(_ navigationBar: UINavigationBar, didPop item: UINavigationItem) {
        didCallPopViewController = false
    }

    /// Since `UINavigationController` doesn't publicly declare its conformance to `UINavigationBarDelegate`,
    /// trying to called `navigationBar(_:shouldPop:)` will result in a compile error.
    /// So, we'll have to use Objective-C runtime to directly get super's implementation of `navigationBar(_:shouldPop:)` and call it.
    private func originalImplementationOfNavigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
        let sel = #selector(UINavigationBarDelegate.navigationBar(_:shouldPop:))
        let imp = class_getMethodImplementation(class_getSuperclass(InterceptableNavigationController.self), sel)
        typealias ShouldPopFunction = @convention(c) (AnyObject, Selector, UINavigationBar, UINavigationItem) -> Bool
        let shouldPop = unsafeBitCast(imp, to: ShouldPopFunction.self)
        return shouldPop(self, sel, navigationBar, item)
    }
}

extension UIViewController {
    @objc func shouldBePopped(_ navigationController: UINavigationController) -> Bool {
        return true
    }
}

Dan dalam Anda melihat pengontrol, implementasikan shouldBePopped(_:). Jika Anda tidak menerapkan metode ini, perilaku default-nya adalah memunculkan pengontrol tampilan segera setelah pengguna mengetuk tombol kembali seperti biasa.

class MyViewController: UIViewController {
    override func shouldBePopped(_ navigationController: UINavigationController) -> Bool {
        let alert = UIAlertController(title: "Do you want to go back?",
                                      message: "Do you really want to go back? Tap on \"Yes\" to go back. Tap on \"No\" to stay on this screen.",
                                      preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "No", style: .cancel, handler: nil))
        alert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { _ in
            navigationController.popViewController(animated: true)
        }))
        present(alert, animated: true, completion: nil)
        return false
    }
}

Anda dapat melihat demo saya di sini .

masukkan deskripsi gambar di sini

yusuke024
sumber
Ini adalah solusi yang luar biasa dan harus dimasukkan ke dalam posting blog! Tampaknya berlebihan untuk apa yang saya cari sekarang, tetapi dalam keadaan lain, ini pasti patut dicoba.
ASSeeger
6

Untuk "SEBELUM memunculkan tampilan dari tumpukan":

- (void)willMoveToParentViewController:(UIViewController *)parent{
    if (parent == nil){
        NSLog(@"do whatever you want here");
    }
}
Anum Malik
sumber
5

Ada cara yang lebih tepat daripada meminta viewControllers. Anda dapat membuat pengontrol Anda menjadi delegasi navigationBar yang memiliki tombol kembali. Berikut contohnya. Dalam implementasi pengontrol di mana Anda ingin menangani penekanan tombol kembali, beri tahu bahwa itu akan mengimplementasikan protokol UINavigationBarDelegate:

@interface MyViewController () <UINavigationBarDelegate>

Kemudian di suatu tempat di kode inisialisasi Anda (mungkin dalam viewDidLoad) jadikan pengontrol Anda sebagai delegasi bilah navigasinya:

self.navigationController.navigationBar.delegate = self;

Terakhir, terapkan metode shouldPopItem. Metode ini dipanggil tepat saat tombol kembali ditekan. Jika Anda memiliki beberapa pengontrol atau Item navigasi dalam tumpukan, Anda mungkin ingin memeriksa item navigasi mana yang muncul (parameter item), sehingga Anda hanya melakukan hal-hal khusus saat Anda mengharapkannya. Berikut contohnya:

-(BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
    NSLog(@"Back button got pressed!");
    //if you return NO, the back button press is cancelled
    return YES;
}
Carlos Guzman
sumber
4
tidak berhasil untuk saya .. menyebalkan karena ramping. "*** Menghentikan aplikasi karena pengecualian yang tidak tertangkap 'NSInternalInconsistencyException', alasan: 'Tidak dapat menyetel delegasi secara manual di UINavigationBar yang dikelola oleh pengontrol.'"
DynamicDan
Sayangnya, ini tidak akan berfungsi dengan UINavigationController, sebagai gantinya, Anda memerlukan UIViewController standar dengan UINavigationBar di dalamnya. Ini berarti Anda tidak dapat memanfaatkan beberapa viewcontroller otomatis yang mendorong dan memunculkan yang diberikan NavigationController kepada Anda. Maaf!
Carlos Guzman
Saya hanya menggunakan UINavigationBar sebagai ganti NavigationBarController dan kemudian berfungsi dengan baik. Saya tahu pertanyaannya adalah tentang NavigationBarController, tetapi solusi ini ramping.
appsunited
3

Jika Anda tidak dapat menggunakan "viewWillDisappear" atau metode serupa, coba subkelas UINavigationController. Ini adalah kelas header:

#import <Foundation/Foundation.h>
@class MyViewController;

@interface CCNavigationController : UINavigationController

@property (nonatomic, strong) MyViewController *viewController;

@end

Kelas implementasi:

#import "CCNavigationController.h"
#import "MyViewController.h"

@implementation CCNavigationController {

}
- (UIViewController *)popViewControllerAnimated:(BOOL)animated {
    @"This is the moment for you to do whatever you want"
    [self.viewController doCustomMethod];
    return [super popViewControllerAnimated:animated];
}

@end

Di sisi lain, Anda perlu menautkan viewController ini ke NavigationController kustom Anda, jadi, dalam metode viewDidLoad untuk viewController reguler Anda, lakukan ini:

@implementation MyViewController {
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        ((CCNavigationController*)self.navigationController).viewController = self;
    }
}
George Harley
sumber
3

Berikut cara lain yang saya terapkan (tidak mengujinya dengan segue rileks tetapi mungkin tidak akan membedakan, seperti yang dinyatakan orang lain sehubungan dengan solusi lain di halaman ini) agar pengontrol tampilan induk melakukan tindakan sebelum anak VC yang didorongnya muncul dari tumpukan tampilan (saya menggunakan ini beberapa level di bawah dari UINavigationController asli). Ini juga dapat digunakan untuk melakukan tindakan sebelum childVC didorong juga. Ini memiliki keuntungan tambahan untuk bekerja dengan tombol kembali sistem iOS, daripada harus membuat UIBarButtonItem atau UIButton kustom.

  1. Minta VC orang tua Anda untuk mengadopsi UINavigationControllerDelegateprotokol dan mendaftar untuk pesan delegasi:

    MyParentViewController : UIViewController <UINavigationControllerDelegate>
    
    -(void)viewDidLoad {
        self.navigationcontroller.delegate = self;
    }
  2. Terapkan UINavigationControllerDelegatemetode contoh ini di MyParentViewController:

    - (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
        // Test if operation is a pop; can also test for a push (i.e., do something before the ChildVC is pushed
        if (operation == UINavigationControllerOperationPop) {
            // Make sure it's the child class you're looking for
            if ([fromVC isKindOfClass:[ChildViewController class]]) {
                // Can handle logic here or send to another method; can also access all properties of child VC at this time
                return [self didPressBackButtonOnChildViewControllerVC:fromVC];
            }
        }
        // If you don't want to specify a nav controller transition
        return nil;
    }
  3. Jika Anda menentukan fungsi panggilan balik tertentu dalam UINavigationControllerDelegatemetode contoh di atas

    -(id <UIViewControllerAnimatedTransitioning>)didPressBackButtonOnAddSearchRegionsVC:(UIViewController *)fromVC {
        ChildViewController *childVC = ChildViewController.new;
        childVC = (ChildViewController *)fromVC;
    
        // childVC.propertiesIWantToAccess go here
    
        // If you don't want to specify a nav controller transition
        return nil;

    }

Evan R
sumber
1

Inilah yang berhasil untuk saya di Swift:

override func viewWillDisappear(_ animated: Bool) {
    if self.navigationController?.viewControllers.index(of: self) == nil {
        // back button pressed or back gesture performed
    }

    super.viewWillDisappear(animated)
}
pableiros.dll
sumber
0

Jika Anda menggunakan Storyboard dan Anda berasal dari segmen push, Anda juga bisa mengganti shouldPerformSegueWithIdentifier:sender:.

Mojo66
sumber