08、导航栏和状态栏配置

1、navigationBar

在这里插入图片描述
它的作用就是决定导航栏的外观,比如:barStyle

1.1 barStyle

导航栏系统默认状态:背景:白色半透明、标题:黑色
在这里插入图片描述

  self.navigationController?.navigationBar.barStyle = .black //灰底,白字
        self.navigationController?.navigationBar.barStyle = .default //白底,黑字(默认)

1.2 tintColor

tintColor导航栏元素项的颜色(上图topItem,backItem,rightBarButtonItem)

self.navigationController?.navigationBar.tintColor = .white

1.3 barTintColor

barTintColor导航栏背景色,默认会有透明效果。

 self.navigationController?.navigationBar.barTintColor = .orange

1.4 isTranslucent

isTranslucent默认导航栏半透明,设置false为不透明,显示颜色就是设定的颜色
在设置为false之后控制器的view自动向下偏移64(导航栏高度的)

self.navigationController?.navigationBar.isTranslucent = false

1.5 setBackgroundImage

设置导航栏背景图片

self.navigationController?.navigationBar.setBackgroundImage(UIImage(named: ""), for: .default)

第一个参数backgroundImage是提供的图片对象,如果图像为空UIImage(named:"")也能让背景透明,但是push之后的控制器导航栏会卡一下。
第二个参数是一个枚举,UIBarMetricsDefault,UIBarMetricsCompact, UIBarMetricsDefaultPrompt = 101, UIBarMetricsCompactPrompt,是在图像不够显示的时候(一般是横竖屏切换的时候)是否横向或纵向平铺

1.6 shadowImage

shadowImage阴影图片

要先设置backgroundImage才会有效果,默认是黑色的背景色,导航栏下面的黑线就是这张图片,给个空图片UIImage(named:"")就能去掉黑色(黑线)。

1.7 prefersLargeTitles

prefersLargeTitles 大标题,默认是NO,设置为YES标题会变大。

1.8 titleTextAttributes

titleTextAttributes导航栏标题的title的富文本属性,但是要设置导航栏标题内容不能通过UINavigationBar,要通过navigationItem

 self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white,NSAttributedString.Key.font : UIFont.systemFont(ofSize: 20)]

1.9 backIndicatorImage

backIndicatorImage 返回按钮的图片

 UINavigationBar.appearance().backIndicatorImage = UIImage(named: "back")

backIndicatorTransitionMaskImage设置了以后再点击之后(或者返回时才能看到效果)

 UINavigationBar.appearance().backIndicatorTransitionMaskImage = UIImage(named: "back")

1.10 backgroundColor

注意上层有毛玻璃遮挡

self.navigationController?.navigationBar.backgroundColor = .orange

请添加图片描述

2、navigationItem

不同于navigationBar,navigationItem是UIViewController的属性,

例如
self.navigationController.navigationItem.title=@"无效";
self.navigationItem.title=@"有效";

2.1 title

当前控制器的标题

2.2 titleView

自定义的UIView可以替换title

 self.navigationItem.titleView = createTitleView()

2.3 backBarButtonItem、leftBarButtonItem、leftBarButtonItems

如果当前视图自定义了leftBarButtonItem这个按钮,就显示这个自定义的按钮,如果没有自定义,但是上一个视图自定义了backBarButtonItem就显示上一个视图的backBarButtonItem,如果都没有就显示系统默认的back

leftBarButtonItems显示为一组按钮,如果要在当前界面自定义就自定义leftBarButtonItem,自定义backBarButtonItem无效。 被push之后就是上一个视图对应的backBarButtonItem.

2.4 rightBarButtonItem、rightBarButtonItems、rightBarButtonItem

rightBarButtonItem,rightBarButtonItems,rightBarButtonItem默认没有,需要自定义按钮及点击事件;

rightBarButtonItems和leftBarButtonItems一样都是一组按钮。

2.5 hidesBackButton

hidesBackButton隐藏返回按钮,注意隐藏之后(如果没提供其他方式返回)就不能返回到上一个视图,往右滑动屏幕也不会返回。

self.navigationItem.hidesBackButton = true

2.6 leftItemsSupplementBackButton

leftItemsSupplementBackButton,如果设置为true会在你自定义了leftBarButtonItem之后也会显示系统的返回按钮。默认是false

3 解决自定义导航栏返回按钮后侧滑不可用问题

iOS导航栏自带的返回按钮形式单一,所以大多情况下,我们都需要自定义导航栏返回按钮。但是此时我们却发现页面的侧滑返回功能不可用了。为了解决这个问题,我们需要在App中使用我们自定义的导航控制控制器。

class HMNavigationController: UINavigationController,UIGestureRecognizerDelegate{
    //第一步:设置自定义导航控制器使用UIGestureRecognizerDelegate
    override func viewDidLoad() {
        super.viewDidLoad()
        //第二步: 设置自定义导航栏控制器的侧滑手势代理
        self.interactivePopGestureRecognizer?.delegate = self
    }
    
    //第三步:实现代理方法
    func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        if self.children.count == 1 {
            //表示用户在根控制器界面,就不需要触发滑动手势
            return false
        }
        
        return true
    }

}

4、导航栏引起的布局问题

4.1 内容偏移属性:automaticallyAdjustsScrollViewInsets

automaticallyAdjustsScrollViewInsets是视图控制器的一个属性,默认为true,用于优化滑动类视图(继承于UIScrollView的视图)在视图控制里的显示:
iOS系统的导航栏UINavigationBar与标签栏UITabBar默认都是半透明模糊效果,在这种情况下系统会对视图控制器的UI布局进行优化:视图控制器里面第一个被添加进去的视图是滑动类视图,并且其Frame是整个屏幕大小时,系统会自动调整其contenInset,以保证滑动视图里的内容不被UINavigationBar与UITabBar遮挡。

但是对于普通的视图,此时我们仍然需要注意:非滑动视图的布局仍然要考虑导航栏和标签栏高度,注意不被遮挡,比如布局的时候加上导航栏高度,以免内容被导航栏遮挡。

其实,这种系统的优化也是可以控制关闭的,关闭优化之后,滑动视图就会和普通视图一样,如果还设置其布局的原点是(0,0),其内容就会被导航栏所覆盖,关键代码如下:

        //automaticallyAdjustsScrollViewInsets在11.0后失效,所以需要判断
        if #available(iOS 11.0, *){
            scrollView.contentInsetAdjustmentBehavior = .never
        }else{
            //automaticallyAdjustsScrollViewIn,关闭自动偏移的系统优化
            self.automaticallyAdjustsScrollViewInsets = NO;
            self.automaticallyAdjustsScrollViewInsets = false
        }

4.2 边缘延伸属性: edgesForExtendedLayout

edgesForExtendedLayout也是视图控制器的布局属性,默认值是UIRectEdge.all,即:当前视图控制器里各种UI控件会忽略导航栏和标签的存在,布局时若设置其原点设置为(0,0),视图会延伸显示到导航栏的下面被覆盖。

所以我们可以设置self.edgesForExtendedLayout=UIRectEdge.none,此时视图控制器里内容就会避开导航栏和标签栏了。

4.3 导航栏透明属性translucent

上述两种属性都是在解决导航栏半透明情况下的布局问题,但是如果我们的需求就是导航栏不透明,那么视图控制器里的控件就会默认从(0,导航栏高度)开始布局了。

5、状态栏配置

5.1 iOS 9之前

自 iOS 7 以来,iOS 采用了沉浸式状态栏设计,而且状态栏风格主要以黑白二色为主。
一般而言,深色背景导航栏会搭配白色状态栏,浅色背景的则会搭配深色状态栏。
iOS 9 以前我们一般先在info.plist文件中将Status bar style设置为Light Content
然后在项目info.plist文件中将View controller-based status bar appearance为NO
请添加图片描述
然后在适当的视图控制器中使用下面的代码设置状态栏风格:

UIApplication.shared.statusBarStyle = .lightContent //设置白色状态栏

 UIApplication.shared.statusBarStyle = .default //设置黑色状态栏

然而这样的代码结构扰乱了我们对状态栏控制的节奏。想象一下,如果项目中的状态栏状态变化非常频繁,你不得不在各种 ViewController下更新和恢复状态栏的 style 状态。事实上我们需要的仅仅只是每一个 ViewController 和对应的 StatusBarStyle 相关联。我们不需要全局修改,全局意味着混乱。

5.2 iOS 9.0及以后

5.2.1 情况一:Single view application

首先在info.plist文件中将Status bar style设置为default或者Light Content,然后通过下面的代码控制viewController的状态栏。

  //info.plist文件中将Status bar style设置为Light Content
    override var preferredStatusBarStyle: UIStatusBarStyle {
        return .lightContent
    }

5.2.2 View controller embed in a navigation controller

假如我的 View Controller 位于一个导航栈中,状态栏又变黑色了。

// These methods control the attributes of the status bar when this view controller is shown. They can be overridden in view controller subclasses to return the desired status bar attributes.
    
@available(iOS 7.0, *)
open var preferredStatusBarStyle: UIStatusBarStyle { get } // Defaults to UIStatusBarStyleDefault
public enum UIStatusBarStyle : Int {
    case `default` = 0 // Automatically chooses light or dark content based on the user interface style

    @available(iOS 7.0, *)
    case lightContent = 1 // Light content, for use on dark backgrounds

    @available(iOS 13.0, *)
    case darkContent = 3 // Dark content, for use on light backgrounds
}

大意是说当 view controller 显示的时候,这个属性可以用来控制状态栏的属性。你可以在 UIViewController 的子类中重载该计算属性返回你期望的状态栏属性。

而这种场景下在viewController中重载不起作用的原因是preferredStatusBarStyle()并未被调用。因为当前 view controller 位于一个容器类 controller 中。容器类 controller 包括UINavigationVontrollerUITabBarController等。这种情况下,状态栏的风格会交由容器管理,你只需要在容器 controller 中重载改属性即可。

class HMNavigationController: UINavigationController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }
    
    override var preferredStatusBarStyle: UIStatusBarStyle {
        return .lightContent
    }
}

5.2.3 情况三:不同页面的状态配置

我们可能遇到这样一个需求,在导航栈中的第一个 view controller 里的状态栏为白色,第二个 view controller 里的状态栏为黑色。上面这种在导航栏控制器中控制状态栏属性的做法显然不符合我们的要求。所幸 iOS SDK 为我们提供了另一个属性:

// Override to return a child view controller or nil. If non-nil, that view controller's status bar appearance attributes will be used. If nil, self is used. Whenever the return values from these methods change, -setNeedsUpdatedStatusBarAttributes should be called.
    
@available(iOS 7.0, *)
open var childViewControllerForStatusBarStyle: UIViewController? { get }

大意是,如果这个方法返回值为 non-nil,则将更改状态栏属性的控制权移交给你返回的那个控制器。如果返回值为 nil 或者不重载该方法,那么由自己负责控制状态栏的属性。每当状态栏的样式被更改时,你都应该该调用控制器的-setNeedsUpdatedStatusBarAttributes方法。最后一句是说,如果你要在当前页面不时的更改状态栏的 style,那么你需要先调用-setNeedsStatusBarAppearanceUpdate方法(它通知系统去重新获取当前UIViewController 的preferredStatusBarStyle)。

要实现我们的需求,可以在NavigationController中重载-childViewControllerForStatusBarStyle属性,既然它返回一个视图控制器的实例,那么我们只要将导航栈中的topViewController作为返回值(该方法会被调用多次,且每次设置状态栏 style push/pop view controller 该方法都会被调用),然后在需要设置状态栏 style 的控制器中像最开始一样重载preferredStatusBarStyle属性即可。

override var childViewControllerForStatusBarStyle: UIViewController? {
    return self.topViewController
}

5.2.4 情况四:Present Modal Controller

需要指出的是,如果一个 view controller 被 present 出来,通常来说这个 view controller 会忽略preferredStatusBarStyle属性,你需要在它被 present 之前设置这样一个属性:

vc.modalPresentationCapturesStatusBarAppearance = true
// This controls whether this view controller takes over control of the status bar's appearance when presented non-full screen on another view controller. Defaults to NO.
@available(iOS 7.0, *)
open var modalPresentationCapturesStatusBarAppearance: Bool

6、主题色顶级解决方案

6.1 常规主题色使用点

应用在发布前都会对其主题色进行设置,以统一应用的风格(可能有多套主题)。在主题色设置上有几个方面,如下:

1.TabBar部分,设置图片高亮、文本高度颜色
2.NavigationBar部分,设置导航栏颜色及字体颜色
3.应用标签等,设置字体的颜色
4.应用图片主题色

主题色的设置点,大体从上面四个方面着手,图片的主题色我们可通过图片更换的方式进行处理。而通过代码来处理的1-3条,有着不同的处理方法。大家常规处理方法如下:
步骤一:变化分离
1.利用Swift扩展语法扩展UIColor,将应用主题色在扩展中统一处理(适合单一主题色)
2.将主题色的配置写入文件中,由相应逻辑进行解析。此方法将主题色逻辑封装成主题色管理类(适合多套主题)

步骤二:离散使用上步封装的类
1.在任何使用主题色的地方,使用扩展中的UIColor方法来设置,一般包括背景色,文字颜色等。

这里给出UIColor的扩展

extension UIColor {
    
    //主题色
    class func applicationMainColor() -> UIColor {
        return UIColor(red: 238/255, green: 64/255, blue: 86/255, alpha:1)
    }
    
    //第二主题色
    class func applicationSecondColor() -> UIColor {
        return UIColor.lightGrayColor()
    }
    
    //警告颜色
    class func applicationWarningColor() -> UIColor {
        return UIColor(red: 0.1, green: 1, blue: 0, alpha: 1)
    }
    
    //链接颜色
    class func applicationLinkColor() -> UIColor {
        return UIColor(red: 59/255, green: 89/255, blue: 152/255, alpha:1)
    }
    
}

6.2 TabBar主题色设置

很多应用中,默认情况下都使用了TabBar控件,但是TabBar主题色等设置根据使用情况的不同,设置起来也不一样。代码创建比较灵活,更改主题色比较容易。而使用了Xib/Storyboard也是有办法做统一处理的,如下,迭代更改TabBar默认字体颜色

func configTabBar() {
       let items = self.tabBar.items
       for item in items as [UITabBarItem] {
           let dic = NSDictionary(object: UIColor.applicationMainColor(),
            forKey: 	NSForegroundColorAttributeName)
           item.setTitleTextAttributes(dic, 
            forState: UIControlState.Selected)
       }
   }

设置TabBar图片及文字默认选中颜色

self.tabBar.selectedImageTintColor = UIColor.applicationMainColor()

Tips注意事项
Changing this property’s value provides visual feedback in the user interface, including the running of any associated animations. The selected item displays the tab bar item’s selectedImage image, using the tab bar’s selectedImageTintColor value. To prevent system coloring of an item, provide images using the UIImageRenderingModeAlwaysOriginal rendering mode.
在一些情况,正常状态为白色图片时,真机测试时,白色图片会出现偏色(显示结果为灰色),这是因为系统默认着色导致的,在创建UITabBarItem时,可通过使用UIImageRenderingModeAlwaysOriginal避免。示例代码如下:

let imageNormal = UIImage(contentsOfFile: "imageNormal")?.
imageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal)
let imageSelected = UIImage(contentsOfFile: "imageSelected")
let tabBarItem = UITabBarItem(title: "title",
         image: imageNormal,
         selectedImage: imageSelected)

6.3 一劳永逸,利用Hook原理通设NavigationBar颜色

IOS应用中,NavigationBar十分常用,它的使用主要包括以下两个场景
1.代码直接构建
2.Xib/Storyboard构建
如果是纯代码构建的时候,比较简单,直接使用UIColor的扩展来设置颜色。实际项目中,有些界面是通过Xib/Storyboard来创建的,有些是代码写的,但这也难不到大家,使用继承。创建一个继承自UINavigationController的子类,通过这个子类来统一设置主题色。然后告诉项目中的所有人,强制使用UINavigationController子类,包括Xib/Storyboard等。问题是旧项目怎么办,这种强制要求可以工作,有没有一个更好的办法,让所有人正常使用UINavigationController,而在神不知鬼不觉的情况下,通设所有NavigationBar呢?
先上代码,再解释
1.创建一个UIViewController的扩展

extension UIViewController {
    func viewDidLoadForChangeTitleColor() {
        self.viewDidLoadForChangeTitleColor()
        if self.isKindOfClass(UINavigationController.classForCoder()) {
           self.changeNavigationBarTextColor(self as UINavigationController)
        }
    }
    
    func changeNavigationBarTextColor(navController: UINavigationController) {
        let nav = navController as UINavigationController
        let dic = NSDictionary(object: UIColor.applicationMainColor(),
         forKey:NSForegroundColorAttributeName)
        nav.navigationBar.titleTextAttributes = dic
        nav.navigationBar.barTintColor = UIColor.applicationSecondColor()
        nav.navigationBar.tintColor = UIColor.applicationMainColor()
        
    }
  
}

2.编写用于Hook的工具类

func swizzlingMethod(clzz: AnyClass, #oldSelector: Selector, #newSelector: Selector) {
    let oldMethod = class_getInstanceMethod(clzz, oldSelector)
    let newMethod = class_getInstanceMethod(clzz, newSelector)
    method_exchangeImplementations(oldMethod, newMethod)
}

3.在AppDelegate中调用

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
       swizzlingMethod(UIViewController.self, 
       oldSelector: "viewDidLoad", 
       newSelector: "viewDidLoadForChangeTitleColor")
//do others
       return true
   }

4.原理说明
在程序入口处,通过运行时机制,动态的替换UIViewController的周期方法viewDidLoad为我们指定的方法viewDidLoadForChangeTitleColor。在viewDidLoadChangeTitleColor中,需要做两件事:

调用原来的viewDidLoad方法
执行修改主题色相关代码

1.如何调用原来的viewDidLoad方法

在AppDelegate中,通过调用方法swizzlingMethod我们将viewDidLoad与viewDidLoadForChangeTitleColor方法体进行了替换,原理如下图:

请添加图片描述
请添加图片描述
从上面的图可以看出,当在viewDidLoadForChangeTitleColor中执行:

self.viewDidLoadForChangeTitleColor()

是不会造成循环调用,反而是调用了我们期望执行的viewDidLoad方法体。

6.3 Xib/Storyboard的处理

一些在Xib/Storyboard中设置的主题色,比如文本颜色,按钮的高亮颜色等,该如何处理呢,以UILabel为例,建立扩展

extension UILabel {
    var colorString: String {
        set(newValue) {
            switch newValue {
            case "main":
                self.textColor = UIColor.applicationMainColor()
            case "second":
                self.textColor = UIColor.applicationSecondColor()
            case "warning":
                self.textColor = UIColor.applicationWarningColor()
            default:
                self.textColor = UIColor.applicationSecondColor()
            }
        }
        get {
            return self.colorString
        }
    }
}

在Xib/Storyboard的查检器中进行编辑,如下图:
请添加图片描述

参考文章:
iOS 正确设置状态栏 Style
iOS 导航栏的navigationBar,navigationItem的详解
Swift主题色顶级解决方案一

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值