Custom backBarButtonItem

作者: 栾小布 | 来源:发表于2015-03-30 22:19 被阅读5294次

    我想得到的效果:

    当用户点击backBarButtonItem的时候,在pop前,我想处理一些逻辑来判断是否pop。
    并且我想要保留backBarButtonItem的'<'。

    为什么得不到这种效果

    1. backBarButtonItem绑定事件会被忽略,UINavigatonController自动为其绑定事件,只做POP动作。There is nothing we can do.
    2. 使用leftBarButtonItem可以绑定事件,但是'<'就不存在了,当然可以定制View来达到效果,但是如果需要兼容iOS6则需要更多的工作(iOS6的backBarButtonItem试样与iOS7不同),而且谁也不会知道在iOS9中,会出现什么新设计。(在iOS9快释出的时候还适配iOS6?其实只是强行找个写这篇文字的理由 :])

    我试过的方法:

    • Add target on backBarButtonItem. Failed.
    • Set leftBarButtonItem with charactor '<'. (All kind of '<' I could find in Characters Viewer) 用一个字符'<'来显示backBarButtonItem的'<'效果,比如'↺'和'√'。这样就不用自己绘制或贴图了。
    • Set leftItemsSupplementBackButton to YES. 该属性使backBarButtonItemleftBarButtonItem同时显示,leftBarButtonItembackBarButtonItem的右边,于是我就想让backBarButtonItem只显示一个'<',让leftBarButtonItem显示文字,并disablebackBarButtonItem不就可以了?但是剧本不是我写的。set "" to backBarButtonItem and set "Back" to leftBarButtonItem, but there is a gap between '<' and 'Back'.

    最终解决方案:

    1. Subclass UINavigatonController, override - (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
    2. Category UINavigatonController, expose super's - (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item

    解释如下:

    UINavigationBarDelegate定义了一些方法来控制POP和PUSH行为:

    navigationBar:shouldPushItem:
    navigationBar:didPushItem:
    navigationBar:shouldPopItem:
    navigationBar:didPopItem:
    

    这里我们利用了navigationBar:shouldPopItem:,如果该方法返回NO,则不POP。
    因此我们创建UINavigatonController的子类,来定制navigationBar:shouldPopItem:的逻辑。
    这里有个小地方要注意,就是我们不需要设置delegate,UINavigatonController会自动将包含的UINavigationBar的delegate指向自己。

    子类的navigationBar:shouldPopItem:我们希望在处理完定制的逻辑后调用父类的该方法完成POP,但是父类UINavigatonController并没有把navigationBar:shouldPopItem:作为接口暴露出来,因此我们需要一点Category的小技巧来为父类创建navigationBar:shouldPopItem:的接口。

    代码如下:

    WFNavigationController.h文件

    @protocol WFNavigationControllerDelegate <NSObject>
    @optional
    - (BOOL)controllerWillPopHandler;
    @end
    
    @interface  WFNavigationController : UINavigationController
    @end
    

    WFNavigationController.m文件

    @interface UINavigationController(UINavigationControllerNeedshouldPopItem)
    - (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item;
    @end
    
    @implementation UINavigationController(UINavigationControllerNeedshouldPopItem)
    @end
    // 以上几行就是使用Category使UINavigationController将其实现的navigationBar:shouldPopItem:暴露出来,
    // 让我们定制的子类可以调用
    
    @interface WFNavigationController() <UINavigationBarDelegate>
    @end
    
    @implementation WFNavigationController
    
    - (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
    {
        UIViewController *vc = self.topViewController;
        if ([vc respondsToSelector:@selector(controllerWillPopHandler)])
        {
            if ([vc performSelector:@selector(controllerWillPopHandler)])
            {
                return [super navigationBar:navigationBar shouldPopItem:item];
            }
            else
            {
                return NO;
            }
        }
        else
        {
            return [super navigationBar:navigationBar shouldPopItem:item];
        }
    }
    
    @end
    

    navigation controller栈顶的vc,遵循WFNavigationControllerDelegate协议,实现- (BOOL)controllerWillPopHandler方法即可。

    推荐使用block而不是delegate

    我在这里使用了delegate而不是block,其实是在偷懒,block是更好的方式,可以让你的代码更易阅读。
    因为相关逻辑都放在一起,而不是像使用委托这样到处散落。
    在该场景下,还有个推荐使用block的更重要的原因:NavigationController的push和pop过程中,topViewController可能不是你预期的那个VC。block可以方便的加载,卸载。
    由于topViewController的不稳定性,所以这篇文字介绍的方法不是最好的。以后有时间再寻找下别的方式。

    考虑以下场景:

    A -> B -> C
    A创建并 push B,B创建并 push C。
    

    B需要在pop前进行逻辑判断,所以B遵循协议。
    这种场景下,有两个地方会触发B实现的委托:

    1. B点击backBarButtonItem返回A,这是我们期望的。
    2. C手动[self.navigationController popViewControllerAnimated:YES];比如点击rightBarButtonItem返回B。这里也会触发!

    也就是说backBarButtonItem时获取到的topViewController是pop前VC,而[self.navigationController popViewControllerAnimated:YES];获取到的是pop后VC。这种两种路径的设计也蛮“有意思”。
    visibleViewControllerviewControllers.lastObject也一样。

    这里我没有深入下去,而是在view life circle中加载,卸载popBlock。或者使用delegate外加一个Bool变量在view life circle中启用,禁用委托。

    以上解决方案不是很理想,有时间我会再研究整理,看有没有更好的方法。

    如果没看懂的话,请留言,我可以写个demo。

    以下可以选择性适当忽略:

    禁止pop后,< Back中的<会置灰,文字Back却不会(可能又是Apple Inc.的小Bug)。
    解决方法很简单:在vc中调用以下两句代码,两句,嗯。

    [self.navigationController setNavigationBarHidden:YES animated:YES];
    [self.navigationController setNavigationBarHidden:NO animated:YES];
    

    比如,用户点击backBarButtonItem时,我提示用户是否继续离开,如果用户选择OKPOP离开,如果用户选择NO则留在本页并执行上面两句,使<Back中的<恢复正常颜色。

    感谢大家阅读完这篇文字:]

    相关文章

      网友评论

      • 黑白灰的绿i:为什么我按照你说的做,没有效果
      • madaoCN: UIViewController *vc = [self valueForKeyPath:@"disappearingViewController"];
        这样应该可以获得到当前VC
      • 起个名字想破头:关于你说的topViewController问题,我是这么做的:
        1.在navigationcontroller的初始化方法中将delegate指向自己self.delegate = self;
        2.实现UINavigationControllerDelegate中的navigationController:didShowViewController:animated:方法,将这个方法中的ViewController作为CurrentViewController
        3.在navigationBar:shouldPopItem:方法中使用CurrentViewController作为上面的topViewController进行回调
      • xuq:你的箭头在取消后会不会变浅?求解决方法
      • 22cc4a7d57d4:楼主,我的初衷和你一样,想截获这个方法,但是我还没有实现,你有没有写好的demo给我看看啊
      • 57f21ae7bdab:格式整理下,看着难受。。
      • 李建邦Carl:这个分类声明好像是多余的
        并且官方文档强调过 运行时分类中同名方法和原有类中的方法谁被调用是不确定的
      • 李建邦Carl:声明好乱 麻烦整理一下
      • _赖笔小新:get 到了,很有用的技巧!

      本文标题:Custom backBarButtonItem

      本文链接:https://www.haomeiwen.com/subject/fmqoxttx.html