在导航控制器中设置后退button的动作
我试图覆盖导航控制器中后退button的默认操作。 我在自定义button上提供了一个目标操作。 奇怪的是,当通过backbutton属性赋值时,它并不关注它们,它只是popup当前视图并返回到根:
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle: @"Servers" style:UIBarButtonItemStylePlain target:self action:@selector(home)]; self.navigationItem.backBarButtonItem = backButton;
只要我通过navigationItem上的leftBarButtonItem设置它,它会调用我的动作,但是这个button看起来像一个普通的圆形而不是箭头后面的一个:
self.navigationItem.leftBarButtonItem = backButton;
如何在返回到根视图之前调用我的自定义操作? 有没有办法来覆盖默认的后退动作,还是有一种方法,总是在离开视图时调用(viewDidUnload不这样做)?
尝试把它放到视图控制器中你想要检测的新闻:
-(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]; }
我已经实现了UIViewController-BackButtonHandler扩展。 它不需要子类的任何东西,只要把它放到你的项目中,并覆盖UIViewController
类中的navigationShouldPopOnBackButton
方法:
-(BOOL) navigationShouldPopOnBackButton { if(needsShowConfirmation) { // Show confirmation alert // ... return NO; // Ignore 'Back' button this time } return YES; // Process 'Back' button click and Pop view controler }
下载示例应用程序
不像Amagrammer说,这是可能的。 你必须inheritance你的navigationController。 我在这里解释了一切(包括示例代码): http : //www.hanspinckaers.com/custom-action-on-back-button-uinavigationcontroller
Swift版本:
(的https://stackoverflow.com/a/19132881/826435 )
在你的视图控制器中,你只需遵守一个协议,并执行你需要的任何操作:
extension MyViewController: NavigationControllerBackButtonDelegate { func shouldPopOnBackButtonPress() -> Bool { performSomeActionOnThePressOfABackButton() return false } }
然后创build一个类,说NavigationController+BackButton
,只是复制粘贴下面的代码:
protocol NavigationControllerBackButtonDelegate { func shouldPopOnBackButtonPress() -> Bool } extension UINavigationController { public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool { // Prevents from a synchronization issue of popping too many navigation items // and not enough view controllers or viceversa from unusual tapping if viewControllers.count < navigationBar.items!.count { return true } // Check if we have a view controller that wants to respond to being popped var shouldPop = true if let viewController = topViewController as? NavigationControllerBackButtonDelegate { shouldPop = viewController.shouldPopOnBackButtonPress() } if (shouldPop) { DispatchQueue.main.async { self.popViewController(animated: true) } } else { // Prevent the back button from staying in an disabled state for view in navigationBar.subviews { if view.alpha < 1.0 { UIView.animate(withDuration: 0.25, animations: { view.alpha = 1.0 }) } } } return false } }
直接做是不可能的。 有几个select:
- 创build您自己的自定义
UIBarButtonItem
,如果testing通过,则在点击时validation并popup - 使用
UITextField
委托方法(如-textFieldShouldReturn:
validation表单域内容,该方法在键盘上按下Return
或Done
button后调用
第一个选项的缺点是无法从自定义的button访问后退button的左箭头样式。 所以你必须使用一个图像或者使用常规的样式button。
第二个选项很好,因为您将文本字段返回到委托方法中,因此您可以将validation逻辑定位到发送给委托callback方法的特定文本字段。
由于一些线程的原因,@HansPinckaers提到的解决scheme并不适合我,但是我发现了一个更简单的方法来捕捉后退button,我想把它放在这里,以免这样可以避免几个小时的欺骗其他人。 这个技巧非常简单:只需在你的UINavigationBar中添加一个透明的UIButton作为子视图,并为你设置你的select器就好像它是真正的button! 这里有一个使用Monotouch和C#的例子,但是到objective-c的转换不应该太难find。
public class Test : UIViewController { public override void ViewDidLoad() { UIButton b = new UIButton(new RectangleF(0, 0, 60, 44)); //width must be adapted to label contained in button b.BackgroundColor = UIColor.Clear; //making the background invisible b.Title = string.Empty; // and no need to write anything b.TouchDown += delegate { Console.WriteLine("caught!"); if (true) // check what you want here NavigationController.PopViewControllerAnimated(true); // and then we pop if we want }; NavigationController.NavigationBar.AddSubview(button); // insert the button to the nav bar } }
有趣的事实:为了testing的目的,为我的假buttonfind好的尺寸,我把它的背景颜色设置为蓝色,并显示在后退button后面 ! 无论如何,它仍然捕捉任何触摸原始button。
这种技术允许您在不影响任何视图控制器的标题或在animation期间看到后退button文本更改的情况下,更改“后退”button的文本。
将其添加到调用视图控制器中的init方法中:
UIBarButtonItem *temporaryBarButtonItem = [[UIBarButtonItem alloc] init]; temporaryBarButtonItem.title = @"Back"; self.navigationItem.backBarButtonItem = temporaryBarButtonItem; [temporaryBarButtonItem release];
find了一个保留后退button样式的解决scheme。 将以下方法添加到您的视图控制器。
-(void) overrideBack{ UIButton *transparentButton = [[UIButton alloc] init]; [transparentButton setFrame:CGRectMake(0,0, 50, 40)]; [transparentButton setBackgroundColor:[UIColor clearColor]]; [transparentButton addTarget:self action:@selector(backAction:) forControlEvents:UIControlEventTouchUpInside]; [self.navigationController.navigationBar addSubview:transparentButton]; }
现在按照以下方法提供所需的function:
-(void)backAction:(UIBarButtonItem *)sender { //Your functionality }
它只是用一个透明的button覆盖后退button;)
我不相信这是可能的,很容易。 我相信解决这个问题的唯一方法就是让自己的后退button箭头图像放在那里。 起初我感到沮丧,但我明白,为了一致起见,为什么它被排除在外。
您可以通过创build常规button并隐藏默认后退button来closures(没有箭头):
self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:@"Servers" style:UIBarButtonItemStyleDone target:nil action:nil] autorelease]; self.navigationItem.hidesBackButton = YES;
通过ShouldPopItem
UINavigationBar
的委托方法并重写 ShouldPopItem
方法有一个更简单的方法 。
最简单的方法
您可以使用UINavigationController的委托方法。 方法willShowViewController
是当你的VC的后退button被调用。做任何你想要什么时候回btn按下
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
onegray的解决scheme是不安全的。根据苹果的官方文件, https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html ,我们应该避免这样做。
“如果某个类别中声明的方法的名称与原始类中的方法相同,或者是同一类(甚至是超类)上的另一个类中的方法相同,则对于使用哪种方法实现在运行时,如果您使用自己的类使用类别,则这不太可能成为问题,但在使用类别向标准Cocoa或Cocoa Touch类中添加方法时可能会导致问题。
使用Swift:
override func viewWillDisappear(animated: Bool) { super.viewWillDisappear(animated) if self.navigationController?.topViewController != self { print("back button tapped") } }
通过使用当前正在离开的目标和动作variables'nil',您应该能够连接保存对话框,以便在button被选中时调用它们。 注意,这可能会在奇怪的时刻触发。
我主要同意Amagrammer,但我不认为用箭头定制这个button是很难的。 我只是重命名后退button,拍摄一个屏幕截图,Photoshop所需的button大小,并将其作为button顶部的图像。
你可以尝试访问NavigationBars的右键项目,并设置它的select器属性…inheritance人参考UIBarButtonItem引用 ,另一件事情,如果这个工作将def工作是,将导航栏的右侧button项设置为一个自定义的UIBarButtonItem,你创build并设置其select器…希望这有助于
对于需要用户input的表单,我build议将其作为“模式”而不是导航堆栈的一部分进行调用。 这样,他们必须照顾业务的forms,然后你可以validation它,并消除它使用自定义button。 你甚至可以devise一个与你应用程序的其余部分看起来一样的导航栏,但是可以给你更多的控制权。
至less在Xcode 5中,有一个简单而不错的(不完美的)解决scheme。 在IB中,从“实用工具”窗格中将条形button项目拖出,并将其放在导航栏左侧的“后退”button所在的位置。 将标签设置为“后退”。 你将有一个functionbutton,你可以绑定到你的IBAction并closures你的viewController。 我正在做一些工作,然后触发一个放松的继续,它完美的作品。
不理想的是,这个button没有得到<箭头,并没有推进以前的VC标题,但我认为这是可以pipe理的。 为了我的目的,我把新的后退button设置为“完成”button,所以它的目的是清楚的。
IB导航器中最后还有两个“后退”button,但为了清晰起见,标记起来很简单。
迅速
override func viewWillDisappear(animated: Bool) { let viewControllers = self.navigationController?.viewControllers! if indexOfArray(viewControllers!, searchObject: self) == nil { // do something } super.viewWillDisappear(animated) } func indexOfArray(array:[AnyObject], searchObject: AnyObject)-> Int? { for (index, value) in enumerate(array) { if value as UIViewController == searchObject as UIViewController { return index } } return nil }
这种方法适用于我(但“后退”button不会有“<”号):
- (void)viewDidLoad { [super viewDidLoad]; UIBarButtonItem* backNavButton = [[UIBarButtonItem alloc] initWithTitle:@"Back" style:UIBarButtonItemStyleBordered target:self action:@selector(backButtonClicked)]; self.navigationItem.leftBarButtonItem = backNavButton; } -(void)backButtonClicked { // Do something... AppDelegate* delegate = (AppDelegate*)[[UIApplication sharedApplication] delegate]; [delegate.navController popViewControllerAnimated:YES]; }
这是我的Swift解决scheme。 在你的UIViewController的子类中,覆盖navigationShouldPopOnBackButton方法。
extension UIViewController { func navigationShouldPopOnBackButton() -> Bool { return true } } extension UINavigationController { func navigationBar(navigationBar: UINavigationBar, shouldPopItem item: UINavigationItem) -> Bool { if let vc = self.topViewController { if vc.navigationShouldPopOnBackButton() { self.popViewControllerAnimated(true) } else { for it in navigationBar.subviews { let view = it as! UIView if view.alpha < 1.0 { [UIView .animateWithDuration(0.25, animations: { () -> Void in view.alpha = 1.0 })] } } return false } } return true } }
@William的答案是正确的,但是,如果用户启动了一个向后滑动的手势,viewWillDisappear方法被调用,甚至'self'不会在导航堆栈中(即self.navigationController.viewControllers获胜不包含“自我”),即使滑动没有完成,视图控制器也没有实际popup。 因此,解决scheme将是:
-
禁用viewDidAppear中的手指滑动手势,只允许使用后退button,方法是:
if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) { self.navigationController.interactivePopGestureRecognizer.enabled = NO; }
-
或者直接使用viewDidDisappear代替,如下所示:
- (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; if (![self.navigationController.viewControllers containsObject:self]) { // back button was pressed or the the swipe-to-go-back gesture was // completed. We know this is true because self is no longer // in the navigation stack. } }
@oneway的答案是在捕捉导航栏后退button事件之前,这里是Swift 3版本。 由于UINavigationBarDelegate
不能用于UIViewController
,所以你需要创build一个委托,当navigationBar
shouldPop
被调用时将被触发。
@objc public protocol BackButtonDelegate { @objc optional func navigationShouldPopOnBackButton() -> Bool } extension UINavigationController: UINavigationBarDelegate { public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool { if viewControllers.count < (navigationBar.items?.count)! { return true } var shouldPop = true let vc = self.topViewController if vc.responds(to: #selector(vc.navigationShouldPopOnBackButton)) { shouldPop = vc.navigationShouldPopOnBackButton() } if shouldPop { DispatchQueue.main.async { self.popViewController(animated: true) } } else { for subView in navigationBar.subviews { if(0 < subView.alpha && subView.alpha < 1) { UIView.animate(withDuration: 0.25, animations: { subView.alpha = 1 }) } } } return false } }
然后,在你的视图控制器中添加委托function:
class BaseVC: UIViewController, BackButtonDelegate { func navigationShouldPopOnBackButton() -> Bool { if ... { return true } else { return false } } }
我意识到,我们经常要为用户添加一个警报控制器,以决定他们是否想要回去。 如果是这样,你总是可以在navigationShouldPopOnBackButton()
函数中return false
,并通过做这样的事情来closures你的视图控制器:
func navigationShouldPopOnBackButton() -> Bool { let alert = UIAlertController(title: "Warning", message: "Do you want to quit?", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { UIAlertAction in self.yes()})) alert.addAction(UIAlertAction(title: "No", style: .cancel, handler: { UIAlertAction in self.no()})) present(alert, animated: true, completion: nil) return false } func yes() { print("yes") DispatchQueue.main.async { _ = self.navigationController?.popViewController(animated: true) } } func no() { print("no") }
使用isMovingFromParentViewController
override func viewWillDisappear(animated: Bool) { super.viewWillDisappear(true) if self.isMovingFromParentViewController { // current viewController is removed from parent // do some work } }
拦截后退button,只需用透明的UIControl覆盖并截取触摸即可。
@interface MyViewController : UIViewController { UIControl *backCover; BOOL inhibitBackButtonBOOL; } @end @implementation MyViewController -(void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; // Cover the back button (cannot do this in viewWillAppear -- too soon) if ( backCover == nil ) { backCover = [[UIControl alloc] initWithFrame:CGRectMake( 0, 0, 80, 44)]; #if TARGET_IPHONE_SIMULATOR // show the cover for testing backCover.backgroundColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.15]; #endif [backCover addTarget:self action:@selector(backCoverAction) forControlEvents:UIControlEventTouchDown]; UINavigationBar *navBar = self.navigationController.navigationBar; [navBar addSubview:backCover]; } } -(void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [backCover removeFromSuperview]; backCover = nil; } - (void)backCoverAction { if ( inhibitBackButtonBOOL ) { NSLog(@"Back button aborted"); // notify the user why... } else { [self.navigationController popViewControllerAnimated:YES]; // "Back" } } @end
我迄今为止find的解决scheme并不是很好,但是对我来说很有用。 采取这个答案 ,我也检查我是否以编程方式popup:
- (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; if ((self.isMovingFromParentViewController || self.isBeingDismissed) && !self.isPoppingProgrammatically) { // Do your stuff here } }
您必须将该属性添加到您的控制器,并在以编程方式popup之前将其设置为YES:
self.isPoppingProgrammatically = YES; [self.navigationController popViewControllerAnimated:YES];
find新的方法来做到这一点:
Objective-C的
- (void)didMoveToParentViewController:(UIViewController *)parent{ if (parent == NULL) { NSLog(@"Back Pressed"); } }
迅速
override func didMoveToParentViewController(parent: UIViewController?) { if parent == nil { println("Back Pressed") } }
@ onegray的答案的迅速版本
protocol RequestsNavigationPopVerification { var confirmationTitle: String { get } var confirmationMessage: String { get } } extension RequestsNavigationPopVerification where Self: UIViewController { var confirmationTitle: String { return "Go back?" } var confirmationMessage: String { return "Are you sure?" } } final class NavigationController: UINavigationController { func navigationBar(navigationBar: UINavigationBar, shouldPopItem item: UINavigationItem) -> Bool { guard let requestsPopConfirm = topViewController as? RequestsNavigationPopVerification else { popViewControllerAnimated(true) return true } let alertController = UIAlertController(title: requestsPopConfirm.confirmationTitle, message: requestsPopConfirm.confirmationMessage, preferredStyle: .Alert) alertController.addAction(UIAlertAction(title: "Cancel", style: .Cancel) { _ in dispatch_async(dispatch_get_main_queue(), { let dimmed = navigationBar.subviews.flatMap { $0.alpha < 1 ? $0 : nil } UIView.animateWithDuration(0.25) { dimmed.forEach { $0.alpha = 1 } } }) return }) alertController.addAction(UIAlertAction(title: "Go back", style: .Default) { _ in dispatch_async(dispatch_get_main_queue(), { self.popViewControllerAnimated(true) }) }) presentViewController(alertController, animated: true, completion: nil) return false } }
现在在任何控制器中,只要符合RequestsNavigationPopVerification
并且这种行为是默认采用的。