Custom backBarButtonItem

我想得到的效果:

當用戶點擊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中的<恢復正常顏色。

感謝大家閱讀完這篇文字:]

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容