iOS'taki navigationController'da geri düğmesi geri çağırma


102

Gezinme denetleyicisine bir görüntü aktardım ve geri düğmesine bastığımda otomatik olarak önceki görünüme gider. Görünümü yığından çıkarmadan önce geri düğmesine basıldığında birkaç şey yapmak istiyorum. Geri düğmesi geri arama işlevi hangisidir?



Geri düğmesi stilini de koruyan bu [çözümü] [1] inceleyin. [1]: stackoverflow.com/a/29943156/3839641
Sarasranglt

Yanıtlar:


162

William Jockusch'un cevabı bu sorunu kolay bir hile ile çözer.

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

32
Bu kod yalnızca kullanıcı geri düğmesine dokunduğunda değil, her durumda görünüm açılır (örneğin, sağ tarafta bir tamam veya kaydet düğmesi olduğunda).
anlamlar

7
Veya yeni bir görünüme geçerken.
GuybrushThreepwood

Bu, kullanıcı sol kenardan kaydırdığında da çağrılır (InteractivePopGestureRecognizer). Benim durumumda, özellikle sol kenardan kaydırma YAPMADIĞINDA kullanıcı geri bastığında arıyorum.
Kyle Clegg

2
Geri düğmesinin neden olduğu anlamına gelmez. Örneğin bir çözülme bölümü olabilir.
smileBot

1
Bir şüphem var, Neden bunu görünüp kaybolmayalım?
JohnVanDijk

85

Bence en iyi çözüm.

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

Ancak yalnızca iOS5 + ile çalışır


3
Bu teknik, bir geri düğmesine dokunma ile çözme geçişi arasında ayrım yapamaz.
smileBot

WillMoveToParentViewController ve viewWillDisappear yöntemi, denetleyicinin imha edilmesi gerektiğini açıklamıyor, didMoveToParentViewController haklı
Hank,

27

Kullanıcı onayı gibi şeyler için görünüm açılmadan önce olayı işleyebilmeniz için muhtemelen geri düğmesini geçersiz kılmak daha iyidir .

in viewDidLoad bir UIBarButtonItem oluşturun ve self.navigationItem.leftBarButtonItem öğesini bir sel içinde geçirerek ayarlayın

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

}

Ardından, eylemi onaylamak için bir UIAlertView yükseltmek, ardından görünüm denetleyicisini açmak gibi şeyler yapabilirsiniz.

Veya yeni bir geri düğmesi oluşturmak yerine, geri düğmesine basıldığında eylemler yapmak için UINavigationController delege yöntemlerine uyabilirsiniz.


UINavigationControllerDelegateGeriye dönüş düğmesi dokunulduğunda denir yöntemi yok.
anlamlar

Bu teknik, görünüm denetleyicisinin verilerinin doğrulanmasına ve gezinme denetleyicisinin geri düğmesinden koşullu geri dönüşe izin verir.
gjpc

Bu çözüm 7+ iOS kenar süpürme özelliğini kırar
Liron Yahdav

9

Bunu tespit etmenin doğru yolu budur.

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

    }
}

bu yöntem aynı zamanda view itildiğinde çağrılır. Yani parent == nil'i kontrol etmek, yığından görünüm denetleyicisini açmak içindir


9

Bu çözümlerle sonuçlandım. Geri düğmesine dokunduğumuzda viewDidDisappear yöntemi çağrılır. true döndüren isMovingFromParentViewController seçicisini çağırarak kontrol edebiliriz. verileri geri verebiliriz (Delege Kullanarak). umarım bu birisine yardımcı olur.

-(void)viewDidDisappear:(BOOL)animated{

    if (self.isMovingToParentViewController) {

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

    }
}

Unutma[super viewDidDisappear:animated]
SamB

9

Belki biraz geç, ama daha önce de aynı davranışı istedim. Ve benim kullandığım çözüm şu anda App Store'da bulunan uygulamalardan birinde oldukça iyi çalışıyor. Kimsenin benzer bir yöntemle gittiğini görmediğim için burada paylaşmak istiyorum. Bu çözümün dezavantajı, alt sınıflandırma gerektirmesidir UINavigationController. Yöntem Swizzling kullanmasına rağmen kaçınmaya yardımcı da, o kadar ileri gitmedim.

Yani, varsayılan geri düğmesi aslında tarafından yönetilir UINavigationBar. Bir kullanıcı geri düğmesine dokunduğunda UINavigationBar, temsilcisine UINavigationItemarayarak yukarı çıkıp çıkmayacağını sorun navigationBar(_:shouldPop:). UINavigationControllerbunu gerçekte uygular, ancak benimsediğini kamuya açıklamaz UINavigationBarDelegate(neden !?). Bu olayı engellemek için bir alt sınıf oluşturun UINavigationController, uygunluğunu bildirin UINavigationBarDelegateve uygulayın navigationBar(_:shouldPop:). trueEn üstteki öğenin çıkarılması gerekiyorsa geri dönün . Kalması falsegerekiyorsa geri dön .

İki sorun var. Birincisi , bir noktada UINavigationControllerversiyonunu aramanız gerektiğidir navigationBar(_:shouldPop:). Ancak UINavigationBarControllerkamuya açık bir şekilde uygunluğunu beyan etmezUINavigationBarDelegate , çağırmaya çalışmak derleme zamanı hatasıyla sonuçlanır. Benim kullandığım çözüm, uygulamayı doğrudan almak ve çağırmak için Objective-C çalışma zamanını kullanmak. Daha iyi bir çözümü olan varsa lütfen bana bildirin.

Diğer sorun ise, navigationBar(_:shouldPop:)ilk önce popViewController(animated:)kullanıcı geri düğmesine dokunursa takip eder. Görünüm denetleyicisi çağrılarak açılırsa sıra tersine döner popViewController(animated:). Bu durumda, popViewController(animated:)daha önce çağrılıp çağrılmadığını tespit etmek için bir boole kullanıyorum , navigationBar(_:shouldPop:)bu da kullanıcının geri düğmesine dokunduğu anlamına geliyor.

Ayrıca, UIViewControllergezinme denetleyicisinin görünüm denetleyicisine, kullanıcı geri düğmesine dokunduğunda açılıp açılmayacağını sormasına izin veren bir uzantı oluşturuyorum . Denetleyicileri görüntüle geri dönüp falsegerekli eylemleri yapabilir ve popViewController(animated:)daha sonra arayabilir .

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

Ve sizde kontrolörleri görüntüleyin, uygulayın shouldBePopped(_:). Bu yöntemi uygulamazsanız, varsayılan davranış, kullanıcı geri düğmesine normal gibi dokunur dokunmaz görünüm denetleyicisini açmak olacaktır.

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

Demomu buradan inceleyebilirsiniz .

görüntü açıklamasını buraya girin


Bu harika bir çözüm ve bir blog yayınında pişirilmeli! Şu anda aradıklarım için abartılı gibi görünüyor, ancak diğer durumlarda, bu kesinlikle denemeye değer.
ASSeeger

6

"Görünümü yığından çıkarmadan ÖNCE" için:

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

5

ViewControllers'a sormaktan daha uygun bir yol var. Oyun kumandanızı, geri düğmesi olan bir gezinti çubuğu temsilcisi yapabilirsiniz. İşte bir örnek. Geri düğmesine basmak istediğiniz denetleyicinin uygulamasında, UINavigationBarDelegate protokolünü uygulayacağını söyleyin:

@interface MyViewController () <UINavigationBarDelegate>

Ardından başlatma kodunuzda bir yerde (muhtemelen viewDidLoad'da) denetleyicinizi gezinme çubuğunun temsilcisi yapın:

self.navigationController.navigationBar.delegate = self;

Son olarak, shouldPopItem yöntemini uygulayın. Bu yöntem hemen geri düğmesine basıldığında çağrılır. Yığın içinde birden fazla denetleyiciniz veya gezinme Öğeniz varsa, muhtemelen bu gezinme öğelerinden hangisinin patladığını (öğe parametresi) kontrol etmek isteyeceksiniz, böylece yalnızca istediğiniz zaman özel işlerinizi yaparsınız. İşte bir örnek:

-(BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
    NSLog(@"Back button got pressed!");
    //if you return NO, the back button press is cancelled
    return YES;
}

4
benim için işe yaramadı .. zayıf çünkü zayıf. "*** Yakalanmamış istisna nedeniyle uygulama sonlandırılıyor 'NSInternalInconsistencyException', nedeni: 'Temsilci, bir denetleyici tarafından yönetilen bir UINavigationBar'da manuel olarak ayarlanamıyor.'"
DynamicDan

Bu maalesef bir UINavigationController ile çalışmayacaktır, bunun yerine içinde bir UINavigationBar bulunan standart bir UIViewController'a ihtiyacınız vardır. Bu, NavigationController'ın size sunduğu birçok otomatik görüntü denetleyicisinden yararlanamayacağınız anlamına gelir. Afedersiniz!
Carlos Guzman

Sadece NavigationBarController yerine UINavigationBar'ı kullandım ve sonra iyi çalışıyor. Sorunun NavigationBarController ile ilgili olduğunu biliyorum, ancak bu çözüm zayıf.
2014

3

"ViewWillDisappear" veya benzer bir yöntemi kullanamıyorsanız, UINavigationController alt sınıfını deneyin. Bu başlık sınıfıdır:

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

@interface CCNavigationController : UINavigationController

@property (nonatomic, strong) MyViewController *viewController;

@end

Uygulama sınıfı:

#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

Öte yandan, bu viewController'ı özel NavigationController'ınıza bağlamanız gerekir, bu nedenle, normal viewController'ınız için viewDidLoad yönteminizde şunu yapın:

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

3

İşte benim uyguladığım başka bir yol (çözülme segmentiyle test etmedim, ancak başkalarının bu sayfadaki diğer çözümlerle ilgili olarak belirttiği gibi muhtemelen farklılaşmayacaktır) üst görünüm denetleyicisinin, ittiği alt VC'den önce eylemler gerçekleştirmesini sağlamak görünüm yığınından atılır (bunu orijinal UINavigationController'dan birkaç seviye aşağıda kullandım). Bu aynı zamanda childVC'ye itilmeden önce eylemler gerçekleştirmek için de kullanılabilir. Bu, özel bir UIBarButtonItem veya UIButton oluşturmak zorunda kalmadan, iOS sistemi geri düğmesiyle çalışmanın ek avantajına sahiptir.

  1. Ana VC'nizin UINavigationControllerDelegateprotokolü benimsemesini ve delege mesajları için kaydolmasını sağlayın :

    MyParentViewController : UIViewController <UINavigationControllerDelegate>
    
    -(void)viewDidLoad {
        self.navigationcontroller.delegate = self;
    }
  2. Bu UINavigationControllerDelegateörnek yöntemini şuraya uygulayın 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. Yukarıdaki UINavigationControllerDelegateörnek yönteminde belirli bir geri çağırma işlevi belirtirseniz

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

    }


1

Swift'de benim için işe yarayan şey bu:

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

    super.viewWillDisappear(animated)
}

0

Bir Storyboard kullanıyorsanız ve bir push segmentinden geliyorsanız, sadece geçersiz kılabilirsiniz shouldPerformSegueWithIdentifier:sender:.

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.