Gezinme çubuğunda 'geri' düğmesine basıldığını algılama


135

Bir Navbar'da geri düğmesine (önceki ekrana dön, üst görünüme dön) düğmesine basıldığında bazı işlemler yapmam gerekiyor.

Olayı yakalamak ve ekran kaybolmadan önce verileri duraklatmak ve kaydetmek için bazı eylemleri tetiklemek için uygulayabileceğim bir yöntem var mı?




Yanıtlar:


316

GÜNCELLEME: Bazı yorumlara göre, orijinal yanıttaki çözüm iOS 8+'daki belirli senaryolar altında çalışmıyor gibi görünüyor. Daha fazla ayrıntı olmadan durumun bu olduğunu doğrulayamıyorum.

Ancak bu durumda sizler için bir alternatif var. Bir görünüm denetleyicisinin ne zaman atlatıldığını algılamak geçersiz kılma yoluyla mümkündür willMove(toParentViewController:). Temel fikir ne zaman bir görünüm denetleyicisi attı ediliyor olmasıdır parentolduğunu nil.

Check out "a Konteyner View Controller uygulanması" Daha fazla detay için.


İOS 5'ten beri, bu durumla başa çıkmanın en kolay yolunun yeni yöntemi kullanmak olduğunu keşfettim - (BOOL)isMovingFromParentViewController:

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

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

- (BOOL)isMovingFromParentViewController gezinme yığınındaki denetleyicileri ittiğinizde ve patlattığınızda mantıklıdır.

Ancak, kalıcı görünüm denetleyicileri sunuyorsanız, - (BOOL)isBeingDismissedbunun yerine şunları kullanmalısınız :

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

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

Bu soruda belirtildiği gibi , her iki özelliği de birleştirebilirsiniz:

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

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

Diğer çözümler a UINavigationBar. Bunun yerine benim yaklaşımım gibi daha çok olayı tetikleyen eylemden gerçekleştirmek için gerekli görevleri ayrıştırır, yani geri düğmesine basarak.


Cevabını beğeniyorum. Ama neden 'self.isBeingDismissed' kullandınız? Benim durumumda, 'self.isBeingDismissed' ifadeleri uygulanmıyor.
Rutvij Kotecha

3
self.isMovingFromParentViewControllergezinme yığınını programsal olarak kullanarak popToRootViewControllerAnimated- geri düğmesine dokunmadan DOĞRU değere sahip . Cevabınızı küçümsemeli miyim? (konu "gezinme çubuğunda" geri "düğmesine basıldığını söylüyor)
kas-kad

2
Müthiş cevap, çok teşekkür ederim. Swift'te kullandım:override func viewWillDisappear(animated: Bool) { super.viewWillDisappear(animated) if isMovingFromParentViewController(){ println("back button pressed") } }
Camillo Visini

1
Yalnızca içinde yapmanız gereken -viewDidDisappear:size bir olsun duyma ihtimaliniz beri -viewWillDisappear:bir olmadan -viewDidDisappear:o tokatlamak bir navigasyon kontrolörü öğesini kapatmak için kaydırarak başladığınızda gibi (ve sonra iptal.
Heath Borders

3
Artık güvenilir bir çözüm değil gibi görünüyor. Bunu ilk kullandığımda çalıştım (iOS 10'du). Ancak şimdi yanlışlıkla çalışmayı durdurduğunu gördüm (iOS 11). "WillMove (toParentViewController)" çözümüne geçmek zorunda kaldı.
Vitalii

100

İken viewWillAppear()ve viewDidDisappear() vardır geri düğmesi dokunulduğunda denilen, onlar da diğer zamanlarda denir. Bununla ilgili daha fazla bilgi için yanıtın sonuna bakın.

UIViewController.parent kullanma

VC, ana öğesinden (NavigationController) willMoveToParentViewController(_:)VEYA yardımıyla kaldırıldığında geri düğmesinin algılanması daha iyi olurdidMoveToParentViewController()

Üst öğe nil ise, görünüm denetleyicisi gezinti yığınından çıkarılır ve kapatılır. Ebeveyn sıfır değilse, yığına eklenir ve sunulur.

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

Dışarı takas willMoveiçin didMoveve onay self.parent işi yapmak için sonra görünümü denetleyicisi atlamamış.

İşten çıkarmayı durdurma

Unutmayın, bir tür zaman uyumsuz kayıt yapmanız gerekiyorsa üst öğeyi kontrol etmek geçişi "duraklatmanıza" izin vermez. Bunu yapmak için aşağıdakileri uygulayabilirsiniz. Burada sadece olumsuz süslü iOS tarz / animasyonlu geri düğmesini kaybetmek. Ayrıca burada etkileşimli kaydırma hareketi ile dikkatli olun. Bu durumu ele almak için aşağıdakileri kullanın.

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


Görüntüleme hakkında daha fazla bilgi görünecek

viewWillAppear viewDidDisappearSorunu almadıysanız , bir örnek inceleyelim. Üç görünüm denetleyiciniz olduğunu varsayalım:

  1. ListVC: Şeylerin tablo görünümü
  2. DetailVC: Bir şey hakkında ayrıntılar
  3. AyarlarVC: Bir şey için bazı seçenekler

Aramaları izleyin Lets detailVCsen gitmek gibi listVChiç settingsVCve gerilistVC

Liste> Ayrıntı (itme ayrıntısıVC) Detail.viewDidAppear<- ayrıntıyı görüntüle
> Ayarlar (itme ayarlarıVC) Detail.viewDidDisappear<- kaybolur

Ve geri döndüğümüzde ...
Ayarlar> Ayrıntı (pop ayarlarıVC) Detail.viewDidAppear<- Görünüyor
Ayrıntı> Liste (pop ayrıntısıVC) Detail.viewDidDisappear<- yok

viewDidDisappearSadece geri giderken değil, ileri giderken de birden çok kez çağrıldığına dikkat edin . İstenebilecek hızlı bir işlem için, ancak bir ağ çağrısı gibi daha karmaşık bir işlem için kaydedilemeyebilir.


Sadece bir not, kullanıcı didMoveToParantViewController:görünüm artık görünür olmadığında çalışması için. InteractiveGesutre ile iOS7 için yararlı
WCByrne

didMoveToParentViewController * bir yazım hatası var
thewormsterror

[Super willMoveToParentViewController: parent] öğesini aramayı unutmayın!
ScottyB

2
Üst görünüm denetleyicisine attığınızda üst parametre nil ve bu yöntemin göründüğü görünümde nil değil. Bu gerçeği, görünüme ulaşırken değil, yalnızca Geri düğmesine basıldığında bir eylem yapmak için kullanabilirsiniz. Ne de olsa asıl soru buydu. :)
Mike

1
Bu, programlı olarak kullanıldığında da çağrılır _ = self.navigationController?.popViewController(animated: true), bu nedenle yalnızca Geri düğmesine basıldığında çağrılmaz. Yalnızca Geri düğmesine basıldığında çalışan bir çağrı arıyorum .
Ethan Allen

16

İlk Yöntem

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

İkinci Yöntem

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

1
İkinci yöntem benim için işe yarayan tek yöntemdi. İlk yöntem, benim kullanım durumum için kabul edilemez olan görüşüm sunulması üzerine de çağrıldı.
marcshilling

10

Bunun işe yaramadığını iddia edenler yanılıyor:

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

Güzel çalışıyor. Öyleyse yaygın olmayan efsaneye neden olan şey nedir?

Sorun , farklı bir yöntemin yanlış bir şekilde uygulanmasından kaynaklanıyor gibi görünüyor , yani willMove(toParent:)çağrı yapmayı unuttum super.

Eğer uygularsanız willMove(toParent:)uğramadan super, daha sonra self.isMovingFromParentolacak falseve kullanımı viewWillDisappearbaşarısız görünecektir. Başarısız olmadı; onu kırdın.

NOT: Gerçek sorun genellikle ilk görünüm denetleyicisinin attığını algılayan ikinci görüntü denetleyicisidir . Lütfen burada daha genel tartışmaya da bakınız: Birleşik UIViewController "en öndeydi" tespiti?

EDIT Bir yorum bunun viewDidDisappearyerine olması gerektiğini gösterir viewWillDisappear.


Bu kod, geri düğmesine basıldığında yürütülür, ancak VC programlı olarak açılırsa da yürütülür.
biomiker

@biomiker Tabii, ama bu diğer yaklaşımlar için de geçerli. Haşhaş patlıyor. Soru, programlı olarak pop yapmadığınızda bir popun nasıl tespit edileceği . Programlı olarak pop yaparsanız, zaten patladığınızı biliyorsunuz , böylece algılanacak bir şey yok.
matt

Evet, bu diğer yaklaşımların birçoğu için de geçerlidir ve bunların birçoğunun benzer yorumları vardır. Sadece açıklığa kavuştum, çünkü bu belirli bir çürütme ile yeni bir cevaptı ve okuduğumda umutlarımı arttırdım. Yine de kayıt için soru, geri düğmesine bir basının nasıl tespit edileceği. Geri düğmesine basılmadığı durumlarda geri düğmesine basılmadığı durumlarda da yürütülecek kodun, belki de soru daha fazla olsa bile, gerçek soruyu tam olarak çözmediğini söylemek makul bir argümandır. bu noktada açık.
biomiker

1
Maalesef true, kaydırma tam olarak açılmamış olsa bile, görünüm denetleyicisinin sol kenarından gelen etkileşimli kaydırma pop hareketi için geri döner . Bunun yerine onu kontrol etmeyi willDisappear, bunu yaparken didDisappeareserleri.
badhanganesh

1
@badhanganesh Teşekkürler, bu bilgiyi eklemek için cevabı düzenledi.
matt

9

İki gündür bu problemle oynuyorum (veya savaşıyorum). IMO'nun en iyi yaklaşımı sadece bir uzantı sınıfı ve bir protokol oluşturmaktır:

@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

Bunun nedeni çalışır UINavigationControllerbir çağrı alacaksınıznavigationBar:shouldPopItem: bir görünüm denetleyicisi her açıldığında . Orada geri basıp basılmadığını tespit ediyoruz (başka bir düğme). Yapmanız gereken tek şey protokolü geri basıldığı görünüm denetleyicisine uygulamaktır.

backButtonPressedSelHer şey yolundaysa, görünüm denetleyicisini manuel olarak açmayı unutmayın .

Zaten alt sınıflara sahipseniz UINavigationViewControllerve uyguladıysanız navigationBar:shouldPopItem:endişelenmeyin, bu müdahale etmez.

Geri hareketini devre dışı bırakmak da ilginizi çekebilir.

if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
    self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}

1
Bu cevap benim için neredeyse tamamlandı, ancak 2 görüntü denetleyicisinin sık sık patlayacağını buldum. YES döndürmek, çağıran yöntemin pop çağrılmasına neden olur, bu nedenle pop çağrılması da 2 görünüm denetleyicisinin patlayacağı anlamına gelir. Daha fazla deets için başka bir soruya bu cevaba bakınız (daha fazla oyu hak eden çok iyi bir cevap): stackoverflow.com/a/26084150/978083
Jason Ridge

İyi bir nokta, açıklamam bu gerçek hakkında net değildi. "Her şey yolundaysa görünüm denetleyicisini manuel olarak açmayı unutmayın", yalnızca "HAYIR" döndürülmesi içindir, aksi takdirde akış normal pop'tur.
7ynk3r

1
"Else" dalı için, pop'unuzu kendiniz halletmek ve doğru olduğunu düşündüğü her şeyi geri döndürmek istemiyorsanız süper uygulamayı aramak daha iyidir, ki bu çoğunlukla EVET, ama aynı zamanda pop'un kendisi ile ilgilenir ve chevron'u düzgün bir şekilde canlandırır .
Ben Sinclair

9

Swift ile iOS 9.3.x'te bu benim için çalışıyor:

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

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

Buradaki diğer çözümlerin aksine, bu beklenmedik bir şekilde tetiklenmiyor gibi görünüyor.


onun yerine willMove kullanmak daha iyidir
Eugene

4

Kayıt için bence bu daha çok aradığı şey…

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

1
Teşekkürler Paul, bu çözüm oldukça basit. Ne yazık ki, simge farklı. Bu geri sarma simgesi değil, "geri sarma" simgesidir. Belki arka simgeyi kullanmanın bir yolu var ...
Ferran Maylinch

2

Gibi purrrminatordiyor, tarafından cevap elitalonberi, tamamen doğru değil your stuffprogramlı kontrolörü haşhaş bile idam edilecektir.

Şimdiye kadar bulduğum çözüm çok hoş değil, ama benim için çalışıyor. Ne elitalondedi yanı sıra , ben de programlı olarak haşhaş olup olmadığını kontrol:

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

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

Programlı olarak açmadan önce bu özelliği denetleyicinize eklemeniz ve EVET olarak ayarlamanız gerekir:

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

Yardımınız için teşekkürler!


2

En iyi yol UINavigationController delege yöntemlerini kullanmaktır

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

Bunu kullanarak hangi denetleyicinin UINavigationController'ı gösterdiğini öğrenebilirsiniz.

if ([viewController isKindOfClass:[HomeController class]]) {
    NSLog(@"Show home controller");
}

Bu doğru cevap olarak işaretlenmelidir! Ayrıca sadece millet hatırlatmak için bir satır daha eklemek isteyebilirsiniz -> self.navigationController.delegate = self;
Mike Critchley

2

Sol taraftaki navigationBar'a bir UIControl ekleyerek bu sorunu çözdüm.

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

Ve görünüm kaybolacağı zaman kaldırmayı hatırlamanız gerekir:

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

Bu kadar!


2

Geri düğmesi geri aramasını şu şekilde kullanabilirsiniz:

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

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

hızlı versiyon için global kapsamda bir şey yapabilirsiniz

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

Geri düğmesi eylemini kontrol etmek istediğiniz görünüme denetleyiciye koyduğunuzun altına:

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

    return true
}

1
Neden birilerinin oy verdiğini bilmiyorum. Bu en iyi cevap gibi görünüyor.
Avinash

@Avinash Nereden navigationShouldPopOnBackButtongeliyor? Herkese açık API'nın bir parçası değildir.
elitalon

@elitalon Üzgünüm, bu yarı cevaptı. Kalan bağlamın söz konusu olduğunu düşünmüştüm. Neyse cevabı şimdi güncelledik
Avinash

1

Coli88'in dediği gibi, UINavigationBarDelegate protokolünü kontrol etmelisiniz.

Daha genel bir şekilde, - (void)viewWillDisapear:(BOOL)animatedo anda görünen görünüm denetleyicisi tarafından tutulan görünüm kaybolmak üzereyken özel işi gerçekleştirmek için de kullanabilirsiniz . Ne yazık ki, bu itme ve pop vakalarını rahatsız edecek.


1

UINavigationController ile Swift için:

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

1

7ynk3r'in cevabı sonunda kullandığım şeye çok yakındı ama bazı tweaks gerekli:

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


0

self.navigationController.isMovingFromParentViewController iOS8 ve 9'da artık çalışmıyor:

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

-1

(SWIFT)

son olarak çözüm bulundu .. aradığımız yöntemi "uSavigationController temsilci yöntemi olan" willShowViewController "dir

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

1
Bu yaklaşımın sorunu çift MyViewControllerolması PushedController.
clozach
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.