iOS의 navigationController에서 뒤로 버튼 콜백


102

내비게이션 컨트롤러에 뷰를 눌렀는데 뒤로 버튼을 누르면 자동으로 이전 뷰로 이동합니다. 스택에서 뷰를 열기 전에 뒤로 버튼을 눌렀을 때 몇 가지 작업을 수행하고 싶습니다. 뒤로 버튼 콜백 기능은 무엇입니까?



뒤로 버튼 스타일도 유지하는이 [솔루션] [1]을 확인하세요. [1] : stackoverflow.com/a/29943156/3839641
Sarasranglt 2015

답변:


162

William Jockusch의 대답 은 쉬운 트릭 으로이 문제를 해결합니다.

-(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
이 코드는 사용자가 뒤로 버튼을 탭할 때뿐만 아니라 모든 이벤트에서 뷰가 팝업 될 때 실행됩니다 (예 : 오른쪽에 완료 또는 저장 버튼이있는 경우).
의미-문제

7
또는 새로운보기로 이동할 때.
GuybrushThreepwood

사용자가 왼쪽 가장자리 (interactivePopGestureRecognizer)에서 패닝 할 때도 호출됩니다. 제 경우에는 특히 사용자가 왼쪽 가장자리에서 패닝하지 않고 뒤로 누를 때를 찾고 있습니다.
Kyle Clegg 2014 년

2
뒤로 버튼이 원인이라는 의미는 아닙니다. 예를 들어 unwind segue가 될 수 있습니다.
smileBot

1
의심 스럽습니다. viewDidDisappear에서이 작업을 수행하면 안되는 이유는 무엇입니까?
JohnVanDijk 2015 년

85

제 생각에는 최고의 솔루션입니다.

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

하지만 iOS5 +에서만 작동합니다.


3
이 기술은 뒤로 버튼 탭과 풀기 segue를 구별 할 수 없습니다.
smileBot

willMoveToParentViewController 및 viewWillDisappear 메서드는 컨트롤러가 파괴되어야한다고 설명하지 않습니다. didMoveToParentViewController가 맞습니다
Hank

27

사용자 확인과 같은 작업을 위해보기가 팝업 되기 전에 이벤트 처리 할 수 ​​있도록 뒤로 버튼을 재정의하는 것이 좋습니다 .

viewDidLoad에서 UIBarButtonItem을 만들고 self.navigationItem.leftBarButtonItem을 설정하여 sel을 전달합니다.

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

}

그런 다음 UIAlertView를 발생시켜 작업을 확인한 다음 뷰 컨트롤러를 팝업하는 등의 작업을 수행 할 수 있습니다.

또는 새 뒤로 버튼을 만드는 대신 뒤로 버튼을 눌렀을 때 작업을 수행하도록 UINavigationController 대리자 메서드를 준수 할 수 있습니다.


에는 UINavigationControllerDelegate뒤로 버튼을 누를 때 호출되는 메서드가 없습니다.
의미-문제

이 기술을 사용하면 뷰 컨트롤러의 데이터를 확인하고 탐색 컨트롤러의 뒤로 버튼에서 조건부 반환을 할 수 있습니다.
gjpc 2013-08-20

이 솔루션은 iOS 7+의 가장자리 스 와이프 기능을 중단합니다
Liron Yahdav 2015

9

이것이이를 감지하는 올바른 방법입니다.

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

    }
}

이 메서드는 뷰가 푸시 될 때도 호출됩니다. 따라서 parent == nil을 확인하는 것은 스택에서 뷰 컨트롤러를 팝하는 것입니다.


9

이 솔루션으로 끝납니다. 뒤로 버튼을 누르면 viewDidDisappear 메서드가 호출됩니다. true를 반환하는 isMovingFromParentViewController 선택기를 호출하여 확인할 수 있습니다. 데이터를 다시 전달할 수 있습니다 (Delegate 사용). 누군가에게 도움이되기를 바랍니다.

-(void)viewDidDisappear:(BOOL)animated{

    if (self.isMovingToParentViewController) {

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

    }
}

잊지 마세요[super viewDidDisappear:animated]
SamB

9

너무 늦었을지도 모르지만 이전에도 똑같은 행동을 원했습니다. 그리고 제가 사용한 솔루션은 현재 App Store에있는 앱 중 하나에서 잘 작동합니다. 비슷한 방법을 사용하는 사람을 본 적이 없기 때문에 여기에서 공유하고 싶습니다. 이 솔루션의 단점은 서브 클래 싱이 필요하다는 것 UINavigationController입니다. Method Swizzling을 사용하면 이를 피하는 데 도움이 될 수 있지만 그렇게 멀리 가지 않았습니다.

따라서 기본 뒤로 버튼은 실제로 UINavigationBar. 사용자가 뒤로 버튼을 탭하면 을 호출 UINavigationBar하여 델리게이트가 상단 UINavigationItem을 표시 해야하는지 물어 봅니다 navigationBar(_:shouldPop:). UINavigationController실제로 이것을 구현하지만 UINavigationBarDelegate(왜!?) 채택한다고 공개적으로 선언하지 않습니다 . 이 이벤트를 가로 채려면의 하위 클래스를 만들고 UINavigationController준수를 선언 UINavigationBarDelegate하고 구현 navigationBar(_:shouldPop:)합니다. true최상위 항목이 팝업되어야하는 경우 반환 합니다. false유지해야하는 경우 반환 합니다.

두 가지 문제가 있습니다. 첫 번째는 어느 시점에서 UINavigationController버전을 호출해야한다는 것 navigationBar(_:shouldPop:)입니다. 그러나 UINavigationBarController공개적으로에 대한 적합성을 선언하지 않고 UINavigationBarDelegate호출하려고하면 컴파일 시간 오류가 발생합니다. 내가 함께했던 해결책은 Objective-C 런타임을 사용하여 구현을 직접 가져와 호출하는 것입니다. 누구든지 더 나은 해결책이 있으면 알려주십시오.

다른 문제는 사용자가 뒤로 버튼을 탭하면 navigationBar(_:shouldPop:)먼저 호출 popViewController(animated:)됩니다. 뷰 컨트롤러가을 호출하여 팝되면 순서가 반대로됩니다 popViewController(animated:). 이 경우 사용자가 뒤로 버튼을 탭했음을 의미하는 popViewController(animated:)이전에 호출 되었는지 감지하기 위해 부울을 사용 navigationBar(_:shouldPop:)합니다.

또한 UIViewController내비게이션 컨트롤러가 뷰 컨트롤러에 사용자가 뒤로 버튼을 탭하면 팝업되어야하는지 묻도록 확장 합니다. 뷰 컨트롤러는 반환 false하여 필요한 작업을 수행하고 popViewController(animated:)나중에 호출 할 수 있습니다 .

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

컨트롤러보기에서 shouldBePopped(_:). 이 메서드를 구현하지 않으면 기본 동작은 사용자가 평소처럼 뒤로 버튼을 탭하자마자 뷰 컨트롤러를 팝업하는 것입니다.

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

여기 에서 내 데모를 볼 수 있습니다 .

여기에 이미지 설명 입력


이것은 멋진 솔루션이며 블로그 포스트에 구워 져야합니다! 내가 지금 찾고있는 것에 대해 과잉 인 것 같지만, 다른 상황에서는 시도해 볼 가치가 있습니다.
ASSeeger

6

"스택에서 뷰를 팝하기 전에"의 경우 :

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

5

viewController를 요청하는 것보다 더 적절한 방법이 있습니다. 컨트롤러를 뒤로 버튼이있는 navigationBar의 델리게이트로 만들 수 있습니다. 여기에 예가 있습니다. 뒤로 버튼 누르기를 처리하려는 컨트롤러의 구현에서 UINavigationBarDelegate 프로토콜을 구현할 것임을 알려줍니다.

@interface MyViewController () <UINavigationBarDelegate>

그런 다음 초기화 코드의 어딘가 (아마도 viewDidLoad)에서 컨트롤러를 탐색 모음의 대리자로 만듭니다.

self.navigationController.navigationBar.delegate = self;

마지막으로 shouldPopItem 메서드를 구현합니다. 이 메서드는 뒤로 버튼을 누르면 바로 호출됩니다. 스택에 여러 컨트롤러 또는 탐색 항목이있는 경우 예상 할 때만 사용자 지정 작업을 수행하도록 해당 탐색 항목 중 어떤 항목이 팝업되는지 확인하고 싶을 것입니다 (항목 매개 변수). 예를 들면 다음과 같습니다.

-(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
날 위해 일하지 않았어요. "*** 잡히지 않은 예외 'NSInternalInconsistencyException'로 인해 앱 종료 중, 이유 : '컨트롤러가 관리하는 UINavigationBar에서 대리자를 수동으로 설정할 수 없습니다.'"
DynamicDan

불행히도 UINavigationController에서는 작동하지 않으며 대신 UINavigationBar가있는 표준 UIViewController가 필요합니다. 이것은 NavigationController가 제공하는 여러 자동 뷰 컨트롤러 푸시 및 팝을 활용할 수 없음을 의미합니다. 죄송합니다!
Carlos Guzman 2014 년

NavigationBarController 대신 UINavigationBar를 사용한 다음 제대로 작동합니다. 질문이 NavigationBarController에 관한 것이라는 것을 알고 있지만이 솔루션은 간결합니다.
appsunited

3

"viewWillDisappear"또는 유사한 메서드를 사용할 수없는 경우 UINavigationController를 하위 클래스로 지정하십시오. 다음은 헤더 클래스입니다.

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

@interface CCNavigationController : UINavigationController

@property (nonatomic, strong) MyViewController *viewController;

@end

구현 클래스 :

#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

반면에이 viewController를 사용자 지정 NavigationController에 연결해야하므로 일반 viewController에 대한 viewDidLoad 메서드에서 다음을 수행합니다.

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

3

다음은 부모 뷰 컨트롤러가 푸시 한 자식 VC보다 먼저 작업을 수행하도록 구현 한 또 다른 방법입니다 (unwind segue로 테스트하지는 않았지만 다른 솔루션이이 페이지의 다른 솔루션에 대해 언급했듯이 아마도 차별화되지 않을 것입니다). 뷰 스택에서 튀어 나옵니다 (원래 UINavigationController에서 몇 수준 아래로 사용했습니다). 이것은 또한 childVC가 푸시되기 전에 작업을 수행하는 데 사용될 수 있습니다. 이는 사용자 정의 UIBarButtonItem 또는 UIButton을 생성하는 대신 iOS 시스템 뒤로 버튼으로 작업 할 수있는 추가 이점이 있습니다.

  1. 부모 VC가 UINavigationControllerDelegate프로토콜을 채택하고 위임 메시지를 등록하도록합니다.

    MyParentViewController : UIViewController <UINavigationControllerDelegate>
    
    -(void)viewDidLoad {
        self.navigationcontroller.delegate = self;
    }
  2. UINavigationControllerDelegate인스턴스 메서드를에서 구현하십시오 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. 위의 UINavigationControllerDelegate인스턴스 메서드 에서 특정 콜백 함수를 지정하는 경우

    -(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에서 나를 위해 작동하는 것입니다.

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

    super.viewWillDisappear(animated)
}

0

스토리 보드를 사용 중이고 푸시 세그에서 오는 경우 shouldPerformSegueWithIdentifier:sender:.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.