ios 检测何时按下导航栏上的“后退”按钮
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/8228411/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Detecting when the 'back' button is pressed on a navbar
提问by elitalon
I need to perform some actions when the back button(return to previous screen, return to parent-view) button is pressed on a Navbar.
当导航栏上的后退按钮(返回上一屏幕,返回父视图)按钮被按下时,我需要执行一些操作。
Is there some method I can implement to catch the event and fire off some actions to pause and save data before the screen disappears?
是否有一些方法可以实现来捕获事件并触发一些操作以在屏幕消失之前暂停和保存数据?
回答by elitalon
UPDATE:According to some comments, the solution in the original answer does not seem to work under certain scenarios in iOS 8+. I can't verify that that is actually the case without further details.
更新:根据一些评论,原始答案中的解决方案在 iOS 8+ 的某些场景下似乎不起作用。如果没有进一步的细节,我无法证实情况确实如此。
For those of you however in that situation there's an alternative. Detecting when a view controller is being popped is possible by overriding willMove(toParentViewController:)
. The basic idea is that a view controller is being popped when parent
is nil
.
但是,对于那些在这种情况下的人,还有另一种选择。可以通过覆盖willMove(toParentViewController:)
. 基本思想是当parent
is时弹出视图控制器nil
。
Check out "Implementing a Container View Controller"for further details.
查看“实现容器视图控制器”以获取更多详细信息。
Since iOS 5 I've found that the easiest way of dealing with this situation is using the new method - (BOOL)isMovingFromParentViewController
:
从 iOS 5 开始,我发现处理这种情况的最简单方法是使用新方法- (BOOL)isMovingFromParentViewController
:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.isMovingFromParentViewController) {
// Do your stuff here
}
}
- (BOOL)isMovingFromParentViewController
makes sense when you are pushing and popping controllers in a navigation stack.
- (BOOL)isMovingFromParentViewController
当您在导航堆栈中推送和弹出控制器时是有意义的。
However, if you are presenting modal view controllers you should use - (BOOL)isBeingDismissed
instead:
但是,如果您要展示模态视图控制器,则应- (BOOL)isBeingDismissed
改用:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.isBeingDismissed) {
// Do your stuff here
}
}
As noted in this question, you could combine both properties:
如本问题所述,您可以组合这两个属性:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.isMovingFromParentViewController || self.isBeingDismissed) {
// Do your stuff here
}
}
Other solutions rely on the existence of a UINavigationBar
. Instead like my approach more because it decouples the required tasks to perform from the action that triggered the event, i.e. pressing a back button.
其他解决方案依赖于UINavigationBar
. 相反,我更喜欢我的方法,因为它将需要执行的任务与触发事件的操作(即按下后退按钮)分离开来。
回答by WCByrne
While viewWillAppear()
and viewDidDisappear()
arecalled when the back button is tapped, they are also called at other times. See end of answer for more on that.
虽然viewWillAppear()
和viewDidDisappear()
被当返回按钮被窃听叫,它们也被称为在其他时间。有关更多信息,请参阅答案结尾。
Using UIViewController.parent
使用 UIViewController.parent
Detecting the back button is better done when the VC is removed from it's parent (the NavigationController) with the help of willMoveToParentViewController(_:)
OR didMoveToParentViewController()
在willMoveToParentViewController(_:)
OR的帮助下,当 VC 从其父级(NavigationController)中移除时,检测后退按钮会更好didMoveToParentViewController()
If parent is nil, the view controller is being popped off the navigation stack and dismissed. If parent is not nil, it is being added to the stack and presented.
如果 parent 为 nil,则视图控制器将从导航堆栈中弹出并解除。如果 parent 不为零,则将其添加到堆栈中并呈现。
// 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 out willMove
for didMove
and check self.parent to do work afterthe view controller is dismissed.
换出willMove
了didMove
和检查self.parent做工作后视图控制器被驳回。
Stopping the dismiss
停止解雇
Do note, checking the parent doesn't allow you to "pause" the transition if you need to do some sort of async save. To do that you could implement the following. Only downside here is you lose the fancy iOS styled/animated back button. Also be careful here with the interactive swipe gesture. Use the following to handle this case.
请注意,如果您需要进行某种异步保存,检查父级不允许您“暂停”转换。为此,您可以执行以下操作。唯一的缺点是你失去了漂亮的 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
}
}
}
More on view will/did appear
更多内容将会/确实出现
If you didn't get the viewWillAppear
viewDidDisappear
issue, Let's run through an example. Say you have three view controllers:
如果你没有明白这个viewWillAppear
viewDidDisappear
问题,让我们来看一个例子。假设你有三个视图控制器:
- ListVC: A table view of things
- DetailVC: Details about a thing
- SettingsVC: Some options for a thing
- ListVC:事物的表格视图
- DetailVC:有关事物的详细信息
- SettingsVC:事物的一些选项
Lets follow the calls on the detailVC
as you go from the listVC
to settingsVC
and back to listVC
detailVC
当您从listVC
到settingsVC
和 返回到 时,让我们跟随呼叫listVC
List > Detail(push detailVC) Detail.viewDidAppear
<- appear
Detail > Settings(push settingsVC) Detail.viewDidDisappear
<- disappear
List>Detail(推送detailVC) Detail.viewDidAppear
<-出现
Detail>Settings(推送settingsVC) Detail.viewDidDisappear
<-消失
And as we go back...
Settings > Detail(pop settingsVC) Detail.viewDidAppear
<- appear
Detail > List(pop detailVC) Detail.viewDidDisappear
<- disappear
当我们回去...
Settings > Detail(pop settingsVC) Detail.viewDidAppear
<- 出现
Detail > List(pop detailVC) Detail.viewDidDisappear
<- 消失
Notice that viewDidDisappear
is called multiple times, not only when going back, but also when going forward. For a quick operation that may be desired, but for a more complex operation like a network call to save, it may not.
请注意,viewDidDisappear
它被多次调用,不仅在返回时,而且在前进时也是如此。对于可能需要的快速操作,但对于更复杂的操作(如网络调用保存),则可能不需要。
回答by Jawad Zeb
First Method
第一种方法
- (void)didMoveToParentViewController:(UIViewController *)parent
{
if (![parent isEqual:self.parentViewController]) {
NSLog(@"Back pressed");
}
}
Second Method
第二种方法
-(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];
}
回答by 7ynk3r
I've playing (or fighting) with this problem for two days. IMO the best approach is just to create an extension class and a protocol, like this:
我已经玩(或打)这个问题两天了。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
This works because UINavigationController
will receive a call to navigationBar:shouldPopItem:
every time a view controller is popped. There we detect if back was pressed or not (any other button).
The only thing you have to do is implement the protocol in the view controller where back is pressed.
这是有效的,因为每次弹出视图控制器时UINavigationController
都会收到一个调用navigationBar:shouldPopItem:
。在那里我们检测是否按下了返回(任何其他按钮)。您唯一需要做的就是在按下后退的视图控制器中实现协议。
Remember to manually pop the view controller inside backButtonPressedSel
, if everything is ok.
backButtonPressedSel
如果一切正常,请记住手动弹出视图控制器。
If you already have subclassed UINavigationViewController
and implemented navigationBar:shouldPopItem:
don't worry, this won't interfere with it.
如果您已经子类化UINavigationViewController
并实现了,navigationBar:shouldPopItem:
请不要担心,这不会干扰它。
You may also be interested in disable the back gesture.
您可能还对禁用后退手势感兴趣。
if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
回答by Chris Villa
This works for me in iOS 9.3.x with Swift:
这在带有 Swift 的 iOS 9.3.x 中对我有用:
override func didMoveToParentViewController(parent: UIViewController?) {
super.didMoveToParentViewController(parent)
if parent == self.navigationController?.parentViewController {
print("Back tapped")
}
}
Unlike other solutions here, this doesn't seem to trigger unexpectedly.
与此处的其他解决方案不同,这似乎不会意外触发。
回答by matt
Those who claim that this doesn't work are mistaken:
那些声称这不起作用的人是错误的:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if self.isMovingFromParent {
print("we are being popped")
}
}
That works fine. So what is causing the widespread myth that it doesn't?
这很好用。那么是什么导致了普遍的神话,它没有呢?
The problem seems to be due to an incorrect implementation of a differentmethod, namely that the implementation of willMove(toParent:)
forgot to call super
.
问题似乎是由于不同方法的错误实现,即willMove(toParent:)
忘记调用的实现super
。
If you implement willMove(toParent:)
without calling super
, then self.isMovingFromParent
will be false
and the use of viewWillDisappear
will appear to fail. It didn't fail; you broke it.
如果您在willMove(toParent:)
不调用 的情况下实现super
,则self.isMovingFromParent
willfalse
和 的使用viewWillDisappear
将出现失败。它没有失败;你打破了它。
回答by Paul Brady
For the record, I think this is more of what he was looking for…
为了记录,我认为这更多是他正在寻找的......
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];
}
回答by Ferran Maylinch
As purrrminator
says, the answer by elitalon
is not completely right, since your stuff
would be executed even when popping the controller programmatically.
如上所述purrrminator
,答案elitalon
并不完全正确,因为your stuff
即使以编程方式弹出控制器也会执行。
The solution I have found so far is not very nice, but it works for me. Besides what elitalon
said, I also check whether I'm popping programmatically or not:
到目前为止我找到的解决方案不是很好,但它对我有用。除了elitalon
所说的,我还检查我是否以编程方式弹出:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if ((self.isMovingFromParentViewController || self.isBeingDismissed)
&& !self.isPoppingProgrammatically) {
// Do your stuff here
}
}
You have to add that property to your controller and set it to YES before popping programmatically:
在以编程方式弹出之前,您必须将该属性添加到控制器并将其设置为 YES:
self.isPoppingProgrammatically = YES;
[self.navigationController popViewControllerAnimated:YES];
Thanks for your help!
谢谢你的帮助!
回答by Eric
I have solved this problem by adding a UIControl to the navigationBar on the left side .
我通过向左侧的导航栏添加一个 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];
And you need to remember to remove it when view will disappear:
并且您需要记住在视图消失时将其删除:
- (void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if (self.leftItemControl) {
[self.leftItemControl removeFromSuperview];
}
}
That's all!
就这样!
回答by Pedro Magalh?es
You can use the back button callback, like this:
您可以使用后退按钮回调,如下所示:
- (BOOL) navigationShouldPopOnBackButton
{
[self backAction];
return NO;
}
- (void) backAction {
// your code goes here
// show confirmation alert, for example
// ...
}
for swift version you can do something like in global scope
对于 swift 版本,您可以在全局范围内执行类似操作
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
}
}
Below one you put in the viewcontroller where you want to control back button action:
在您要控制后退按钮操作的视图控制器中放入下面的一个:
override func navigationShouldPopOnBackButton() -> Bool {
self.backAction()//Your action you want to perform.
return true
}