基礎(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的背景顏色
-(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)效果)
。
-(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.
- 如果當(dāng)前VC通過
self.navigationItem.titleView
指定了自定義的titleView,系統(tǒng)將會(huì)顯示指定的titleView,設(shè)置self.title
以及self.navigationItem.title
不會(huì)改變導(dǎo)航欄的標(biāo)題。- 如果當(dāng)前VC沒有指定titleView,系統(tǒng)則會(huì)根據(jù)當(dāng)前VC的title或者當(dāng)前VC的navigationItem.title的內(nèi)容創(chuàng)建一個(gè)UILabel并顯示。
- 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;
為我們完美的解決這樣的問題
七、隨筆
- 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;\
}\
}()