탐색 표시 줄에서 '뒤로'버튼을 누를 때 감지


135

Navbar에서 뒤로 버튼 (이전 화면으로 돌아 가기, 부모보기로 돌아 가기) 버튼을 누르면 몇 가지 작업을 수행해야합니다.

이벤트를 포착하고 화면이 사라지기 전에 데이터를 일시 중지하고 저장하기 위해 일부 작업을 실행하기 위해 구현할 수있는 방법이 있습니까?




나는 이런 식으로 결정을 보여주었습니다
Taras

답변:


316

업데이트 : 일부 의견에 따르면, 원래 답변의 솔루션은 iOS 8 이상의 특정 시나리오에서 작동하지 않는 것 같습니다. 추가 세부 정보가 없으면 실제로 해당되는지 확인할 수 없습니다.

그러나 그러한 상황에서는 대안이 있습니다. 재정 의하여 뷰 컨트롤러가 갑자기 터지는시기를 감지 할 수 있습니다 willMove(toParentViewController:). 기본 아이디어는 is 일 때 뷰 컨트롤러가 팝업되는 것 parent입니다 nil.

확인 "컨테이너 뷰 컨트롤러 구현" 자세한 내용은.


iOS 5 부터이 상황을 처리하는 가장 쉬운 방법은 새로운 방법을 사용하는 것입니다 - (BOOL)isMovingFromParentViewController.

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

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

- (BOOL)isMovingFromParentViewController 탐색 스택에서 컨트롤러를 밀거나 튀길 때 의미가 있습니다.

그러나 모달 뷰 컨트롤러를 제시하는 경우 - (BOOL)isBeingDismissed대신 사용해야 합니다.

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

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

이 질문 에서 언급했듯이 두 속성을 결합 할 수 있습니다.

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

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

다른 솔루션은의 존재에 의존합니다 UINavigationBar. 대신 이벤트를 트리거 한 작업 (예 : 뒤로 버튼 누름)에서 수행해야하는 작업을 분리하기 때문에 내 접근 방식이 더 좋습니다.


나는 당신이 대답하는 것을 좋아합니다. 하지만 왜 'self.isBeingDismissed'를 사용 했습니까? 필자의 경우 'self.isBeingDismissed'의 문이 구현되지 않았습니다.
Rutvij Kotecha

3
self.isMovingFromParentViewControllerpopToRootViewControllerAnimated뒤로 버튼을 터치하지 않고 프로그래밍 방식으로 탐색 스택을 팝 할 때 TRUE 값이 있습니다. 답을 내려야합니까? (피험자는 "
탐색기

2
대단한 답변입니다. 대단히 감사합니다. 스위프트 나는 사용 :override func viewWillDisappear(animated: Bool) { super.viewWillDisappear(animated) if isMovingFromParentViewController(){ println("back button pressed") } }
카밀로 Visini

1
당신은 단지 내에서이 작업을 수행해야 -viewDidDisappear:당신이 얻을 것이다있을 가능성이 있기 때문에 -viewWillDisappear:없이 -viewDidDisappear:그 슬쩍 당신이 탐색 컨트롤러 항목을 해제 보내 주시면 시작할 때처럼 (후 취소 할 수 있습니다.
히스 테두리를

3
더 이상 신뢰할만한 솔루션이 아닌 것 같습니다. 내가 이것을 처음 사용할 때 일했습니다 (iOS 10이었습니다). 그러나 이제 우연히 작동이 중단되었습니다 (iOS 11). "willMove (toParentViewController)"솔루션으로 전환했습니다.
Vitalii

100

하지만 viewWillAppear()하고 viewDidDisappear() 있다 '뒤로'버튼을 탭 때 호출, 그들은 또한 다른 시간에 호출됩니다. 이에 대한 자세한 내용은 답변 끝을 참조하십시오.

UIViewController.parent 사용

뒤로 버튼을 감지하는 것은 willMoveToParentViewController(_:)OR 의 도움으로 VC가 부모 (NavigationController)에서 제거 될 때 더 잘 수행됩니다.didMoveToParentViewController()

parent가 nil이면 뷰 컨트롤러가 탐색 스택에서 튀어 나와 해제됩니다. 부모가 0이 아닌 경우 스택에 추가되고 표시됩니다.

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

스왑 willMove에 대한 didMove체크의 self.parent 작업을 수행하는 뷰 컨트롤러가 그라운드를 떠납니다.

해고 중지

비동기 저장을 수행해야하는 경우 부모를 확인하면 전환을 "일시 중지"할 수 없습니다. 이를 위해 다음을 구현할 수 있습니다. 여기서 단점은 멋진 iOS 스타일 / 애니메이션 뒤로 버튼을 잃는다는 것입니다. 또한 대화식 스 와이프 제스처로주의하십시오. 이 사례를 처리하려면 다음을 사용하십시오.

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


더 많은 볼 것 / 나타남

viewWillAppear viewDidDisappear문제가 발생 하지 않은 경우 예제를 살펴 보겠습니다. 세 개의 뷰 컨트롤러가 있다고 가정 해보십시오.

  1. ListVC : 사물의 테이블보기
  2. DetailVC : 일에 대한 세부 사항
  3. 설정 VC : 사물의 일부 옵션

상의 호출을 수행 할 수 있습니다 detailVC당신은에서 이동로 listVCsettingsVC다시에listVC

목록> 세부 사항 (push detailVC) Detail.viewDidAppear<-나타나는
세부 사항> 설정 (푸시 설정 VC ) Detail.viewDidDisappear<-사라짐

그리고 돌아가서 ...
설정> 세부 사항 (팝 설정 VC) <- Detail.viewDidAppear나타나는
세부 사항> 목록 (팝 세부 사항 VC) Detail.viewDidDisappear<-사라짐

공지 사항 viewDidDisappear다시가는뿐만 아니라 향후하지 않을 경우에만, 여러 번이라고합니다. 빠른 작업이 필요할 수 있지만 네트워크 호출과 같은 더 복잡한 작업의 경우 저장하지 않을 수 있습니다.


참고로, 사용자 didMoveToParantViewController:는보기가 더 이상 보이지 않을 때 작업해야합니다. interactiveGesutre와 iOS7에 대한 유용한
WCByrne

didMoveToParentViewController * 오타가 있습니다
thewormsterror

[super willMoveToParentViewController : parent]를 호출하는 것을 잊지 마십시오!
ScottyB

2
상위 뷰 컨트롤러에 팝업을 표시하면 상위 매개 변수가 nil이고이 메소드가 표시되는 뷰가 표시되면 nil이 아닙니다. 이 사실을 사용하여보기에 도달 할 때가 아니라 뒤로 단추를 눌렀을 때만 작업을 수행 할 수 있습니다. 그것은 결국 원래의 질문이었습니다. :)
Mike

1
프로그래밍 방식으로을 사용할 때도 호출 _ = self.navigationController?.popViewController(animated: true)되므로 뒤로 버튼을 누를 때만 호출되는 것은 아닙니다. 뒤로를 누를 때만 작동하는 통화를 찾고 있습니다.
Ethan Allen

16

첫 번째 방법

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

두 번째 방법

-(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
두 번째 방법은 나를 위해 일한 유일한 방법이었습니다. 첫 번째 방법은 내 견해를 제시 할 때도 사용되었으므로 사용 사례에 적합하지 않습니다.
marcshilling

10

이것이 효과가 없다고 주장하는 사람들은 착각합니다.

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

잘 작동합니다. 그렇다면 광범위한 신화를 일으키는 원인은 무엇입니까?

문제는 다른 메소드 의 잘못된 구현 , 즉 구현 willMove(toParent:)을 호출 하는 것을 잊었 기 때문인 것으로 보입니다 super.

당신이 구현하는 경우 willMove(toParent:)호출하지 않고 super, 다음 self.isMovingFromParent될 것입니다 false와의 사용은 viewWillDisappear실패하게 나타납니다. 실패하지 않았다. 넌 파산 했어

참고 : 진짜 문제는 보통이고 두 번째 것을 검출하는 뷰 컨트롤러 첫 번째 뷰 컨트롤러가 튀어되었다. Unified UIViewController "가장 앞쪽에"감지되는 일반적인 토론도 참조하십시오.

편집 의견은 이것이 viewDidDisappear아닌이어야 한다고 제안합니다 viewWillDisappear.


이 코드는 뒤로 버튼을 누를 때 실행되지만 VC가 프로그래밍 방식으로 팝업되는 경우에도 실행됩니다.
biomiker

@biomiker 물론이지만 다른 접근 방식에서도 마찬가지입니다. 터지는 소리가납니다. 문제는 프로그래밍 방식으로 팝업 하지 않았을 때 팝을 감지하는 방법 입니다. 프로그래밍 방식으로 팝업하는 경우 이미 터지는 것을 알고 있으므로 감지 할 항목이 없습니다.
matt

예, 이것은 다른 여러 접근 방식에 해당하며 그 중 상당수는 비슷한 의견을 가지고 있습니다. 나는 이것이 특정 반박에 대한 최근의 답변 이었기 때문에 분명히 알았으며 그것을 읽을 때 희망을 얻었습니다. 그러나 레코드의 경우 문제는 뒤로 버튼의 누름을 감지하는 방법입니다. 뒤로 버튼을 눌렀는지 여부를 표시하지 않고 뒤로 버튼을 누르지 않은 상황에서도 실행되는 코드는 질문이 더 많았을지라도 실제 질문을 완전히 해결하지 못한다고 말하는 것이 합리적입니다. 그 시점에서 명시 적입니다.
biomiker

1
불행히도 true스 와이프가 완전히 튀어 나오지 않더라도 대화 형 스 와이프 팝 제스처 (뷰 컨트롤러의 왼쪽 가장자리에서)로 돌아갑니다 . 그래서 그 대신에 그것을 검사 willDisappear에 그렇게 didDisappear작동합니다.
badhanganesh

1
@badhanganesh 감사합니다. 해당 정보를 포함하도록 수정 된 답변입니다.
매트

9

이 문제로 이틀 동안 게임을했습니다. IMO의 가장 좋은 방법은 다음과 같이 확장 클래스와 프로토콜을 만드는 것입니다.

@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

이것은 뷰 컨트롤러가 팝업 될 때마다 UINavigationController호출을 받기 때문에 작동합니다 navigationBar:shouldPopItem:. 여기에서 뒤로 밀렸는지 여부를 감지합니다 (다른 버튼). 당신이해야 할 유일한 것은 다시 누르면 뷰 컨트롤러에서 프로토콜을 구현하는 것입니다.

backButtonPressedSel모든 것이 정상이면 내부에보기 컨트롤러를 수동으로 팝해야합니다 .

이미 서브 클래 싱 UINavigationViewController되고 구현 된 navigationBar:shouldPopItem:것을 염려하지 않아도 방해가되지 않습니다.

뒤로 제스처 비활성화에 관심이있을 수도 있습니다.

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

1
이 답변은 2 개의 뷰 컨트롤러가 종종 팝업되는 것을 제외하고는 거의 완벽했습니다. YES를 반환하면 호출 메서드가 pop을 호출하므로 pop을 호출하면 2 개의 viewcontroller가 팝업됩니다. 자세한 살충제에 대한 다른 질문에 대한이 답변 (더 upvotes을받을 권리가 아주 좋은 대답)를 참조하십시오 stackoverflow.com/a/26084150/978083
제이슨 리지

좋은 지적, 내 설명은 그 사실에 대해 명확하지 않았다. "모든 것이 정상인 경우 수동으로 뷰 컨트롤러를 팝업해야 함"은 "NO"를 반환하는 경우에만 해당되며, 그렇지 않으면 흐름은 일반 팝입니다.
7ynk3r

1
"else"브랜치의 경우, 팝을 직접 처리하고 옳다고 생각하는 것을 반환하지 않으려면 수퍼 구현을 호출하는 것이 좋습니다. .
벤 싱클레어

9

이것은 Swift가있는 iOS 9.3.x에서 작동합니다.

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

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

다른 솔루션과 달리 예기치 않은 트리거는 아닙니다.


대신 willMove를 사용하는 것이 좋습니다
Eugene Gordin

4

기록을 위해, 나는 이것이 그가 찾고 있던 것 이상이라고 생각합니다.

    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
고마워 Paul,이 솔루션은 매우 간단합니다. 불행히도 아이콘은 다릅니다. 이것은 뒤로 아이콘이 아니라 "되감기"아이콘입니다. 뒤로 아이콘을 사용하는 방법이있을 수 있습니다 ...
Ferran Maylinch

2

말했듯 purrrminator이, by by에 의한 답 은 컨트롤러가 프로그래밍 방식으로 팝 될 때조차 실행될 것이기 elitalon때문에 완전하지 않습니다 your stuff.

지금까지 찾은 솔루션은 그리 좋지는 않지만 나에게 효과적입니다. elitalon말한 것 외에도 프로그래밍 방식으로 터지는 지 확인합니다.

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

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

프로그래밍 방식으로 팝업하기 전에 해당 속성을 컨트롤러에 추가하고 YES로 설정해야합니다.

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

당신의 도움을 주셔서 감사합니다!


2

가장 좋은 방법은 UINavigationController 델리게이트 메소드를 사용하는 것입니다

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

이를 사용하여 UINavigationController를 표시하는 컨트롤러를 알 수 있습니다.

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

정답으로 표시해야합니다! 사람들에게 생각 나게하기 위해 한 줄을 더 추가하고 싶을 수도 있습니다-> self.navigationController.delegate = self;
Mike Critchley

2

왼쪽의 navigationBar에 UIControl을 추가 하여이 문제를 해결했습니다.

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

그리고보기가 사라질 때 제거해야합니다.

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

그게 다야!


2

다음과 같이 뒤로 버튼 콜백을 사용할 수 있습니다.

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

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

신속한 버전의 경우 글로벌 범위와 같은 작업을 수행 할 수 있습니다

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

아래에서 뒤로 버튼 동작을 제어하려는 뷰 컨트롤러에 넣습니다.

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

    return true
}

1
왜 누군가가 아래로 투표했는지 몰라 이것은 가장 좋은 대답 인 것 같습니다.
Avinash

@Avinash 어디 navigationShouldPopOnBackButton에서 왔습니까? 공개 API의 일부가 아닙니다.
elitalon

@elitalon 죄송합니다.이 답변은 절반입니다. 나는 남아있는 맥락이 문제라고 생각했다. 어쨌든 지금 답변을 업데이트했습니다
Avinash

1

Coli88이 말했듯이 UINavigationBarDelegate 프로토콜을 확인해야합니다.

보다 일반적인 방식으로, - (void)viewWillDisapear:(BOOL)animated현재 보이는 뷰 컨트롤러가 보유한 뷰가 사라질 때를 사용하여 사용자 정의 작업을 수행 할 수도 있습니다 . 불행히도, 이것은 푸시와 팝 케이스를 귀찮게 할 것입니다.


1

UINavigationController를 사용하는 Swift의 경우 :

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

1

7ynk3r 의 답변은 실제로 내가 사용한 것과 거의 비슷하지만 약간의 조정이 필요했습니다.

- (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 및 9에서 작동하지 않습니다.

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

-1

(빠른)

finaly found solution .. 우리가 찾고 있던 방법은 UINavigationController의 대리자 메소드 인 "willShowViewController"입니다.

//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
이 접근법의 문제점은에 결합 MyViewController한다는 것 PushedController입니다.
clozach
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.