Swift YDRootNavigationController的实现原理

简介

YDRootNavigationController 是一个使用Swift语言和面向协议编程思想对原生的导航栏控制进行再封装的基础导航栏控制器库,它支持全局设置默认的导航栏样式同时支持视图控制器自定义导航栏样式,使用方法简单,可以按照UINavigationController的使用方法去使用,不添加额外的控制器层级。

YDRootNavigationController

做过iOS开发的人都知道, 原生的导航栏样式设置有很多局限,如果是整个App都是统一的导航栏样式还好, 要是存在两种或两种以上的样式就非常麻烦了,因为同一个导航栏控制器的视图控制器的导航栏是共用的,一旦改了导航栏的样式就会影响到其他视图控制器的导航栏样式,例如,有个App默认全局是显示导航栏,但是当前视图控制器需要隐藏导航栏,其他视图控制器有需要隐藏有的不需要,如果设置隐藏后,push到的控制器的不隐藏导航栏的,这时就出现bug了,于是你在viewDidDisappear方法里面设置显示导航栏后,这时控制器pop到前一个控制器也是隐藏导航栏的,这时候就又又出现bug了,尤其是团队开发的App,经常因为这个问题导致出现bug。
以前使用Objective-C开发时,我们可能会用到 RTRootNavigationController 这个库,它支持每个视图控制器拥有自己的导航栏,这样不同控制器的导航栏样式设置就互不影响,而且支持原生的返回手势开启与关闭,但是由于它是为每个视图控制器都要再包一层导航栏控制器和一层视图控制器,这样的弊端就是原来的视图控制器的层级结构发生变化,每个视图控制器都要包两层控制器,这就造成了冗余和资源浪费,原来的一些属性和用法就失效或者替换成其他属性,无法按照原生的UINavigationController的使用方法去使用导航栏控制器,例如 ,当前控制器的navigationController返回的就不是你所需要的那个外层的导航栏控制器,所以在学习Swift语言后,我就有一个想法:是不是可以自己用Swift语言封装一个在保留原生的UINavigationController的使用方法,尽量不用添加其他的层级,支持配置全局默认的导航栏样式的同时支持每个控制器可以自定义导航栏的Pod库。

RTRootNavigationController

后来,在使用Swift语言开发项目中,由于当时并没有比较成熟的关于导航栏设置的第三方库,所以项目中都是用最原始的方法,例如:导航栏的显示与隐藏,每个控制器直接在viewWillAppear里面直接设置,由于是一个团队在开发一款App,而且导航栏样式比较多,每个人又负责不同的页面,有的页面要显示有的又要隐藏导航栏,页面跳转路径又不固定,就会经常出现该显示导航栏的页面没显示,该隐藏导航栏的页面没隐藏等各种相关的bug。于是乎,我就将存放在心里已久的想法付诸实践,从一开始只支持管理导航栏的显示与隐藏,到后来支持返回手势、配置全局默认导航栏样式、视图控制器自定义导航栏样式、返回按钮、标签栏的管理,整个库的功能基本稳定可靠。项目也从之前出现导航栏显示与隐藏各种相关的bug,到后来完全不再出现相关问题,团队也不用浪费时间在相关问题上,日常有关开发的效率也得到了提高。

概览

YDRootNavigationController架构图

原理

  • 全局默认设置
  • 视图控制器自定义设置

全局默认设置

YDAppAppearanceProtocol协议定义了一些全局默认配置的属性,其中包含了导航栏、标签栏、工具栏样式的设置,还有显示隐藏状态、返回手势等一些属性的设置。

/// 全局默认配置
public protocol YDAppAppearanceProtocol {
    var barItemNormalTitleTextAttributes: [NSAttributedString.Key : Any]? { get }
    var barItemHighlightedTitleTextAttributes: [NSAttributedString.Key : Any]? { get }
    var barItemDisableTitleTextAttributes: [NSAttributedString.Key : Any]? { get }
    /// 导航栏标题文字属性
    var titleTextAttributes: [NSAttributedString.Key: Any]? { get }
    /// 导航栏背景颜色
    var navigationBarBackgroundColor: UIColor? { get }
    /// 导航栏背景图片(设置背景图片后,如果有设置背景颜色,背景颜色将失效)
    var navigationBarBackgroundImage: UIImage? { get }
    /// 导航栏阴影颜色
    var navigationBarShadowColor: UIColor? { get }
    /// 工具栏背景颜色
    var toolBarBackgroundColor: UIColor? { get }
    var toolBarShadowColor: UIColor? { get }
    var tabBarBackgroundColor: UIColor? { get }
    var tabBarShadowColor: UIColor? { get }
    /// 返回按钮图片
    var backItemImage: UIImage? { get }
    /// 返回按钮图片内边距
    var backItemImageInsets: UIEdgeInsets? { get }
    var isHidesBackItem: Bool { get }
    /// 设置是否隐藏导航栏(默认关闭)
    var prefersNavigationBarHidden: Bool { get }
    /// (默认打开)设置导航栏控制器push时,是否隐藏标签栏(当导航栏控制器是标签栏控制器的子控制器时)
    var isHidesBottomBarWhenPushed: Bool { get }
    /// 设置侧滑返回手势是否开启(默认打开)
    var isInteractivePopGestureEnabled: Bool { get }
    /// 设置全屏返回手势是否开启(默认关闭)
    var isFullScreenPopGestureEnabled: Bool { get }
}

通过YDAppAppearanceProtocol的扩展实现相关属性的默认设置

public extension YDAppAppearanceProtocol {
    var barItemNormalTitleTextAttributes: [NSAttributedString.Key : Any]? { nil }
    var barItemHighlightedTitleTextAttributes: [NSAttributedString.Key : Any]? { nil }
    var barItemDisableTitleTextAttributes: [NSAttributedString.Key : Any]? { nil }
    var titleTextAttributes: [NSAttributedString.Key: Any]? { nil }
    var navigationBarBackgroundColor: UIColor? { nil }
    var navigationBarBackgroundImage: UIImage? { nil }
    var navigationBarShadowColor: UIColor? { nil }
    var toolBarBackgroundColor: UIColor? { nil }
    var toolBarShadowColor: UIColor? { nil }
    var tabBarBackgroundColor: UIColor? { nil }
    var tabBarShadowColor: UIColor? { nil }
    var backItemImage: UIImage? { nil }
    var backItemImageInsets: UIEdgeInsets? { nil }
    var isHidesBackItem: Bool { false }
    var prefersNavigationBarHidden: Bool { false }
    var isHidesBottomBarWhenPushed: Bool { true }
    var isInteractivePopGestureEnabled: Bool { true }
    var isFullScreenPopGestureEnabled: Bool { false }
    
    func configure() {…}
}

其中,configure()方法是用来配置导航栏、标签栏、工具栏默认样式生效的方法,该方法就是通过将协议属性返回的值设置到对应的原生导航栏、标签栏、工具栏appearance中,开发中只要有一个类实现YDAppAppearanceProtocol协议,如果属性值跟默认设置是一样的,即可以不用实现这个属性,然后在AppDelegate中调用configure()方法,就可以实现全局默认的导航栏、标签栏、工具栏样式。

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // 全局默认样式配置
        MyAppAppearance().configure()
        return true
    }
}

视图控制器自定义设置

YDViewControllerProtocol协议定义了一些视图控制器自定义的属性

@objc public protocol YDViewControllerProtocol {
    /// 是否隐藏返回按钮
    var isHidesBackItem: Bool { get }
    /// 是否隐藏导航栏
    var prefersNavigationBarHidden: Bool { get }
    /// push时是否隐藏标签栏
    var isHidesBottomBarWhenPushed: Bool { get }
    /// 设置侧滑返回手势是否开启(用于控制器显示前的默认设置)
    var isInteractivePopGestureEnabled: Bool { get }
    /// 设置全屏返回手势是否开启(用于控制器显示前的默认设置)
    var isFullScreenPopGestureEnabled: Bool { get }
    /// 自定义返回按钮
    var backItem: UIBarButtonItem? { get }
    /// 定制返回按钮样式
    var backItemType: YDBackItemType { get }
    /// 导航栏样式(背景颜色、标题文字属性、阴影颜色)
    var navigationBarAppearence: YDNavigationBarAppearence { get }
    /// 自定义返回按钮点击事件
    @objc func backItemAction(_ sender: Any?)
}

通过UIViewController扩展实现YDViewControllerProtocol来设置相关属性方法的默认值和实现

extension UIViewController: YDViewControllerProtocol {
    open var isHidesBackItem: Bool { YDRootNavigationController.appAppearance.isHidesBackItem }
    open var prefersNavigationBarHidden: Bool { YDRootNavigationController.appAppearance.prefersNavigationBarHidden }
    open var isHidesBottomBarWhenPushed: Bool { YDRootNavigationController.appAppearance.isHidesBottomBarWhenPushed }
    open var isInteractivePopGestureEnabled: Bool { YDRootNavigationController.appAppearance.isInteractivePopGestureEnabled }
    open var isFullScreenPopGestureEnabled: Bool { YDRootNavigationController.appAppearance.isFullScreenPopGestureEnabled }
    open var backItem: UIBarButtonItem? { nil }
    open var backItemType: YDBackItemType { .default() }
    open var navigationBarAppearence: YDNavigationBarAppearence {
        YDNavigationBarAppearence()
    }

    @objc open func backItemAction(_ sender: Any?) {
        navigationController?.popViewController(animated: true)
    }
    
    /// 设置侧滑返回手势是否开启(用于控制器显示后动态设置)
    /// - Parameter enable: 是否开启
    public func interactivePopGesture(_ isEnabled: Bool) {
        guard let navigationController = self.navigationController as? YDRootNavigationController else { return }
        navigationController.setInteractivePopGesture(isEnabled)
    }
    
    /// 设置全屏返回手势是否开启(用于控制器显示后动态设置)
    /// - Parameter enable: 是否开启
    public func fullScreenPopGesture(_ isEnabled: Bool) {
        guard let navigationController = self.navigationController as? YDRootNavigationController else { return }
        navigationController.setFullScreenPopGesture(isEnabled)
    }
}

YDRootNavigationController的类属性appAppearance为全局默认配置,如果没有实现YDAppAppearanceProtocol协议,就会生成一个协议默认实现类的实例对象。

open class YDRootNavigationController: UINavigationController {
    /// 全局默认设置
    static var appAppearance: YDAppAppearanceProtocol = YDAppAppearance()
}

YDRootNavigationController扩展实现UINavigationControllerDelegate协议,在实现协议的方法中去设置自定义导航栏的样式和显示状态,或者按照全局默认配置去设置,使每个视图控制器都有独立的导航栏样式、返回手势等其他相关配置,这个实现思路是参考了RTRootNavigationController ,但却不用插入额外的导航栏和视图控制器,保留了原生UINavigationController的使用方法,全屏返回手势的功能则是参考了 FDFullscreenPopGesture 的实现方法。YDRootNavigationController就是用Swift的面向协议编程将这两个库的功能完美的结合在一起,并扩展了功能。

extension YDRootNavigationController: UINavigationControllerDelegate {
    
    open func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
    }
    
    open func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
    }
    ……
}

具体使用方法可以查看这篇文章 Swift 全局默认导航栏样式与视图控制器自定义并存

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,458评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,030评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,879评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,278评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,296评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,019评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,633评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,541评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,068评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,181评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,318评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,991评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,670评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,183评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,302评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,655评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,327评论 2 358

推荐阅读更多精彩内容