Mendeteksi saat tombol 'kembali' ditekan di bilah navigasi

135

Saya perlu melakukan beberapa tindakan ketika tombol kembali (kembali ke layar sebelumnya, kembali ke tampilan orang tua) ditekan di Navbar.

Apakah ada beberapa metode yang dapat saya terapkan untuk menangkap peristiwa dan menjalankan beberapa tindakan untuk menjeda dan menyimpan data sebelum layar menghilang?

ewok
sumber
1
Lihatlah solusi di utas ini
Jiri Volejnik
Saya melakukannya dengan cara ini, tunjukkan keputusan di sini
Taras

Jawaban:

317

PEMBARUAN: Menurut beberapa komentar, solusi dalam jawaban asli tampaknya tidak berfungsi dalam skenario tertentu di iOS 8+. Saya tidak dapat memverifikasi bahwa itu sebenarnya terjadi tanpa rincian lebih lanjut.

Namun bagi Anda dalam situasi itu ada alternatif. Mendeteksi saat pengontrol tampilan dimunculkan dimungkinkan dengan mengganti willMove(toParentViewController:). Ide dasarnya adalah bahwa pengontrol tampilan akan muncul saat parentitu nil.

Lihat "Menerapkan Pengontrol Tampilan Penampung" untuk detail lebih lanjut.


Sejak iOS 5, saya telah menemukan bahwa cara termudah untuk menangani situasi ini adalah menggunakan metode baru - (BOOL)isMovingFromParentViewController:

- (void)viewWillDisappear:(BOOL)animated {
  [super viewWillDisappear:animated];

  if (self.isMovingFromParentViewController) {
    // Do your stuff here
  }
}

- (BOOL)isMovingFromParentViewController masuk akal saat Anda mendorong dan memunculkan pengontrol dalam tumpukan navigasi.

Namun, jika Anda menyajikan pengontrol tampilan modal, Anda harus menggunakan - (BOOL)isBeingDismissedsebagai gantinya:

- (void)viewWillDisappear:(BOOL)animated {
  [super viewWillDisappear:animated];

  if (self.isBeingDismissed) {
    // Do your stuff here
  }
}

Seperti disebutkan dalam pertanyaan ini , Anda dapat menggabungkan kedua properti:

- (void)viewWillDisappear:(BOOL)animated {
  [super viewWillDisappear:animated];

  if (self.isMovingFromParentViewController || self.isBeingDismissed) {
    // Do your stuff here
  }
}

Solusi lain bergantung pada keberadaan a UINavigationBar. Alih-alih menyukai pendekatan saya lebih karena itu memisahkan tugas yang diperlukan untuk melakukan dari tindakan yang memicu acara, yaitu menekan tombol kembali.

elitalon.dll
sumber
Saya suka Anda menjawab. Tetapi mengapa Anda menggunakan 'self.isBeingDismissed'? Dalam kasus saya, pernyataan di 'self.isBeingDismissed' tidak diterapkan.
Rutvij Kotecha
3
self.isMovingFromParentViewControllermemiliki nilai TRUE ketika saya memunculkan tumpukan navigasi secara terprogram menggunakan popToRootViewControllerAnimated- tanpa sentuhan pada tombol kembali. Haruskah saya meremehkan jawaban Anda? (subjek mengatakan "tombol 'kembali' ditekan di bilah navigasi")
kas-kad
2
Jawaban yang bagus, terima kasih banyak. Di Swift saya menggunakan:override func viewWillDisappear(animated: Bool) { super.viewWillDisappear(animated) if isMovingFromParentViewController(){ println("back button pressed") } }
Camillo
2
Anda hanya boleh melakukan ini di dalam -viewDidDisappear:karena mungkin saja Anda akan mendapatkan -viewWillDisappear:tanpa -viewDidDisappear:(seperti saat Anda mulai menggesek untuk menutup item pengontrol navigasi dan kemudian membatalkan gesekan itu.
Heath Borders
3
Sepertinya bukan solusi yang dapat diandalkan lagi. Bekerja pada saat saya pertama kali menggunakan ini (itu adalah iOS 10). Tapi sekarang saya tidak sengaja menemukannya dengan tenang berhenti bekerja (iOS 11). Harus beralih ke solusi "willMove (toParentViewController)".
Vitalii
101

Sementara viewWillAppear()dan viewDidDisappear() yang disebut ketika tombol kembali disadap, mereka juga disebut di lain waktu. Lihat akhir jawaban untuk lebih lanjut tentang itu.

Menggunakan UIViewController.parent

Mendeteksi tombol kembali lebih baik dilakukan ketika VC dihapus dari induknya (NavigationController) dengan bantuan willMoveToParentViewController(_:)ORdidMoveToParentViewController()

Jika induknya nihil, pengontrol tampilan akan dikeluarkan dari tumpukan navigasi dan ditutup. Jika induk bukan nol, itu akan ditambahkan ke tumpukan dan disajikan.

// Objective-C
-(void)willMoveToParentViewController:(UIViewController *)parent {
     [super willMoveToParentViewController:parent];
    if (!parent){
       // The back button was pressed or interactive gesture used
    }
}


// Swift
override func willMove(toParent parent: UIViewController?) {
    super.willMove(toParent: parent)
    if parent == nil {
        // The back button was pressed or interactive gesture used
    }
}

Swap keluar willMoveuntuk didMovedan cek self.parent untuk melakukan pekerjaan setelah pengontrol tampilan diberhentikan.

Menghentikan penutupan

Perhatikan, memeriksa induk tidak memungkinkan Anda untuk "menjeda" transisi jika Anda perlu melakukan semacam penyimpanan asinkron. Untuk melakukan itu, Anda dapat menerapkan yang berikut ini. Satunya downside di sini adalah Anda kehilangan tombol kembali bergaya / animasi iOS yang mewah. Berhati-hatilah juga di sini dengan gerakan gesek interaktif. Gunakan yang berikut untuk menangani kasus ini.

var backButton : UIBarButtonItem!

override func viewDidLoad() {
    super.viewDidLoad()

     // Disable the swipe to make sure you get your chance to save
     self.navigationController?.interactivePopGestureRecognizer.enabled = false

     // Replace the default back button
    self.navigationItem.setHidesBackButton(true, animated: false)
    self.backButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.Plain, target: self, action: "goBack")
    self.navigationItem.leftBarButtonItem = backButton
}

// Then handle the button selection
func goBack() {
    // Here we just remove the back button, you could also disabled it or better yet show an activityIndicator
    self.navigationItem.leftBarButtonItem = nil
    someData.saveInBackground { (success, error) -> Void in
        if success {
            self.navigationController?.popViewControllerAnimated(true)
            // Don't forget to re-enable the interactive gesture
            self.navigationController?.interactivePopGestureRecognizer.enabled = true
        }
        else {
            self.navigationItem.leftBarButtonItem = self.backButton
            // Handle the error
        }
    }
}


Lebih lanjut tentang tampilan akan / memang muncul

Jika Anda tidak mendapatkan viewWillAppear viewDidDisappearmasalah, mari kita lihat contoh. Katakanlah Anda memiliki tiga pengontrol tampilan:

  1. ListVC: Tampilan tabel hal-hal
  2. DetailVC: Detail tentang suatu hal
  3. SettingsVC: Beberapa opsi untuk sesuatu

Mari kita ikuti panggilan di detailVCsaat Anda pergi dari listVCke settingsVCdan kembali kelistVC

List> Detail (push detailVC) Detail.viewDidAppear<- muncul
Detail> Settings (push settingsVC) Detail.viewDidDisappear<- menghilang

Dan saat kita kembali ...
Settings> Detail (pop settingsVC) Detail.viewDidAppear<- muncul
Detail> List (pop detailVC) Detail.viewDidDisappear<- menghilang

Perhatikan bahwa viewDidDisappeardipanggil beberapa kali, tidak hanya saat mundur, tetapi juga saat maju. Untuk operasi cepat yang mungkin diinginkan, tetapi untuk operasi yang lebih kompleks seperti menyimpan panggilan jaringan, mungkin tidak.

WCByrne
sumber
Sekadar catatan, pengguna didMoveToParantViewController:harus melakukan pekerjaan saat tampilan tidak lagi terlihat. Bermanfaat untuk iOS7 dengan
Gesutre
didMoveToParentViewController * ada kesalahan ketik
thewormsterror
Jangan lupa untuk memanggil [super willMoveToParentViewController: parent]!
ScottyB
2
Parameter induk adalah nihil saat Anda membuka pengontrol tampilan induk, dan bukan nil saat tampilan metode ini muncul sedang ditampilkan. Anda dapat menggunakan fakta itu untuk melakukan tindakan hanya saat tombol Kembali ditekan, dan bukan saat membuka tampilan. Bagaimanapun, itu adalah pertanyaan asli. :)
Mike
1
Ini juga dipanggil saat menggunakan secara terprogram _ = self.navigationController?.popViewController(animated: true), jadi tidak hanya dipanggil saat menekan tombol Kembali. Saya mencari panggilan yang hanya berfungsi saat Kembali ditekan.
Ethan Allen
16

Metode Pertama

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

Metode Kedua

-(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];
}
AndroidGeek
sumber
1
Metode kedua adalah satu-satunya yang berhasil untuk saya. Metode pertama juga dipanggil berdasarkan tampilan saya yang disajikan, yang tidak dapat diterima untuk kasus penggunaan saya.
marcshilling
11

Mereka yang mengklaim bahwa ini tidak berhasil adalah salah:

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    if self.isMovingFromParent {
        print("we are being popped")
    }
}

Itu bekerja dengan baik. Jadi apa yang menyebabkan mitos tersebar luas bahwa hal itu tidak terjadi?

Masalahnya tampaknya karena implementasi yang salah dari metode yang berbeda , yaitu implementasi willMove(toParent:)lupa menelepon super.

Jika Anda menerapkan willMove(toParent:)tanpa memanggil super, maka self.isMovingFromParentakan falsedan penggunaan viewWillDisappearakan tampak gagal. Itu tidak gagal; Anda memecahkannya.

CATATAN: Masalah sebenarnya biasanya adalah pengontrol tampilan kedua yang mendeteksi bahwa pengontrol tampilan pertama muncul. Silakan lihat juga pembahasan yang lebih umum di sini: Unified UIViewController "menjadi terdepan" deteksi?

EDIT Sebuah komentar menyarankan bahwa ini harus viewDidDisappeardaripada viewWillDisappear.

Matt
sumber
Kode ini dijalankan ketika tombol kembali diketuk, tetapi juga dijalankan jika VC muncul secara terprogram.
biomiker
1
@biomiker Tentu, tapi itu juga berlaku untuk pendekatan lainnya. Popping sedang bermunculan. Pertanyaannya adalah bagaimana cara mendeteksi pop ketika Anda tidak muncul secara terprogram. Jika Anda pop secara terprogram Anda sudah tahu Anda sedang bermunculan sehingga tidak ada yang terdeteksi.
matt
Ya, ini berlaku untuk beberapa pendekatan lainnya dan banyak di antaranya memiliki komentar serupa. Saya hanya mengklarifikasi karena ini adalah jawaban baru-baru ini dengan bantahan tertentu dan saya mendapatkan harapan saya ketika saya membacanya. Sebagai catatan, pertanyaannya adalah bagaimana mendeteksi penekanan tombol kembali. Ini adalah argumen yang masuk akal untuk mengatakan bahwa kode yang juga akan dijalankan dalam situasi di mana tombol kembali tidak ditekan, tanpa menunjukkan apakah tombol kembali ditekan atau tidak, tidak sepenuhnya menyelesaikan pertanyaan sebenarnya, bahkan jika mungkin pertanyaannya bisa lebih dari itu. eksplisit tentang hal itu.
biomiker
1
Sayangnya ini mengembalikan trueuntuk gerakan pop gesek interaktif - dari tepi kiri pengontrol tampilan - bahkan jika gesekan tidak sepenuhnya meletuskannya. Jadi, alih-alih memeriksanya willDisappear, melakukannya di tempat didDisappearkerja.
badhanganesh
1
@badhanganesh Terima kasih, jawaban yang diedit untuk menyertakan info itu.
matt
9

Saya telah bermain (atau berkelahi) dengan masalah ini selama dua hari. IMO pendekatan terbaik hanya dengan membuat kelas ekstensi dan protokol, seperti ini:

@protocol UINavigationControllerBackButtonDelegate <NSObject>
/**
 * Indicates that the back button was pressed.
 * If this message is implemented the pop logic must be manually handled.
 */
- (void)backButtonPressed;
@end

@interface UINavigationController(BackButtonHandler)
@end

@implementation UINavigationController(BackButtonHandler)
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
    UIViewController *topViewController = self.topViewController;
    BOOL wasBackButtonClicked = topViewController.navigationItem == item;
    SEL backButtonPressedSel = @selector(backButtonPressed);
    if (wasBackButtonClicked && [topViewController respondsToSelector:backButtonPressedSel]) {
        [topViewController performSelector:backButtonPressedSel];
        return NO;
    }
    else {
        [self popViewControllerAnimated:YES];
        return YES;
    }
}
@end

Ini berfungsi karena UINavigationControllerakan menerima panggilan ke navigationBar:shouldPopItem:setiap kali pengontrol tampilan muncul. Di sana kami mendeteksi apakah kembali ditekan atau tidak (tombol lain). Satu-satunya hal yang harus Anda lakukan adalah mengimplementasikan protokol dalam pengontrol tampilan di mana tombol kembali ditekan.

Ingatlah untuk memasukkan pengontrol tampilan secara manual ke dalam backButtonPressedSel, jika semuanya baik-baik saja.

Jika Anda sudah membuat subclass UINavigationViewControllerdan mengimplementasikan navigationBar:shouldPopItem:jangan khawatir, ini tidak akan mengganggu.

Anda mungkin juga tertarik untuk menonaktifkan gerakan punggung.

if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
    self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
7ynk3r
sumber
1
Jawaban ini hampir lengkap untuk saya, kecuali saya menemukan bahwa 2 viewcontrollers sering muncul. Mengembalikan YES menyebabkan metode panggilan memanggil pop, jadi memanggil pop juga berarti bahwa 2 viewcontrollers akan muncul. Lihat jawaban ini pada pertanyaan lain untuk lebih banyak deets (jawaban sangat bagus yang membutuhkan lebih banyak suara positif): stackoverflow.com/a/26084150/978083
Jason Ridge
Poin bagus, uraian saya tidak jelas tentang fakta itu. "Ingatlah untuk secara manual memunculkan pengontrol tampilan jika semuanya baik-baik saja" itu hanya untuk kasus mengembalikan "TIDAK", jika tidak alirannya adalah sembulan normal.
7ynk3r
1
Untuk cabang "lain ', Lebih baik memanggil implementasi super jika Anda tidak ingin menangani pop sendiri dan membiarkannya mengembalikan apa pun yang dianggapnya benar, yang sebagian besar YA, tetapi juga menangani pop itu sendiri kemudian dan menganimasikan chevron dengan benar .
pronebird
9

Ini berfungsi untuk saya di iOS 9.3.x dengan Swift:

override func didMoveToParentViewController(parent: UIViewController?) {
    super.didMoveToParentViewController(parent)

    if parent == self.navigationController?.parentViewController {
        print("Back tapped")
    }
}

Tidak seperti solusi lain di sini, ini sepertinya tidak memicu secara tak terduga.

Chris Villa
sumber
lebih baik menggunakan willMove sebagai gantinya
Eugene Gordin
4

Sebagai catatan, menurut saya ini lebih dari apa yang dia cari…

    UIBarButtonItem *l_backButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRewind target:self action:@selector(backToRootView:)];

    self.navigationItem.leftBarButtonItem = l_backButton;


    - (void) backToRootView:(id)sender {

        // Perform some custom code

        [self.navigationController popToRootViewControllerAnimated:YES];
    }
Paul Brady
sumber
1
Terima kasih Paul, solusi ini cukup sederhana. Sayangnya, ikonnya berbeda. Ini adalah ikon "mundur", bukan ikon kembali. Mungkin ada cara untuk menggunakan ikon belakang ...
Ferran Maylinch
3

Anda dapat menggunakan tombol panggil balik, seperti ini:

- (BOOL) navigationShouldPopOnBackButton
{
    [self backAction];
    return NO;
}

- (void) backAction {
    // your code goes here
    // show confirmation alert, for example
    // ...
}

untuk versi cepat Anda dapat melakukan sesuatu seperti dalam lingkup global

extension UIViewController {
     @objc func navigationShouldPopOnBackButton() -> Bool {
     return true
    }
}

extension UINavigationController: UINavigationBarDelegate {
     public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
          return self.topViewController?.navigationShouldPopOnBackButton() ?? true
    }
}

Di bawah yang Anda letakkan di viewcontroller tempat Anda ingin mengontrol aksi tombol kembali:

override func navigationShouldPopOnBackButton() -> Bool {
    self.backAction()//Your action you want to perform.

    return true
}
Pedro Magalhães
sumber
1
Tidak tahu mengapa seseorang memilih. Ini tampaknya merupakan jawaban terbaik sejauh ini.
Avinash
@Avinash Dari mana navigationShouldPopOnBackButtonasalnya? Ini bukan bagian dari API publik.
elitalon
@elitalon Maaf, ini setengah jawaban. Saya pikir konteks yang tersisa ada di sana. Pokoknya telah memperbarui jawabannya sekarang
Avinash
2

Seperti yang purrrminatordikatakan, jawaban oleh elitalontidak sepenuhnya benar, karena your stuffakan dijalankan bahkan ketika memunculkan pengontrol secara terprogram.

Solusi yang saya temukan sejauh ini tidak terlalu bagus, tetapi berhasil untuk saya. Selain apa yang elitalondikatakan, saya juga memeriksa apakah saya muncul secara terprogram atau tidak:

- (void)viewWillDisappear:(BOOL)animated {
  [super viewWillDisappear:animated];

  if ((self.isMovingFromParentViewController || self.isBeingDismissed)
      && !self.isPoppingProgrammatically) {
    // Do your stuff here
  }
}

Anda harus menambahkan properti itu ke pengontrol Anda dan mengaturnya ke YES sebelum muncul secara terprogram:

self.isPoppingProgrammatically = YES;
[self.navigationController popViewControllerAnimated:YES];

Terima kasih atas bantuan Anda!

Ferran Maylinch
sumber
2

Cara terbaik adalah dengan menggunakan metode delegasi UINavigationController

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated

Dengan menggunakan ini, Anda dapat mengetahui pengontrol apa yang menampilkan UINavigationController.

if ([viewController isKindOfClass:[HomeController class]]) {
    NSLog(@"Show home controller");
}
Harald
sumber
Ini harus ditandai sebagai jawaban yang benar! Mungkin juga ingin menambahkan satu baris lagi hanya untuk mengingatkan orang -> self.navigationController.delegate = self;
Mike Critchley
2

Saya telah memecahkan masalah ini dengan menambahkan UIControl ke navigationBar di sisi kiri.

UIControl *leftBarItemControl = [[UIControl alloc] initWithFrame:CGRectMake(0, 0, 90, 44)];
[leftBarItemControl addTarget:self action:@selector(onLeftItemClick:) forControlEvents:UIControlEventTouchUpInside];
self.leftItemControl = leftBarItemControl;
[self.navigationController.navigationBar addSubview:leftBarItemControl];
[self.navigationController.navigationBar bringSubviewToFront:leftBarItemControl];

Dan Anda harus ingat untuk menghapusnya saat tampilan akan hilang:

- (void) viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    if (self.leftItemControl) {
        [self.leftItemControl removeFromSuperview];
    }    
}

Itu saja!

Eric
sumber
1

Anda harus memeriksa Protokol UINavigationBarDelegate . Dalam hal ini Anda mungkin ingin menggunakan metode navigationBar: shouldPopItem:.

Coli88
sumber
1

Seperti yang dikatakan Coli88, Anda harus memeriksa protokol UINavigationBarDelegate.

Dalam cara yang lebih umum, Anda juga dapat menggunakan - (void)viewWillDisapear:(BOOL)animateduntuk melakukan pekerjaan kustom saat tampilan yang dipertahankan oleh pengontrol tampilan yang terlihat saat ini akan menghilang. Sayangnya, cover ini akan mengganggu push dan pop case.

ramdam
sumber
1

Untuk Swift dengan UINavigationController:

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(animated)
    if self.navigationController?.topViewController != self {
        print("back button tapped")
    }
}
Murray Sagal
sumber
1

Jawaban 7ynk3r sangat dekat dengan apa yang saya gunakan pada akhirnya tetapi perlu beberapa penyesuaian:

- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {

    UIViewController *topViewController = self.topViewController;
    BOOL wasBackButtonClicked = topViewController.navigationItem == item;

    if (wasBackButtonClicked) {
        if ([topViewController respondsToSelector:@selector(navBackButtonPressed)]) {
            // if user did press back on the view controller where you handle the navBackButtonPressed
            [topViewController performSelector:@selector(navBackButtonPressed)];
            return NO;
        } else {
            // if user did press back but you are not on the view controller that can handle the navBackButtonPressed
            [self popViewControllerAnimated:YES];
            return YES;
        }
    } else {
        // when you call popViewController programmatically you do not want to pop it twice
        return YES;
    }
}
micromanc3r
sumber
0

self.navigationController.isMovingFromParentViewController tidak berfungsi lagi di iOS8 dan 9 saya menggunakan:

-(void) viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    if (self.navigationController.topViewController != self)
    {
        // Is Popping
    }
}
Vassily
sumber
-1

(CEPAT)

solusi yang akhirnya ditemukan .. metode yang kami cari adalah "willShowViewController" yang merupakan metode delegasi dari UINavigationController

//IMPORT UINavigationControllerDelegate !!
class PushedController: UIViewController, UINavigationControllerDelegate {

    override func viewDidLoad() {
        //set delegate to current class (self)
        navigationController?.delegate = self
    }

    func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
        //MyViewController shoud be the name of your parent Class
        if var myViewController = viewController as? MyViewController {
            //YOUR STUFF
        }
    }
}
Jiří Zahálka
sumber
1
Masalahnya dengan pendekatan ini adalah bahwa hal itu pasangan MyViewControlleruntuk PushedController.
clozach