iOS-导航栏看这里就够了

基础介绍

内容 作用
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.
// 栈里的视图控制器数组
@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  
// 当前push栈中最上层的item
@property(nullable, nonatomic,readonly,strong) UINavigationItem *topItem;
// 仅次于最上层的item,一般式被推向导航栏左侧的item
@property(nullable, nonatomic,readonly,strong) UINavigationItem *backItem;
// 获取push栈中所有item的数组
@property(nullable,nonatomic,copy) NSArray *items;


/// UINavigationItem包含了title,titleView,prompt,leftBarButtonItem,rightBarButtonItem,backBarButonItem等当前页面上所有的信息
UIKIT_EXTERN API_AVAILABLE(ios(2.0)) @interface UINavigationItem : NSObject 
// 设置导航栏中间的内容标题
@property(nullable, nonatomic,copy)   NSString        *title;             // Title when topmost on the stack. default is nil
// 设置导航栏中间的内容视图    
@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变为74)
@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数组
@property(nullable,nonatomic,copy) NSArray *leftBarButtonItems API_AVAILABLE(ios(5.0));
// 右边👉操作Item数组
@property(nullable,nonatomic,copy) NSArray *rightBarButtonItems API_AVAILABLE(ios(5.0));
// 左边👈操作Item
@property(nullable, nonatomic,strong) UIBarButtonItem *leftBarButtonItem;
// 右边👉操作Item
@property(nullable, nonatomic,strong) UIBarButtonItem *rightBarButtonItem;


/// 一个可以放置在Bar之上的所有小控件类的抽象类,可以设置标题,图片等
UIKIT_EXTERN API_AVAILABLE(ios(2.0)) @interface UIBarItem : NSObject 
@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 
@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会使用默认的;

开发中常遇到的问题

一、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时 设置的导航栏背景颜色会和实际rgb值有误差
    self.navigationBar.translucent = NO;

    //如果想要半透明效果 颜色没有色差 可以通过设置背景图片的方法 背景图片会覆盖barTintColor
    //- (void)setBackgroundImage:(nullable UIImage *)backgroundImage forBarMetrics:(UIBarMetrics)barMetrics
}

二、UINavigationBar底部的shadowImage

  默认是nil。当非nil时,显示一个自定义的阴影图像代替默认的阴影图像。要显示一个自定义的阴影,自定义的背景图像也必须使用-setBackgroundImage:forBarMetrics:(设置shadowImage必须先setBackgroundImage,否则无法实现效果)

shadowImage调整前.png

shadowImage调整后.png
-(void)changeNavigationBarBottonLine {
    //设置底部line颜色时需要同时设置backgroundImage即导航栏的背景图片 否则没有效果
    [self.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
    [self.navigationBar setShadowImage:[self imageWithColor:[ UIColor redColor]]];
    //此处设置透明颜色的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:@"返回"]];
    
    //设置文字
    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];

  设置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];
}

  使用这种方法,不能使用边缘滑动返回手势,且不能同时设置图片和标题

  • 4.使用CustomView的方法

1.如果B视图有一个自定义的左侧按钮(leftBarButtonItem),则会显示这个自定义按钮;
2.如果B没有自定义按钮,但是A视图的backBarButtonItem属性有自定义项,则显示这个自定义项;
3.如果前2条都没有,则默认显示一个后退按钮,后退按钮的标题是A视图的标题;

  此方法不适于backBarButtonItem,只能用于leftBarButtonItem

  • 小结:
      使用3、4方法,边缘返回会失效,此时加上这句代码依然可以实现边缘滑动返回
self.navigationController.interactivePopGestureRecognizer.delegate = self;

四、navigationBar偶尔显示上一个页面的navigationBar

  一般情况下都是正常的。但是在偶然情况下,会出现在进入新界面后,新界面的navigationBar会突然消失,出现的还是上一个界面的 navigationBar。从此以后,navigationBar 全乱了, kill 掉重新进,恢复正常。
原因:
  一般我们会打点调用navigationBarHidden的属性来设置导航栏是否隐藏,这种方法是不带动画效果的。这样偶尔就会导致错乱,这应该是一个系统的bug,所以应尽量使用

五、易混淆知识点

1.self.title、self.navigationItem.title、self.tabBarItem.title之间的关系

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,系统将会显示指定的titleView,设置self.title以及self.navigationItem.title不会改变导航栏的标题。
  2. 如果当前VC没有指定titleView,系统则会根据当前VC的title或者当前VC的navigationItem.title的内容创建一个UILabel并显示。
  3. self.title会重写navigationItem和tabBarItem的title。

2.self.navigationItem,self.navigationController.navigationItem的关系

  navigationItem是UIViewController的一个属性,navigationController继承UIViewController,自然会继承viewControoler的navigationItem属性。此处self.navigationController.navigationItem是应该被忽视的。navigationItem直接由viewController管理。

3.UIBarMetrics和UIBarPosition

typedef NS_ENUM(NSInteger, UIBarMetrics) {
    UIBarMetricsDefault, //横屏
    UIBarMetricsCompact,//竖屏
    UIBarMetricsDefaultPrompt = 101, //横屏且设置了prompt属性  Applicable only in bars with the prompt property, such as UINavigationBar and UISearchBar
    UIBarMetricsCompactPrompt, //竖屏且设置了prompt属性
};

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

六、侧滑导致的Navigationbar异常显示和隐藏的问题

  self.navigationController.navigationBarHidden或者self.navigationController.navigationBar.hidden来隐藏navigatiuonbar,这样直接更改属性的方式是不带动画的,而且滑动时的转场动画也不为我们处理好,才导致了问题的出现,而- (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;/
}/
}()

版权声明:
作者:congcong
链接:https://www.techfm.club/p/46382.html
来源:TechFM
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
< <上一篇
下一篇>>