nút quay lại gọi lại trong điều hướng


102

Tôi đã đẩy một chế độ xem vào bộ điều khiển điều hướng và khi tôi nhấn nút quay lại, nó sẽ tự động chuyển sang chế độ xem trước đó. Tôi muốn thực hiện một số việc khi nhấn nút quay lại trước khi bật chế độ xem ra khỏi ngăn xếp. Chức năng gọi lại của nút quay lại là gì?



Kiểm tra [giải pháp] [1] này, nó cũng giữ lại kiểu nút quay lại. [1]: stackoverflow.com/a/29943156/3839641
Sarasranglt

Câu trả lời:


162

Câu trả lời của William Jockusch giải quyết vấn đề này bằng mẹo dễ dàng.

-(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
Mã này không chỉ được thực thi khi người dùng nhấn vào nút quay lại, mà trong mọi trường hợp, chế độ xem được hiển thị (ví dụ: khi có nút hoàn tất hoặc lưu ở phía bên phải).
ý nghĩa-vấn đề

7
Hoặc khi chuyển sang chế độ xem mới.
GuybrushThreepwood

Điều này cũng được gọi khi người dùng lia từ cạnh trái (tương tácPopGestureRecognizer). Trong trường hợp của tôi, tôi đặc biệt tìm kiếm thời điểm người dùng nhấn trở lại trong khi KHÔNG di chuyển từ cạnh trái.
Kyle Clegg

2
Không có nghĩa là nút quay lại là nguyên nhân. Ví dụ, có thể là một segue thư giãn.
smileBot

1
Tôi nghi ngờ, Tại sao chúng ta không nên làm điều này trong viewDidDisappear?
JohnVanDijk

85

Theo tôi giải pháp tốt nhất.

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

Nhưng nó chỉ hoạt động với iOS5 +


3
Kỹ thuật này không thể phân biệt giữa một lần nhấn nút quay lại và một hành động thư giãn.
smileBot

Các willMoveToParentViewController và viewWillDisappear phương pháp không giải thích được điều khiển phải bị tiêu diệt, didMoveToParentViewController là đúng
Hank

27

có lẽ tốt hơn là ghi đè nút ngược để bạn có thể xử lý sự kiện trước khi chế độ xem xuất hiện cho những thứ như xác nhận người dùng.

trong viewDidLoad, hãy tạo một UIBarButtonItem và đặt self.navigationItem.leftBarButtonItem để nó truyền trong bản chọ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];

}

Sau đó, bạn có thể làm những việc như nâng UIAlertView để xác nhận hành động, sau đó bật bộ điều khiển chế độ xem, v.v.

Hoặc thay vì tạo một nút quay lại mới, bạn có thể tuân theo các phương thức ủy quyền của UINavigationController để thực hiện các hành động khi nút quay lại được nhấn.


Các UINavigationControllerDelegatekhông có phương pháp được gọi là khi back-button được khai thác.
ý nghĩa-vấn đề

Kỹ thuật này cho phép xác nhận dữ liệu của bộ điều khiển chế độ xem và trả về có điều kiện từ nút quay lại của bộ điều khiển điều hướng.
gjpc

Giải pháp này phá vỡ tính năng vuốt cạnh của iOS 7 trở lên
Liron Yahdav

9

Đây là cách chính xác để phát hiện điều này.

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

    }
}

phương thức này được gọi khi chế độ xem cũng được đẩy. Vì vậy, kiểm tra cha == nil dành cho bộ điều khiển chế độ xem bật lên từ ngăn xếp


9

Tôi kết thúc với các giải pháp này. Khi chúng ta chạm vào nút quay lại, phương thức viewDidDisappear được gọi. chúng ta có thể kiểm tra bằng cách gọi bộ chọn isMovingFromParentViewController trả về true. chúng tôi có thể chuyển dữ liệu trở lại (Sử dụng Ủy quyền). Hãy gõ điều này giúp ai đó.

-(void)viewDidDisappear:(BOOL)animated{

    if (self.isMovingToParentViewController) {

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

    }
}

Đừng quên[super viewDidDisappear:animated]
SamB

9

Có lẽ hơi muộn, nhưng trước đây tôi cũng muốn có những hành vi tương tự. Và giải pháp mà tôi đã sử dụng hoạt động khá tốt trong một trong những ứng dụng hiện có trên App Store. Vì mình chưa thấy ai đi theo phương pháp tương tự nên mình xin chia sẻ ở đây. Nhược điểm của giải pháp này là nó yêu cầu phân lớp con UINavigationController. Mặc dù sử dụng Method Swizzling có thể giúp tránh điều đó, nhưng tôi đã không đi xa đến vậy.

Vì vậy, nút quay lại mặc định thực sự được quản lý bởi UINavigationBar. Khi người dùng nhấn vào nút quay lại, UINavigationBarhãy hỏi người đại diện của họ xem nó có bật lên trên cùng UINavigationItembằng cách gọi hay không navigationBar(_:shouldPop:). UINavigationControllerthực sự thực hiện điều này, nhưng nó không tuyên bố công khai rằng nó thông qua UINavigationBarDelegate(tại sao !?). Để chặn sự kiện này, hãy tạo một lớp con của UINavigationController, khai báo sự phù hợp của nó UINavigationBarDelegatevà triển khai navigationBar(_:shouldPop:). Trở lại truenếu mục hàng đầu sẽ được xuất hiện. Trả lại falsenếu nó nên ở lại.

Có hai vấn đề. Đầu tiên là bạn phải gọi UINavigationControllerphiên bản của navigationBar(_:shouldPop:)một lúc nào đó. Nhưng UINavigationBarControllerkhông tuyên bố công khai nó tuân thủ UINavigationBarDelegate, cố gắng gọi nó sẽ dẫn đến lỗi thời gian biên dịch. Giải pháp mà tôi đã đi là sử dụng Objective-C runtime để thực hiện trực tiếp và gọi nó. Vui lòng cho tôi biết nếu có ai có giải pháp tốt hơn.

Vấn đề khác là nó navigationBar(_:shouldPop:)được gọi đầu tiên theo sau popViewController(animated:)nếu người dùng nhấn vào nút quay lại. Thứ tự sẽ đảo ngược nếu bộ điều khiển chế độ xem được hiển thị bằng cách gọi popViewController(animated:). Trong trường hợp này, tôi sử dụng boolean để phát hiện xem popViewController(animated:)có được gọi trước navigationBar(_:shouldPop:)đó hay không, nghĩa là người dùng đã nhấn vào nút quay lại.

Ngoài ra, tôi thực hiện một phần mở rộng UIViewControllerđể cho phép bộ điều khiển điều hướng hỏi bộ điều khiển chế độ xem xem nó có nên được bật lên không nếu người dùng nhấn vào nút quay lại. Bộ điều khiển chế độ xem có thể quay lại falsevà thực hiện bất kỳ hành động cần thiết nào và gọi popViewController(animated:)sau.

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

Và trong bạn xem bộ điều khiển, thực hiện shouldBePopped(_:). Nếu bạn không triển khai phương pháp này, hành vi mặc định sẽ là bật bộ điều khiển chế độ xem ngay khi người dùng chạm vào nút quay lại giống như bình thường.

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

Bạn có thể xem bản demo của tôi ở đây .

nhập mô tả hình ảnh ở đây


Đây là một giải pháp tuyệt vời và nên được đưa vào blogpost! Có vẻ là quá mức cần thiết cho những gì tôi đang tìm kiếm ngay bây giờ, nhưng trong các trường hợp khác, điều này chắc chắn rất đáng để thử.
ASSeeger

6

Đối với "TRƯỚC KHI bật chế độ xem ra khỏi ngăn xếp":

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

5

Có một cách thích hợp hơn là hỏi viewControllers. Bạn có thể đặt bộ điều khiển của mình làm đại biểu của Thanh điều hướng có nút quay lại. Đây là một ví dụ. Trong quá trình triển khai bộ điều khiển mà bạn muốn xử lý thao tác nhấn nút quay lại, hãy nói với bộ điều khiển rằng nó sẽ triển khai giao thức UINavigationBarDelegate:

@interface MyViewController () <UINavigationBarDelegate>

Sau đó, ở đâu đó trong mã khởi tạo của bạn (có thể là trong viewDidLoad) đặt bộ điều khiển của bạn làm đại biểu của thanh điều hướng của nó:

self.navigationController.navigationBar.delegate = self;

Cuối cùng, triển khai phương thức shouldPopItem. Phương thức này được gọi ngay khi nhấn nút quay lại. Nếu bạn có nhiều bộ điều khiển hoặc các Mục điều hướng trong ngăn xếp, có thể bạn sẽ muốn kiểm tra xem mục nào trong số các mục điều hướng đó đang được bật lên (tham số mục), để bạn chỉ thực hiện nội dung tùy chỉnh của mình khi bạn muốn. Đây là một ví dụ:

-(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
không làm việc cho tôi .. pitty vì nó gầy. "*** Đang chấm dứt ứng dụng do không có ngoại lệ 'NSInternalInconsistencyException', lý do: 'Không thể đặt đại biểu theo cách thủ công trên UINavigationBar do bộ điều khiển quản lý.'"
DynamicDan 24/12/13

Rất tiếc, điều này sẽ không hoạt động với UINavigationController, thay vào đó, bạn cần một UIViewController tiêu chuẩn với một UINavigationBar trong đó. Điều này có nghĩa là bạn không thể tận dụng một số thao tác đẩy và bật chế độ xem tự động mà NavigationController cung cấp cho bạn. Lấy làm tiếc!
Carlos Guzman

Tôi vừa sử dụng UINavigationBar thay vì NavigationBarController và sau đó nó hoạt động tốt. Tôi biết câu hỏi là về NavigationBarController, nhưng giải pháp này khá đơn giản.
khai thác

3

Nếu bạn không thể sử dụng "viewWillDisappear" hoặc phương pháp tương tự, hãy thử phân lớp UINavigationController. Đây là lớp tiêu đề:

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

@interface CCNavigationController : UINavigationController

@property (nonatomic, strong) MyViewController *viewController;

@end

Lớp thực hiện:

#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

Mặt khác, bạn cần liên kết viewController này với NavigationController tùy chỉnh của mình, vì vậy, trong phương thức viewDidLoad cho viewController thông thường của bạn, hãy thực hiện điều này:

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

3

Đây là một cách khác mà tôi đã triển khai (không kiểm tra nó với một segue unwind nhưng nó có thể sẽ không phân biệt, như những người khác đã nêu về các giải pháp khác trên trang này) để yêu cầu bộ điều khiển chế độ xem gốc thực hiện các hành động trước khi VC con mà nó đã đẩy được bật ra khỏi ngăn xếp chế độ xem (tôi đã sử dụng điều này một vài cấp độ từ UINavigationController ban đầu). Điều này cũng có thể được sử dụng để thực hiện các hành động trước khi childVC bị đẩy. Điều này có thêm lợi thế khi làm việc với nút quay lại hệ thống iOS, thay vì phải tạo UIBarButtonItem hoặc UIButton tùy chỉnh.

  1. Yêu cầu VC cha của bạn áp dụng UINavigationControllerDelegategiao thức và đăng ký các thông báo ủy quyền:

    MyParentViewController : UIViewController <UINavigationControllerDelegate>
    
    -(void)viewDidLoad {
        self.navigationcontroller.delegate = self;
    }
  2. Triển khai UINavigationControllerDelegatephương thức phiên bản này trong 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. Nếu bạn chỉ định một hàm gọi lại cụ thể trong UINavigationControllerDelegatephương thức phiên bản trên

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

Đây là những gì nó hoạt động đối với tôi trong Swift:

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

    super.viewWillDisappear(animated)
}

0

Nếu bạn đang sử dụng Bảng phân cảnh và bạn đến từ một trò chơi đẩy, bạn cũng có thể ghi đè shouldPerformSegueWithIdentifier:sender:.

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.