iOS-導航欄看這里就夠了

基礎(chǔ)介紹

內(nèi)容 作用
UINavigationController 是一個容器類,對ViewController進行棧管理,包含navigationBar。
UINavigationBar 即UINavigationController頂部的導航欄,主要負責外觀背景的展示,并對navigationItem進行棧管理
UINavigationItem 是導航欄上顯示的具體的元素的一個抽象類,UINavigationController 通過Category的方法為ViewController添加了一個navigationItem,把UINavigationItem交由ViewController管理
/// UINavigationController包含了viewcontrollers、navigationbar、toolbar
UIKIT_EXTERN API_AVAILABLE(ios(2.0)) @interface UINavigationController : UIViewController
// 導航欄
@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對象
@property(null_resettable,nonatomic,readonly) UIToolbar *toolbar API_AVAILABLE(ios(3.0)) API_UNAVAILABLE(tvos); // For use when presenting an action sheet.


/// 包含當前控制器導航欄上用戶自定義視圖、和下級視圖導航欄控制器
@class UIView, UINavigationBar, UINavigationItem, UIToolbar;
@protocol UINavigationControllerDelegate;
@interface UIViewController (UINavigationControllerItem)
// 當前控制器導航欄上用戶自定義視圖
@property(nonatomic,readonly,strong) UINavigationItem *navigationItem; // Created on-demand so that a view controller may customize its navigation appearance.
//  push時,隱藏底部菜單欄
@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.
// 下級視圖的導航控制器
@property(nullable, nonatomic,readonly,strong) UINavigationController *navigationController; // If this view controller has been pushed onto a navigation controller, return it.


/// UINavigaitonBar就是導航欄 主要對UINavigationItem進行棧管理 展示導航欄的外觀背景
@class UINavigationItem, UIBarButtonItem, UIImage, UIColor, UINavigationBarAppearance;
@protocol UINavigationBarDelegate;
UIKIT_EXTERN API_AVAILABLE(ios(2.0)) @interface UINavigationBar : UIView <NSCoding, UIBarPositioning> 
// 當前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等當前頁面上所有的信息
UIKIT_EXTERN API_AVAILABLE(ios(2.0)) @interface UINavigationItem : NSObject <NSCoding>
// 設(shè)置導航欄中間的內(nèi)容標題
@property(nullable, nonatomic,copy)   NSString        *title;             // Title when topmost on the stack. default is nil
// 設(shè)置導航欄中間的內(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的高度會增加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;


/// 一個可以放置在Bar之上的所有小控件類的抽象類,可以設(shè)置標題,圖片等
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,增加了動作以及目標等button的屬性。相當于放在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是個容器,里面可以裝很多UIViewController。裝這么多UIViewController讓用戶怎么控制它們呢?總得有個工具吧,這個工具就是UINavigationBar。一個容器就這么一個bar,相當于控制臺。但是管理那么多UIViewController,控制臺上得按鈕啊、標題啊,都千篇一律是不是看起來太無聊了。為了解決這個問題,UINavigationController為每個UIViewController生成一個UINavigationItem,通過這個UINavigationItem可以改變控制臺“上面”的按鈕和標題。如果你不自定義UINavigationItem,UINavigationController會使用默認的;

開發(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];

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

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

二、UINavigationBar底部的shadowImage

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

shadowImage調(diào)整前.png

shadowImage調(diào)整后.png

-(void)changeNavigationBarBottonLine {
    //設(shè)置底部line顏色時需要同時設(shè)置backgroundImage即導航欄的背景圖片 否則沒有效果
    [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;
}

三、自定義導航欄的返回按鈕

  • 自定義文字+圖片


    自定義返回按鈕.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;
}

??對backBarButtonItem的修改是在當前viewController前一個頁面完成的,在當前頁面修改針對下一個viewController的navigationItem生效

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

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

  • 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;
}

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

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

  • 4.使用CustomView的方法

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

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

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

四、navigationBar偶爾顯示上一個頁面的navigationBar

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

五、易混淆知識點

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. 如果當前VC通過 self.navigationItem.titleView指定了自定義的titleView,系統(tǒng)將會顯示指定的titleView,設(shè)置self.title以及self.navigationItem.title不會改變導航欄的標題。
  2. 如果當前VC沒有指定titleView,系統(tǒng)則會根據(jù)當前VC的title或者當前VC的navigationItem.title的內(nèi)容創(chuàng)建一個UILabel并顯示。
  3. self.title會重寫navigationItem和tabBarItem的title。

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

??navigationItem是UIViewController的一個屬性,navigationController繼承UIViewController,自然會繼承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在頂部,且他的背景擴展到statusBar的區(qū)域
} NS_ENUM_AVAILABLE_IOS(7_0);

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

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

七、隨筆

  1. iOS15后適配導航欄高度宏寫法
#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)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380