iOS-導(dǎo)航欄看這里就夠了

基礎(chǔ)介紹

內(nèi)容 作用
UINavigationController 是一個(gè)容器類,對(duì)ViewController進(jìn)行棧管理,包含navigationBar。
UINavigationBar 即UINavigationController頂部的導(dǎo)航欄,主要負(fù)責(zé)外觀背景的展示,并對(duì)navigationItem進(jìn)行棧管理
UINavigationItem 是導(dǎo)航欄上顯示的具體的元素的一個(gè)抽象類,UINavigationController 通過Category的方法為ViewController添加了一個(gè)navigationItem,把UINavigationItem交由ViewController管理
/// UINavigationController包含了viewcontrollers、navigationbar、toolbar
UIKIT_EXTERN API_AVAILABLE(ios(2.0)) @interface UINavigationController : UIViewController
// 導(dǎo)航欄
@property(nonatomic,readonly) UINavigationBar *navigationBar; // The navigation bar managed by the controller. Pushing, popping or setting navigation items on a managed navigation bar is not supported.
// 棧里的視圖控制器數(shù)組
@property(nonatomic,copy) NSArray<__kindof UIViewController *> *viewControllers; // The current view controller stack.
// toolbar對(duì)象
@property(null_resettable,nonatomic,readonly) UIToolbar *toolbar API_AVAILABLE(ios(3.0)) API_UNAVAILABLE(tvos); // For use when presenting an action sheet.


/// 包含當(dāng)前控制器導(dǎo)航欄上用戶自定義視圖、和下級(jí)視圖導(dǎo)航欄控制器
@class UIView, UINavigationBar, UINavigationItem, UIToolbar;
@protocol UINavigationControllerDelegate;
@interface UIViewController (UINavigationControllerItem)
// 當(dāng)前控制器導(dǎo)航欄上用戶自定義視圖
@property(nonatomic,readonly,strong) UINavigationItem *navigationItem; // Created on-demand so that a view controller may customize its navigation appearance.
//  push時(shí),隱藏底部菜單欄
@property(nonatomic) BOOL hidesBottomBarWhenPushed API_UNAVAILABLE(tvos); // If YES, then when this view controller is pushed into a controller hierarchy with a bottom bar (like a tab bar), the bottom bar will slide out. Default is NO.
// 下級(jí)視圖的導(dǎo)航控制器
@property(nullable, nonatomic,readonly,strong) UINavigationController *navigationController; // If this view controller has been pushed onto a navigation controller, return it.


/// UINavigaitonBar就是導(dǎo)航欄 主要對(duì)UINavigationItem進(jìn)行棧管理 展示導(dǎo)航欄的外觀背景
@class UINavigationItem, UIBarButtonItem, UIImage, UIColor, UINavigationBarAppearance;
@protocol UINavigationBarDelegate;
UIKIT_EXTERN API_AVAILABLE(ios(2.0)) @interface UINavigationBar : UIView <NSCoding, UIBarPositioning> 
// 當(dāng)前push棧中最上層的item
@property(nullable, nonatomic,readonly,strong) UINavigationItem *topItem;
// 僅次于最上層的item,一般式被推向?qū)Ш綑谧髠?cè)的item
@property(nullable, nonatomic,readonly,strong) UINavigationItem *backItem;
// 獲取push棧中所有item的數(shù)組
@property(nullable,nonatomic,copy) NSArray<UINavigationItem *> *items;


/// UINavigationItem包含了title,titleView,prompt,leftBarButtonItem,rightBarButtonItem,backBarButonItem等當(dāng)前頁面上所有的信息
UIKIT_EXTERN API_AVAILABLE(ios(2.0)) @interface UINavigationItem : NSObject <NSCoding>
// 設(shè)置導(dǎo)航欄中間的內(nèi)容標(biāo)題
@property(nullable, nonatomic,copy)   NSString        *title;             // Title when topmost on the stack. default is nil
// 設(shè)置導(dǎo)航欄中間的內(nèi)容視圖    
@property(nullable, nonatomic,strong) UIView          *titleView;         // Custom view to use in lieu of a title. May be sized horizontally. Only used when item is topmost on the stack.
// 提示描述 (添加該描述以后NavigationBar的高度會(huì)增加30,由44變?yōu)?4)
@property(nullable,nonatomic,copy)   NSString *prompt API_UNAVAILABLE(tvos);     // Explanatory text to display above the navigation bar buttons.
// 返回操作鍵
@property(nullable,nonatomic,strong) UIBarButtonItem *backBarButtonItem API_UNAVAILABLE(tvos); // Bar button item to use for the back button in the child navigation item.
// 左邊??操作Item數(shù)組
@property(nullable,nonatomic,copy) NSArray<UIBarButtonItem *> *leftBarButtonItems API_AVAILABLE(ios(5.0));
// 右邊??操作Item數(shù)組
@property(nullable,nonatomic,copy) NSArray<UIBarButtonItem *> *rightBarButtonItems API_AVAILABLE(ios(5.0));
// 左邊??操作Item
@property(nullable, nonatomic,strong) UIBarButtonItem *leftBarButtonItem;
// 右邊??操作Item
@property(nullable, nonatomic,strong) UIBarButtonItem *rightBarButtonItem;


/// 一個(gè)可以放置在Bar之上的所有小控件類的抽象類,可以設(shè)置標(biāo)題,圖片等
UIKIT_EXTERN API_AVAILABLE(ios(2.0)) @interface UIBarItem : NSObject <NSCoding, UIAppearance>
@property(nullable, nonatomic,copy)             NSString    *title;        // default is nil
@property(nullable, nonatomic,strong)           UIImage     *image;        // default is nil


/// 繼承UIBarItem,增加了動(dòng)作以及目標(biāo)等button的屬性。相當(dāng)于放在UIToolBar或者UINavigationBar上的特殊的button。
UIKIT_EXTERN API_AVAILABLE(ios(2.0)) @interface UIBarButtonItem : UIBarItem <NSCoding>
@property(nullable, nonatomic)         SEL                  action;           // default is NULL
@property(nullable, nonatomic,weak)    id                   target;           // default is nil

通俗地說就是,UINavigationController是個(gè)容器,里面可以裝很多UIViewController。裝這么多UIViewController讓用戶怎么控制它們呢?總得有個(gè)工具吧,這個(gè)工具就是UINavigationBar。一個(gè)容器就這么一個(gè)bar,相當(dāng)于控制臺(tái)。但是管理那么多UIViewController,控制臺(tái)上得按鈕啊、標(biāo)題啊,都千篇一律是不是看起來太無聊了。為了解決這個(gè)問題,UINavigationController為每個(gè)UIViewController生成一個(gè)UINavigationItem,通過這個(gè)UINavigationItem可以改變控制臺(tái)“上面”的按鈕和標(biāo)題。如果你不自定義UINavigationItem,UINavigationController會(huì)使用默認(rèn)的;

開發(fā)中常遇到的問題

一、UINavigationBar的背景顏色

UINavigationBar背景顏色.png
-(void)changeNavigationBarBackgroundColor {
    //背景色
    self.navigationBar.barTintColor = [UIColor orangeColor];

    //title字體
    [self.navigationBar setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor whiteColor],NSFontAttributeName:[UIFont systemFontOfSize:17]}];

    //修改UIBarButtonItem 圖片 title顏色
    self.navigationBar.tintColor = [UIColor redColor];

    //是否半透明 當(dāng)為YES時(shí) 設(shè)置的導(dǎo)航欄背景顏色會(huì)和實(shí)際rgb值有誤差
    self.navigationBar.translucent = NO;

    //如果想要半透明效果 顏色沒有色差 可以通過設(shè)置背景圖片的方法 背景圖片會(huì)覆蓋barTintColor
    //- (void)setBackgroundImage:(nullable UIImage *)backgroundImage forBarMetrics:(UIBarMetrics)barMetrics
}

二、UINavigationBar底部的shadowImage

??默認(rèn)是nil。當(dāng)非nil時(shí),顯示一個(gè)自定義的陰影圖像代替默認(rèn)的陰影圖像。要顯示一個(gè)自定義的陰影,自定義的背景圖像也必須使用-setBackgroundImage:forBarMetrics:(設(shè)置shadowImage必須先setBackgroundImage,否則無法實(shí)現(xiàn)效果)。

shadowImage調(diào)整前.png

shadowImage調(diào)整后.png

-(void)changeNavigationBarBottonLine {
    //設(shè)置底部line顏色時(shí)需要同時(shí)設(shè)置backgroundImage即導(dǎo)航欄的背景圖片 否則沒有效果
    [self.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
    [self.navigationBar setShadowImage:[self imageWithColor:[ UIColor redColor]]];
    //此處設(shè)置透明顏色的image,底部line即可隱藏,但此種方法隱藏,沒有辦法再顯示 下面方法通過找到該view 控制其hidden屬性
    //[self reducibilityHiddenNavogationBarLine];
}

-(UIImage*)imageWithColor:(UIColor*)color {
    CGRect rect=CGRectMake(0.0f, 0.0f, 1.0f, 1.0f);
    UIGraphicsBeginImageContext(rect.size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, [color CGColor]);
    CGContextFillRect(context, rect);
    UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return theImage;
}

??找到該imageView

-(UIImageView *)findLineImageViewUnder:(UIView *)view {
    if ([view isKindOfClass:[UIImageView class]] && view.bounds.size.height <= 1.0) {
        return (UIImageView *)view;
    }
    for (UIView * subView in view.subviews) {
        UIImageView * imageView = [self findLineImageViewUnder:subView];
        if (imageView) {
            return imageView;
        }
    }
    return nil;
}

三、自定義導(dǎo)航欄的返回按鈕

  • 自定義文字+圖片


    自定義返回按鈕.png
-(void)createCustomBackBarItem {
    
    //修改圖片文字顏色
    self.navigationController.navigationBar.tintColor = [UIColor orangeColor];
    
    //替換圖片
    [self.navigationController.navigationBar setBackIndicatorImage:[UIImage imageNamed:@"返回"]];
    [self.navigationController.navigationBar setBackIndicatorTransitionMaskImage:[UIImage imageNamed:@"返回"]];
    
    //設(shè)置文字
    UIBarButtonItem * backBarItem = [[UIBarButtonItem alloc]initWithTitle:@"返回" style:UIBarButtonItemStylePlain target:nil action:nil];
    self.navigationItem.backBarButtonItem = backBarItem;
}

??對(duì)backBarButtonItem的修改是在當(dāng)前viewController前一個(gè)頁面完成的,在當(dāng)前頁面修改針對(duì)下一個(gè)viewController的navigationItem生效

  • 2.不顯示文字
[[UIBarButtonItem appearance] setBackButtonTitlePositionAdjustment:UIOffsetMake(0, -100) forBarMetrics:UIBarMetricsDefault];

??設(shè)置Title在Y方向上的偏移量,使其移除屏幕,該方法在第一次進(jìn)入時(shí)會(huì)有個(gè)文字移動(dòng)的動(dòng)畫效果,效果不好,不推薦使用

  • 3.使用leftBarButtonItem替代backBarButtonItem
-(void)setLeftBarItemBack
{
    UIBarButtonItem *leftBarBtnItem = [[UIBarButtonItem alloc]initWithImage:[UIImage imageNamed:@"back"] style:UIBarButtonItemStylePlain target:self action:@selector(clickLeftBarBtnItem:)];
    [self.navigationItem setLeftBarButtonItem:leftBarBtnItem animated:YES];
    self.navigationItem.leftBarButtonItem.tintColor = NavigationLeftBackColor;
}

/**
 *  導(dǎo)航條leftBarBtn事件
 */
- (void)clickLeftBarBtnItem:(UIBarButtonItem *)sender {
    [self.navigationController popViewControllerAnimated:YES];
}

??使用這種方法,不能使用邊緣滑動(dòng)返回手勢(shì),且不能同時(shí)設(shè)置圖片和標(biāo)題

  • 4.使用CustomView的方法

1.如果B視圖有一個(gè)自定義的左側(cè)按鈕(leftBarButtonItem),則會(huì)顯示這個(gè)自定義按鈕;
2.如果B沒有自定義按鈕,但是A視圖的backBarButtonItem屬性有自定義項(xiàng),則顯示這個(gè)自定義項(xiàng);
3.如果前2條都沒有,則默認(rèn)顯示一個(gè)后退按鈕,后退按鈕的標(biāo)題是A視圖的標(biāo)題;

??此方法不適于backBarButtonItem,只能用于leftBarButtonItem

  • 小結(jié):
    ??使用3、4方法,邊緣返回會(huì)失效,此時(shí)加上這句代碼依然可以實(shí)現(xiàn)邊緣滑動(dòng)返回
self.navigationController.interactivePopGestureRecognizer.delegate = self;

四、navigationBar偶爾顯示上一個(gè)頁面的navigationBar

??一般情況下都是正常的。但是在偶然情況下,會(huì)出現(xiàn)在進(jìn)入新界面后,新界面的navigationBar會(huì)突然消失,出現(xiàn)的還是上一個(gè)界面的 navigationBar。從此以后,navigationBar 全亂了, kill 掉重新進(jìn),恢復(fù)正常。
原因:
??一般我們會(huì)打點(diǎn)調(diào)用navigationBarHidden的屬性來設(shè)置導(dǎo)航欄是否隱藏,這種方法是不帶動(dòng)畫效果的。這樣偶爾就會(huì)導(dǎo)致錯(cuò)亂,這應(yīng)該是一個(gè)系統(tǒng)的bug,所以應(yīng)盡量使用

五、易混淆知識(shí)點(diǎn)

1.self.title、self.navigationItem.title、self.tabBarItem.title之間的關(guān)系

self.navigationItem.title = @"my title"; //sets navigation bar title.
self.tabBarItem.title = @"my title"; //sets tab bar title.
self.title = @"my title"; //sets both of these.
  1. 如果當(dāng)前VC通過 self.navigationItem.titleView指定了自定義的titleView,系統(tǒng)將會(huì)顯示指定的titleView,設(shè)置self.title以及self.navigationItem.title不會(huì)改變導(dǎo)航欄的標(biāo)題。
  2. 如果當(dāng)前VC沒有指定titleView,系統(tǒng)則會(huì)根據(jù)當(dāng)前VC的title或者當(dāng)前VC的navigationItem.title的內(nèi)容創(chuàng)建一個(gè)UILabel并顯示。
  3. self.title會(huì)重寫navigationItem和tabBarItem的title。

2.self.navigationItem,self.navigationController.navigationItem的關(guān)系

??navigationItem是UIViewController的一個(gè)屬性,navigationController繼承UIViewController,自然會(huì)繼承viewControoler的navigationItem屬性。此處self.navigationController.navigationItem是應(yīng)該被忽視的。navigationItem直接由viewController管理。

3.UIBarMetrics和UIBarPosition

typedef NS_ENUM(NSInteger, UIBarMetrics) {
    UIBarMetricsDefault, //橫屏
    UIBarMetricsCompact,//豎屏
    UIBarMetricsDefaultPrompt = 101, //橫屏且設(shè)置了prompt屬性  Applicable only in bars with the prompt property, such as UINavigationBar and UISearchBar
    UIBarMetricsCompactPrompt, //豎屏且設(shè)置了prompt屬性
};

typedef NS_ENUM(NSInteger, UIBarPosition) {
    UIBarPositionAny = 0, //Bar在任何位置
    UIBarPositionBottom = 1, //Bar在底部
    UIBarPositionTop = 2, //Bar在頂部
    UIBarPositionTopAttached = 3, //Bar在頂部,且他的背景擴(kuò)展到statusBar的區(qū)域
} NS_ENUM_AVAILABLE_IOS(7_0);

六、側(cè)滑導(dǎo)致的Navigationbar異常顯示和隱藏的問題

??self.navigationController.navigationBarHidden或者self.navigationController.navigationBar.hidden來隱藏navigatiuonbar,這樣直接更改屬性的方式是不帶動(dòng)畫的,而且滑動(dòng)時(shí)的轉(zhuǎn)場(chǎng)動(dòng)畫也不為我們處理好,才導(dǎo)致了問題的出現(xiàn),而- (void)setNavigationBarHidden:(BOOL)hidden animated:(BOOL)animated;為我們完美的解決這樣的問題

七、隨筆

  1. iOS15后適配導(dǎo)航欄高度宏寫法
#define kStatusBarHeight \
^(){\
if (@available(iOS 15.0, *)) {\
    CGFloat height = 0.0f;\
    NSSet *scenes = [[UIApplication sharedApplication] connectedScenes];\
    for (UIScene *scene in scenes) {\
        if ([scene isKindOfClass:[UIWindowScene class]]) { \
            UIWindowScene *windowScene = (UIWindowScene*)scene;\
            height = windowScene.statusBarManager.statusBarFrame.size.height;\
        }\
    }\
    return height;\
} else if (@available(iOS 13.0, *)) {\
    UIStatusBarManager *statusBarManager = [UIApplication sharedApplication].windows.firstObject.windowScene.statusBarManager;\
    return statusBarManager.statusBarFrame.size.height;\
} else {\
    return [UIApplication sharedApplication].statusBarFrame.size.height;\
}\
}()
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。