Phát hiện khi nhấn nút 'quay lại' trên thanh điều hướng


134

Tôi cần thực hiện một số hành động khi nhấn nút quay lại (quay lại màn hình trước đó, quay lại chế độ xem chính) trên thanh Navbar.

Có một số phương pháp tôi có thể thực hiện để bắt sự kiện và tắt một số hành động để tạm dừng và lưu dữ liệu trước khi màn hình biến mất?




Tôi đã làm theo cách này cho thấy quyết định ở đây
Taras

Câu trả lời:


316

CẬP NHẬT: Theo một số ý kiến, giải pháp trong câu trả lời ban đầu dường như không hoạt động theo các kịch bản nhất định trong iOS 8+. Tôi không thể xác minh rằng đó thực sự là trường hợp mà không có thêm chi tiết.

Tuy nhiên, đối với những người trong tình huống đó, có một sự thay thế. Có thể phát hiện khi một bộ điều khiển xem đang được bật bằng cách ghi đè willMove(toParentViewController:). Ý tưởng cơ bản là một bộ điều khiển xem đang được bật khi parentnil.

Kiểm tra "Triển khai bộ điều khiển xem container" để biết thêm chi tiết.


Vì iOS 5 tôi đã thấy rằng cách dễ nhất để xử lý tình huống này là sử dụng phương pháp mới - (BOOL)isMovingFromParentViewController:

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

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

- (BOOL)isMovingFromParentViewController có ý nghĩa khi bạn đang đẩy và bật bộ điều khiển trong ngăn xếp điều hướng.

Tuy nhiên, nếu bạn đang trình bày các bộ điều khiển xem chế độ, bạn nên sử dụng - (BOOL)isBeingDismissedthay thế:

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

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

Như đã lưu ý trong câu hỏi này , bạn có thể kết hợp cả hai thuộc tính:

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

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

Các giải pháp khác dựa trên sự tồn tại của a UINavigationBar. Thay vào đó, tôi thích cách tiếp cận của mình hơn vì nó tách các tác vụ cần thiết để thực hiện từ hành động đã kích hoạt sự kiện, tức là nhấn nút quay lại.


Tôi thích bạn trả lời. Nhưng tại sao bạn lại sử dụng 'self.isByingDismissed'? Trong trường hợp của tôi, các câu lệnh trong 'self.isByingDismissed' không được triển khai.
Rutvij Kotecha

3
self.isMovingFromParentViewControllercó giá trị TRUE khi tôi bật ngăn xếp điều hướng bằng lập trình bằng cách sử dụng popToRootViewControllerAnimated- mà không cần chạm vào nút quay lại. Tôi có nên downvote câu trả lời của bạn? (chủ đề nói nút "quay lại" được nhấn trên thanh điều hướng ")
kas-kad

2
Câu trả lời tuyệt vời, cảm ơn bạn rất nhiều. Trong Swift tôi đã sử dụng:override func viewWillDisappear(animated: Bool) { super.viewWillDisappear(animated) if isMovingFromParentViewController(){ println("back button pressed") } }
Camillo Visini

1
Bạn chỉ nên thực hiện việc này -viewDidDisappear:vì có thể bạn sẽ -viewWillDisappear:không có -viewDidDisappear:(như khi bạn bắt đầu vuốt để loại bỏ một mục điều khiển điều hướng và sau đó hủy thao tác vuốt đó.
Heath

3
Hình như không còn là một giải pháp đáng tin cậy nữa. Làm việc tại thời điểm tôi sử dụng lần đầu tiên (đó là iOS 10). Nhưng bây giờ tôi vô tình thấy nó bình tĩnh ngừng hoạt động (iOS 11). Phải chuyển sang giải pháp "willMove (toParentViewControll)".
Vitalii

100

Trong khi viewWillAppear()viewDidDisappear() được gọi khi nhấn nút quay lại, chúng cũng được gọi vào thời điểm khác. Xem cuối câu trả lời để biết thêm về điều đó.

Sử dụng UIViewControll.parent

Việc phát hiện nút quay lại được thực hiện tốt hơn khi VC bị xóa khỏi cha mẹ của nó (Bộ điều hướng dẫn hướng) với sự trợ giúp của willMoveToParentViewController(_:)ORdidMoveToParentViewController()

Nếu cha mẹ là con số không, bộ điều khiển khung nhìn sẽ được bật ra khỏi ngăn điều hướng và bị loại bỏ. Nếu cha mẹ không phải là con số không, nó sẽ được thêm vào ngăn xếp và được trình bày.

// 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 ra willMovecho didMovevà kiểm tra self.parent để làm việc sau khi bộ điều khiển xem được sa thải.

Dừng việc sa thải

Xin lưu ý, việc kiểm tra cha mẹ không cho phép bạn "tạm dừng" quá trình chuyển đổi nếu bạn cần thực hiện một số loại lưu không đồng bộ. Để làm điều đó bạn có thể thực hiện như sau. Nhược điểm duy nhất ở đây là bạn mất nút quay lại theo kiểu iOS / hoạt hình lạ mắt. Cũng cẩn thận ở đây với cử chỉ vuốt tương tác. Sử dụng như sau để xử lý trường hợp này.

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


Thêm vào xem sẽ / đã xuất hiện

Nếu bạn không gặp viewWillAppear viewDidDisappearvấn đề, hãy xem qua một ví dụ. Giả sử bạn có ba bộ điều khiển xem:

  1. ListVC: Bảng xem mọi thứ
  2. Chi tiếtVC: Chi tiết về một điều
  3. Cài đặtVC: Một số tùy chọn cho một thứ

Hãy thực hiện theo các cuộc gọi detailVCkhi bạn đi từ listVCđến settingsVCvà quay lạilistVC

Danh sách> Chi tiết ( chi tiết đẩyVC) Detail.viewDidAppear<- xuất hiện
Chi tiết> Cài đặt (cài đặt đẩyVC ) Detail.viewDidDisappear<- biến mất

Và khi chúng tôi quay lại ...
Cài đặt> Chi tiết (cài đặt popVC) <- Detail.viewDidAppearxuất hiện
Chi tiết> Danh sách ( chi tiết popVC) Detail.viewDidDisappear<- biến mất

Lưu ý rằng viewDidDisappearđược gọi nhiều lần, không chỉ khi đi về, mà cả khi đi tiếp. Đối với một hoạt động nhanh chóng có thể được mong muốn, nhưng đối với một hoạt động phức tạp hơn như một cuộc gọi mạng để lưu, thì có thể không.


Chỉ cần một lưu ý, người dùng didMoveToParantViewController:để thực hiện công việc khi chế độ xem không còn hiển thị. Hữu ích cho iOS7 với tính tương
tácGesutre

didMoveToParentViewControll * có một lỗi đánh máy
thewormsterror

Đừng quên gọi [super willMoveToParentViewCont điều khiển: cha mẹ]!
ScottyB

2
Tham số cha là nil khi bạn bật đến bộ điều khiển khung nhìn cha và không phải là không khi chế độ xem phương thức này xuất hiện đang được hiển thị. Bạn chỉ có thể sử dụng thực tế đó để thực hiện một hành động khi nhấn nút Quay lại, chứ không phải khi đến chế độ xem. Rốt cuộc, đó là câu hỏi ban đầu. :)
Mike

1
Điều này cũng được gọi khi sử dụng theo chương trình _ = self.navigationController?.popViewController(animated: true), vì vậy nó không chỉ được gọi khi nhấn nút Quay lại. Tôi đang tìm kiếm một cuộc gọi chỉ hoạt động khi nhấn Back.
Ethan Allen

16

Phương pháp đầu tiên

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

Phương pháp thứ hai

-(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
Phương pháp thứ hai là phương pháp duy nhất phù hợp với tôi. Phương pháp đầu tiên cũng được đưa ra khi quan điểm của tôi được trình bày, không thể chấp nhận được cho trường hợp sử dụng của tôi.
marcshilling

10

Những người cho rằng điều này không hoạt động là sai:

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

Điều đó làm việc tốt. Vì vậy, những gì đang gây ra huyền thoại rộng rãi mà nó không?

Vấn đề dường như là do việc thực hiện không đúng một phương pháp khác , cụ thể là việc thực hiện willMove(toParent:)quên gọi super.

Nếu bạn thực hiện willMove(toParent:)mà không gọi super, thì self.isMovingFromParentsẽ falsevà việc sử dụng viewWillDisappearsẽ thất bại. Nó đã không thất bại; bạn đã phá vỡ nó.

GHI CHÚ: Vấn đề thực sự thường là bộ điều khiển khung nhìn thứ hai phát hiện ra rằng bộ điều khiển khung nhìn thứ nhất đã được bật lên. Xin vui lòng xem thêm các cuộc thảo luận chung hơn ở đây: Phát hiện UIViewContoder "hợp nhất" trở thành hàng đầu?

EDIT Một bình luận cho thấy rằng điều này nên viewDidDisappearhơn là viewWillDisappear.


Mã này được thực thi khi nhấn nút quay lại, nhưng cũng được thực thi nếu VC được bật theo chương trình.
biomiker

@biomiker Chắc chắn, nhưng điều đó cũng đúng với các cách tiếp cận khác. Popping là popping. Câu hỏi là làm thế nào để phát hiện một cửa sổ bật lên khi bạn không bật chương trình. Nếu bạn bật theo chương trình, bạn đã biết bạn đang bật nên không có gì để phát hiện.
matt

Vâng, điều này đúng với một số cách tiếp cận khác và nhiều trong số đó có ý kiến ​​tương tự. Tôi chỉ đang làm rõ vì đây là một câu trả lời gần đây với một phản bác cụ thể và tôi đã hy vọng khi đọc nó. Đối với bản ghi mặc dù, câu hỏi là làm thế nào để phát hiện nhấn nút quay lại. Đó là một lý lẽ hợp lý để nói rằng mã cũng sẽ thực thi trong các tình huống không nhấn nút quay lại, mà không cho biết liệu nút quay lại có được nhấn hay không, không giải quyết được hoàn toàn câu hỏi thực sự, ngay cả khi có lẽ câu hỏi có thể nhiều hơn rõ ràng về điểm đó.
khối

1
Thật không may, điều này trả về truecho cử chỉ pop vuốt tương tác - từ cạnh trái của trình điều khiển chế độ xem - ngay cả khi thao tác vuốt không bật hoàn toàn. Vì vậy, thay vì kiểm tra nó willDisappear, làm như vậy trong didDisappearcông việc.
badhanganesh

1
@badhanganesh Cảm ơn, đã chỉnh sửa câu trả lời để bao gồm thông tin đó.
matt

9

Tôi đã chơi (hoặc chiến đấu) với vấn đề này trong hai ngày. IMO cách tiếp cận tốt nhất chỉ là tạo một lớp mở rộng và một giao thức, như thế này:

@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

Điều này hoạt động vì UINavigationControllersẽ nhận được một cuộc gọi đếnnavigationBar:shouldPopItem: mỗi khi một bộ điều khiển xem được bật lên. Ở đó chúng tôi phát hiện xem có được nhấn lại hay không (bất kỳ nút nào khác). Điều duy nhất bạn phải làm là thực hiện giao thức trong trình điều khiển xem nơi nhấn trở lại.

Hãy nhớ tự bật trình điều khiển xem bên trong backButtonPressedSel, nếu mọi thứ đều ổn.

Nếu bạn đã phân lớp UINavigationViewControllervà triển khai, navigationBar:shouldPopItem:đừng lo lắng, điều này sẽ không can thiệp vào nó.

Bạn cũng có thể quan tâm đến việc vô hiệu hóa cử chỉ trở lại.

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

1
Câu trả lời này đã gần như hoàn chỉnh đối với tôi, ngoại trừ việc tôi thấy rằng 2 trình điều khiển khung nhìn thường sẽ được bật lên. Trả về CÓ khiến phương thức gọi để gọi pop, do đó, gọi pop cũng có nghĩa là 2 trình điều khiển khung nhìn sẽ được bật. Xem câu trả lời này trên một câu hỏi để biết thêm Deets (một câu trả lời rất tốt xứng đáng hơn upvotes): stackoverflow.com/a/26084150/978083
Jason Ridge

Điểm tốt, mô tả của tôi không rõ ràng về thực tế đó. "Hãy nhớ bật thủ công trình điều khiển chế độ xem nếu mọi thứ đều ổn" chỉ dành cho trường hợp trả về "KHÔNG", nếu không thì luồng là pop bình thường.
7ynk3r

1
Đối với chi nhánh "khác", tốt hơn hết là gọi siêu thực hiện nếu bạn không muốn tự xử lý pop và để nó trả về bất cứ điều gì nó cho là đúng, chủ yếu là CÓ, nhưng sau đó cũng quan tâm đến pop và hoạt hình đúng cách .
Ben Sinclair

9

Điều này hoạt động với tôi trong iOS 9.3.x với Swift:

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

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

Không giống như các giải pháp khác ở đây, điều này dường như không kích hoạt bất ngờ.


thay vào đó, tốt hơn là sử dụng willMove
Eugene Gordin

4

Đối với hồ sơ, tôi nghĩ rằng đây là nhiều hơn những gì anh ta đang tìm kiếm

    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
Cảm ơn Paul, giải pháp này khá đơn giản. Thật không may, biểu tượng là khác nhau. Đây là biểu tượng "tua lại", không phải biểu tượng quay lại. Có lẽ có một cách để sử dụng biểu tượng trở lại ...
Ferran Maylinch 11/05/2015

2

Như đã purrrminatornói, câu trả lời elitalonkhông hoàn toàn đúng, vì your stuffsẽ được thực thi ngay cả khi bật bộ điều khiển theo chương trình.

Giải pháp tôi tìm thấy cho đến nay không phải là rất tốt, nhưng nó hiệu quả với tôi. Bên cạnh những gì đã elitalonnói, tôi cũng kiểm tra xem tôi có lập trình hay không:

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

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

Bạn phải thêm thuộc tính đó vào bộ điều khiển của mình và đặt nó thành CÓ trước khi bật chương trình:

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

Cảm ơn bạn đã giúp đỡ!


2

Cách tốt nhất là sử dụng các phương thức ủy nhiệm của UINavestionControll

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

Sử dụng điều này, bạn có thể biết bộ điều khiển nào đang hiển thị UINavestionControll.

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

Điều này nên được đánh dấu là câu trả lời chính xác! Cũng có thể muốn thêm một dòng nữa chỉ để nhắc nhở mọi người -> self.navlationControll.delegate = self;
Mike Critchley

2

Tôi đã giải quyết vấn đề này bằng cách thêm UIControl vào navigationBar ở bên trái.

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

Và bạn cần nhớ xóa nó khi chế độ xem sẽ biến mất:

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

Đó là tất cả!


2

Bạn có thể sử dụng gọi lại nút quay lại, như thế này:

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

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

đối với phiên bản nhanh, bạn có thể làm một cái gì đó như trong phạm vi toàn cầu

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

Bên dưới một cái bạn đặt trong trình điều khiển khung nhìn nơi bạn muốn điều khiển hành động nút quay lại:

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

    return true
}

1
Không biết tại sao ai đó xuống bình chọn. Điều này dường như là câu trả lời tốt nhất cho đến nay.
Avinash

@Avinash Không navigationShouldPopOnBackButtonđến từ đâu? Nó không phải là một phần của API công khai.
elitalon

@elitalon Xin lỗi, đây là một nửa câu trả lời. Tôi đã nghĩ rằng bối cảnh còn lại là ở đó trong câu hỏi. Dù sao cũng đã cập nhật câu trả lời ngay bây giờ
Avinash

1

Như Coli88 đã nói, bạn nên kiểm tra giao thức UINavestionBarDelegate.

Nói một cách tổng quát hơn, bạn cũng có thể sử dụng - (void)viewWillDisapear:(BOOL)animatedđể thực hiện công việc tùy chỉnh khi chế độ xem được giữ lại bởi bộ điều khiển chế độ xem hiện tại sắp biến mất. Thật không may, điều này sẽ bao gồm làm phiền các trường hợp đẩy và pop.


1

Đối với Swift với Trình điều khiển UINavestion:

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

1

Câu trả lời của 7ynk3r thực sự gần với những gì tôi đã sử dụng cuối cùng nhưng nó cần một số điều chỉnh:

- (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.navestionControll.isMovingFromParentViewControll không hoạt động nữa trên iOS8 và 9 Tôi sử dụng:

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

-1

(NHANH)

giải pháp cuối cùng tìm thấy .. phương thức mà chúng tôi đang tìm kiếm là "willShowViewControll", đây là phương thức ủy nhiệm của UINavestionControll

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

Vấn đề với cách tiếp cận này là nó cặp vợ chồng MyViewControllerđến PushedController.
clozach
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.