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