问题 使用MonoTouch的UINavigationController和UINavigationBarDelegate.ShouldPopItem()


当点击UINavigationBar的后退按钮(由UINavigationController控制)时,如何弹出UIAlertView?在某些情况下,我想问用户“你确定吗?”问题的类型,以便他可以中止操作并保持当前视图或弹出导航堆栈并转到父视图。

我发现最吸引人的方法是在UINavigationBar的Delegate上覆盖ShouldPopItem()。

现在,这里有一个非常相似的问题: iphone navigationController:在退出当前视图之前等待uialertview响应

还有一些类似性质的其他问题,例如: 检查UIViewController是否即将从导航堆栈中弹出?  和 如何判断在UINavigationControllerStack中按下后退按钮的时间

所有这些状态“子类UINavigationController”作为可能的答案。

然后有一个像子类UINavigationController一样读取通常不是一个好主意: Monotouch:UINavigationController,覆盖initWithRootViewController

苹果文档 还要说UINavigationController不是用于子类的。

其他一些人声称在使用UINavigationController时甚至不能覆盖ShouldPopItem(),因为它不允许将自定义/子类UINavigationBarDelegate分配给UINavigationBar。

我的子类化尝试都没有奏效,我的自定义代表未被接受。

我还在某处读到可能在我的自定义UINavigationController中实现ShouldPopItem(),因为它将自己指定为其UINavigationBar的Delegate。

没什么好吃的,这没用。 UINavigationController的子类如何知道属于UINavigationBarDelegate的Methods。它遭到拒绝:“找不到合适的方法来覆盖”。删除已编译的“override”关键字,但完全忽略该方法(如预期的那样)。我认为,使用Obj-C可以实现几个协议(类似于C#AFAIK中的接口)来实现这一点。不幸的是,UINavigationBarDelegate不是一个接口而是MonoTouch中的一个类,所以这似乎是不可能的。

我在这里几乎迷路了。当UINavigationBar由UINavigationController控制时,如何在UINavigationBar的Delegate上覆盖ShouldPopItem()?或者是否有其他方法可以弹出UIAlertView并在可能弹出导航堆栈之前等待它的结果?


8710
2018-06-20 15:24


起源



答案:


这篇文章有点旧,但如果你仍然对解决方案感兴趣(尽管仍然涉及子类化):

这实现了“你确定要退出吗?”按下后退按钮时发出警报,从这里的代码修改: http://www.hanspinckaers.com/custom-action-on-back-button-uinavigationcontroller/

事实证明,如果在CustomNavigationController中实现UINavigationBarDelegate,则可以使用shouldPopItem方法:


CustomNavigationController.h:

#import <Foundation/Foundation.h>

@interface CustomNavigationController : UINavigationController <UIAlertViewDelegate, UINavigationBarDelegate> {

BOOL alertViewClicked;
BOOL regularPop;
}

@end

CustomNavigationController.m:

#import "CustomNavigationController.h"
#import "SettingsTableController.h"

@implementation CustomNavigationController


- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {

if (regularPop) {
    regularPop = FALSE;
    return YES;
}

if (alertViewClicked) {
    alertViewClicked = FALSE;
    return YES;
}

if ([self.topViewController isMemberOfClass:[SettingsTableViewController class]]) {
    UIAlertView * exitAlert = [[[UIAlertView alloc] initWithTitle:@"Are you sure you want to quit?" message:nil delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Yes", nil] autorelease];

    [exitAlert show];

    return NO;

}   
else {
    regularPop = TRUE;
    [self popViewControllerAnimated:YES];
    return NO;
}   
}

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == 0) {
    //Cancel button
}

else if (buttonIndex == 1) {    
        //Yes button
    alertViewClicked = TRUE;
    [self popViewControllerAnimated:YES];
}           
}

@end

使用“regularPop”bool的奇怪逻辑是因为某些原因只是在shouldPopItem上返回“YES”只弹出导航栏,而不是与navBar关联的视图 - 为此,你必须直接调用popViewControllerAnimated(然后调用shouldPopItem as它的一部分逻辑。)


7
2017-09-17 09:58



感谢您的回复,但我不认为这适用于MonoTouch。问题是(如问题中所述) UINavigationBarDelegate 不是协议/接口,而是MonoTouch中的类。该 CustomNavigationController 必须从两者继承 UINavigationController和 UINavigationBarDelegate 这是不可能的AFAIK。 - riha
谢谢,这完美无缺。花了我几个小时试图找出为什么只有条形图会弹出而不是视图,并且确实添加了“regularPop”,因为你建议解决问题。 - Enzo Tran
我刚刚尝试了当前的iOS 7.1,它仍然运行良好。 - arlomedia
实际上我发现如果我从我的代码中的任何其他地方调用popViewControllerAnimated,手动弹出一个项目,这个代码的存在会导致视图弹出但导航栏保持不变(与原始问题相反) 。在返回[super popViewControllerAnimated]之前,我还必须覆盖popViewControllerAnimated方法并将regularPop设置为YES。 - arlomedia
如果你想使用那个方法,还需要覆盖popToViewController。我没有尝试过popToRootViewController,但这可能是一样的。 - arlomedia


答案:


这篇文章有点旧,但如果你仍然对解决方案感兴趣(尽管仍然涉及子类化):

这实现了“你确定要退出吗?”按下后退按钮时发出警报,从这里的代码修改: http://www.hanspinckaers.com/custom-action-on-back-button-uinavigationcontroller/

事实证明,如果在CustomNavigationController中实现UINavigationBarDelegate,则可以使用shouldPopItem方法:


CustomNavigationController.h:

#import <Foundation/Foundation.h>

@interface CustomNavigationController : UINavigationController <UIAlertViewDelegate, UINavigationBarDelegate> {

BOOL alertViewClicked;
BOOL regularPop;
}

@end

CustomNavigationController.m:

#import "CustomNavigationController.h"
#import "SettingsTableController.h"

@implementation CustomNavigationController


- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {

if (regularPop) {
    regularPop = FALSE;
    return YES;
}

if (alertViewClicked) {
    alertViewClicked = FALSE;
    return YES;
}

if ([self.topViewController isMemberOfClass:[SettingsTableViewController class]]) {
    UIAlertView * exitAlert = [[[UIAlertView alloc] initWithTitle:@"Are you sure you want to quit?" message:nil delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Yes", nil] autorelease];

    [exitAlert show];

    return NO;

}   
else {
    regularPop = TRUE;
    [self popViewControllerAnimated:YES];
    return NO;
}   
}

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == 0) {
    //Cancel button
}

else if (buttonIndex == 1) {    
        //Yes button
    alertViewClicked = TRUE;
    [self popViewControllerAnimated:YES];
}           
}

@end

使用“regularPop”bool的奇怪逻辑是因为某些原因只是在shouldPopItem上返回“YES”只弹出导航栏,而不是与navBar关联的视图 - 为此,你必须直接调用popViewControllerAnimated(然后调用shouldPopItem as它的一部分逻辑。)


7
2017-09-17 09:58



感谢您的回复,但我不认为这适用于MonoTouch。问题是(如问题中所述) UINavigationBarDelegate 不是协议/接口,而是MonoTouch中的类。该 CustomNavigationController 必须从两者继承 UINavigationController和 UINavigationBarDelegate 这是不可能的AFAIK。 - riha
谢谢,这完美无缺。花了我几个小时试图找出为什么只有条形图会弹出而不是视图,并且确实添加了“regularPop”,因为你建议解决问题。 - Enzo Tran
我刚刚尝试了当前的iOS 7.1,它仍然运行良好。 - arlomedia
实际上我发现如果我从我的代码中的任何其他地方调用popViewControllerAnimated,手动弹出一个项目,这个代码的存在会导致视图弹出但导航栏保持不变(与原始问题相反) 。在返回[super popViewControllerAnimated]之前,我还必须覆盖popViewControllerAnimated方法并将regularPop设置为YES。 - arlomedia
如果你想使用那个方法,还需要覆盖popToViewController。我没有尝试过popToRootViewController,但这可能是一样的。 - arlomedia


作为参考,我放弃后采取的路线 ShouldPopItem() 是用一个替换后退按钮 UIBarButtonItem 有自定义 UIButton 如此分配 CustomView。该 UIButton 精心设计看起来像原始后退按钮使用两个图像为正常和按下状态。最后,需要隐藏原始后退按钮。

对于它应该做的事情来说,代码太多了。所以是的,谢谢Apple。

BTW:另一种可能性是创造一个 UIButton 随着秘密 UIButtonType 101(实际上是后退按钮)但我避免了这个,因为它可能会在以后的任何iOS版本中中断。


3
2017-09-19 06:11



我知道这是垃圾。但是,似乎没有更好的方法。 - riha


仅覆盖 UINavigationBarDelegate 方法 UINavigationController 子类,它应该只是工作。当您从代码中推送或弹出视图控制器时,不仅在按下后退按钮时,还要小心调用协议方法。这是因为它们是按下/弹出通知而非按钮按下动作。


3
2017-09-17 13:15





Xamarin确实提供了 IUINavigationBarDelegate 界面允许你实现 UINavigationBarDelegate 作为您的自定义的一部分 UINavigationController 类。

但是接口不需要 ShouldPopItem 方法得以实施。所有界面都添加了相应的 Protocol 属性为类,因此可以用作 UINavigationBarDelegate

所以另外你需要添加 ShouldPopItem 对班级的声明如下:

[Export ("navigationBar:shouldPopItem:")]
public bool ShouldPopItem (UINavigationBar navigationBar, UINavigationItem item)
{
}

3
2017-10-30 23:26



或者从UINavigationBarDelegate(非接口)版本继承并覆盖它。当然,只有当类尚未从另一个类继承时,这才有可能。 - Falgantil
这就是方法! - John
这正是我所寻找的,因为在我的情况下,NavigationController是“null”,因此我无法设置Delegate。所以这是我的解决方案。谢谢。 - M. Hartner


我已将此解决方案与本机Obj-C解决方案合并。这是我目前正在处理iOS中取消BACK按钮的方式

似乎可以通过这种方式处理NavigationBar的shouldPopItem方法:

  1. 子类UINavigationController
  2. 使用IUINavigationBarDelegate标记自定义UINavigationController
  3. 使用“导出”属性添加此方法

    [导出(“navigationBar:shouldPopItem:”)] public bool ShouldPopItem(UINavigationBar navigationBar,UINavigationItem item) { }

现在你可以在ShoulPopItem方法中处理弹出。这方面的一个例子是创建这样的接口

public interface INavigationBackButton
{
    // This method should return TRUE to cancel the "back operation" or "FALSE" to allow normal back
    bool BackButtonPressed();
}

然后使用此界面标记需要处理后退按钮的UIViewController。实现这样的东西

public bool BackButtonPressed()
{
    bool needToCancel = // Put your logic here. Remember to return true to CANCEL the back operation (like in Android)
    return needToCancel;
}

然后在你的ShouldPopItem实现中有这样的东西 坦克: https://github.com/onegray/UIViewController-BackButtonHandler/blob/master/UIViewController%2BBackButtonHandler.m

        [Export("navigationBar:shouldPopItem:")]
        public bool ShouldPopItem(UINavigationBar navigationBar, UINavigationItem item)
        {
            if (this.ViewControllers.Length < this.NavigationBar.Items.Length)
                return true;

            bool shouldPop = true;
            UIViewController controller = this.TopViewController;
            if (controller is INavigationBackButton)
                shouldPop = !((INavigationBackButton)controller).BackButtonPressed();

            if (shouldPop)
            {
                //MonoTouch.CoreFoundation.DispatchQueue.DispatchAsync
                CoreFoundation.DispatchQueue.MainQueue.DispatchAsync(
                    () =>
                    {
                        PopViewController(true);
                    });
            }
            else
            {
                // Workaround for iOS7.1. Thanks to @boliva - http://stackoverflow.com/posts/comments/34452906
                foreach (UIView subview in this.NavigationBar.Subviews)
                {
                    if(subview.Alpha < 1f)
                        UIView.Animate(.25f, () => subview.Alpha = 1);
                }

            }

            return false;                          
        }

0
2017-08-13 14:10