내비게이션 컨트롤러에 뷰를 눌렀는데 뒤로 버튼을 누르면 자동으로 이전 뷰로 이동합니다. 스택에서 뷰를 열기 전에 뒤로 버튼을 눌렀을 때 몇 가지 작업을 수행하고 싶습니다. 뒤로 버튼 콜백 기능은 무엇입니까?
내비게이션 컨트롤러에 뷰를 눌렀는데 뒤로 버튼을 누르면 자동으로 이전 뷰로 이동합니다. 스택에서 뷰를 열기 전에 뒤로 버튼을 눌렀을 때 몇 가지 작업을 수행하고 싶습니다. 뒤로 버튼 콜백 기능은 무엇입니까?
답변:
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];
}
제 생각에는 최고의 솔루션입니다.
- (void)didMoveToParentViewController:(UIViewController *)parent
{
if (![parent isEqual:self.parentViewController]) {
NSLog(@"Back pressed");
}
}
하지만 iOS5 +에서만 작동합니다.
사용자 확인과 같은 작업을 위해보기가 팝업 되기 전에 이벤트 를 처리 할 수 있도록 뒤로 버튼을 재정의하는 것이 좋습니다 .
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
뒤로 버튼을 누를 때 호출되는 메서드가 없습니다.
이 솔루션으로 끝납니다. 뒤로 버튼을 누르면 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]
너무 늦었을지도 모르지만 이전에도 똑같은 행동을 원했습니다. 그리고 제가 사용한 솔루션은 현재 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
}
}
여기 에서 내 데모를 볼 수 있습니다 .
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;
}
"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;
}
}
다음은 부모 뷰 컨트롤러가 푸시 한 자식 VC보다 먼저 작업을 수행하도록 구현 한 또 다른 방법입니다 (unwind segue로 테스트하지는 않았지만 다른 솔루션이이 페이지의 다른 솔루션에 대해 언급했듯이 아마도 차별화되지 않을 것입니다). 뷰 스택에서 튀어 나옵니다 (원래 UINavigationController에서 몇 수준 아래로 사용했습니다). 이것은 또한 childVC가 푸시되기 전에 작업을 수행하는 데 사용될 수 있습니다. 이는 사용자 정의 UIBarButtonItem 또는 UIButton을 생성하는 대신 iOS 시스템 뒤로 버튼으로 작업 할 수있는 추가 이점이 있습니다.
부모 VC가 UINavigationControllerDelegate
프로토콜을 채택하고 위임 메시지를 등록하도록합니다.
MyParentViewController : UIViewController <UINavigationControllerDelegate>
-(void)viewDidLoad {
self.navigationcontroller.delegate = self;
}
이 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;
}
위의 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;
}