引言
导导航栏在iOS应用中扮演着至关重要的角色,几乎所有的项目都离不开它。虽然未来可能会有新的设计趋势取代导航栏,但至少在目前,它依然是主流。因此,我们有必要深入了解如何在项目中更灵活、更优雅地设置导航栏。
系统导航栏
UI设计
通常来讲当我们创建一个APP的时候都会使用UINavgationController来包裹我们的视图控制器,这步操作也就为我们的视图控制器添加了系统的导航栏,使它具备有push其它视图控制器的能力,这也是最基本最常见的做法。
func addSubViewControllers() {
var viewControllers = [UIViewController]()
/// 首页
let homeViewController = PHHomeViewController()
let homeNavgationController = UINavigationController(rootViewController: homeViewController)
viewControllers.append(homeNavgationController)
....
/// 我的
let mineViewController = PHMeViewController()
let mineNavgationController = UINavigationController(rootViewController: mineViewController)
viewControllers.append(mineNavgationController)
self.viewControllers = viewControllers
// 默认显示视频页面
selectedIndex = CSTabBarConfig.defaultSelectedIndex
}
为了示例看起来更容易理解,采用了逐个添加的方式,但是事实上我们最好是从配置中读取子控制器的数据,根据数据来创建子控制器。
这样创建出来子控制器会自动携带导航栏,而且功能十分强大,我们可以直接设置,导航栏颜色,设置标题,设置左右按钮,设置导航栏是否透明,设置导航栏的背景图片,等等等。
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = "Home"
if #available(iOS 13.0, *) {
let appearance = UINavigationBarAppearance()
appearance.backgroundColor = .white
self.navigationController?.navigationBar.standardAppearance = appearance
} else {
self.navigationController?.navigationBar.backgroundColor = .white
}
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Search", style: .plain, target: self, action: #selector(nextAction))
self.navigationController?.navigationBar.isTranslucent = false
}
效果如下:
看上去没什么问题,页面简洁也很漂亮。但是设计团队一般不会只有这样的导航栏,实际开发中我们会遇到各种各样的的导航栏设计,并且不光是静态的设计,有些导航栏的设计还会跟随页面滑动而改变。
比如这样:
或者这样:
这时候系统的导航栏为我们提供的功能就显得有些捉襟见肘,倒不是说它不能实现这样的效果,只是往往需要编写大量的代码才能实现。
页面切换
为页面添加导航栏除了可以看见的UI设计外,它的另外一个主要功能就是使视图控制器可以push出另外一个视图控制器,并且还可以平滑的返回到当前控制器。
为了演示这个功能,来实现一下search按钮的点击事件,代码如下:
@objc func nextAction() {
let searchVC = PHSearchViewController()
self.navigationController?.pushViewController(searchVC, animated: true)
}
而搜索页面,我们只是简单的修改一下导航栏的标题,以及修改属于它的导航栏颜色,代码如下:
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = "Search"
let appearance = UINavigationBarAppearance()
appearance.backgroundColor = .orange
self.navigationController?.navigationBar.standardAppearance = appearance
}
效果如下:
我们成功的切换也页面,并在新的页面中设置了它自己的导航栏颜色,它还为我们携带了一个返回按钮,可以让我们返回到上一个页面。
但是如果我们来看一下完整的流程的话会发现有一点问题,完整流程如下:
可以发现,当我们切换到子页面的时候:
- 底部TabBar没有自动隐藏,通常来讲TabBar只会显示在一级页面,而具体的详情或者是其它子页面并不需要显示它。
- 奇怪的返回按钮,我们并没有设置返回按钮,它自动添加的返回按钮样式设计非常不友好,除了图标还有文案,颜色也不是我们想要的颜色。
- 返回到Home页,导航栏背景的颜色仍然是橙色。
除了上述的问题,还有许多其它的不太友好的地方,比如某一个页面需要隐藏导航栏,那么返回到其它页面导航栏也会消失。
问题分析
那我们来分析一下为什么会有这些问题呢?
1.首先从UI设计上来说,系统提供的UINavigationBar为我们提供很大的便利,简化了页面的实现流程,同时丢失的就是灵活性和自定义的能力。
2.关于TabBar的问题,由于navigationController是PHHomeViewController所提供的,而PHHomeViewController又被创建在PHTabBarController下面,属于PHTabBarController的子视图,那么由PHHomeViewController的navigationController推出的所有页面当然也都在PHTabBarController之下,所以二级页面,三级页面如果你不手动隐藏的话也都仍然会显示TabBar。
3.返回按钮的问题,其实也属于UI设计的问题。这是由系统的UINavigationBar自动创建的返回按钮。
4.背景颜色问题,其实所有由PHHomeViewController的navigationController推出的视图的导航控制器都是同一个,那么它们的导航栏也就都是同一个,当我们修改颜色的时候所有导航栏的颜色都会改变这就不奇怪了。
自定义导航栏
那么上面系统导航栏存在的问题,该怎么解决呢?
其实系统提供的导航栏本身也可以解决这些问题,它的返回按钮可以重新设置,它的背景颜色也可以根据不同的视图控制器进行修改,TabBar也可以根据页面的层级进行手动隐藏和显式,但是这需要写很多的代码,很多的重复代码。而且在实际开发中,我们往往不会采用这种方式,毕竟费力不讨好。
那么该如何设置导航导航了,才能更优雅的解决这些问题呢?这时候自定义导航栏就该登场了。
设置NavigationController
为了更优雅的使用自定义导航栏,我们需要APP启动开始就进行一些设置,直接将UITabBarController包裹在UINavigationController下,并隐藏navigationBar,代码如下:
private func setupRootViewController() {
window = UIWindow(frame: UIScreen.main.bounds)
window?.backgroundColor = .white
window?.makeKeyAndVisible()
let tabbarController = PHTabBarController()
let nav = UINavigationController(rootViewController: tabbarController)
nav.isNavigationBarHidden = true
window?.rootViewController = nav
}
那么设置子控制器的代码,就不需要添加UINavigationController了,修改后代码如下:
var viewControllers = [UIViewController]()
let dataSource = CSTabBarConfig.tabBarSubViewControllerData
for dataDict in dataSource {
if let className = dataDict["class_name"] {
if let targetClass = NSClassFromString("MeMe.\(className)") as? UIViewController.Type {
let targetViewController = targetClass.init()
viewControllers.append(targetViewController)
}
}
}
self.viewControllers = viewControllers
做了这一步之后,会有什么效果呢?
我们发现所有的页面控制器也仍然都具备push新页面控制器的能力,而且TabBar会自动隐藏,不需要我们再关心它的隐藏和现实控制了。
目前导航栏处于隐藏的状态,没有标题和返回按钮,看起来有点奇怪,会让用户觉得有点无从下手,但是如果把导航栏显示出来,上面的问题似乎就又避免不了了。
设置UINavigationBar
导航栏对于用户来说,其实就是页面顶部带一些标题信息和操作按钮的视图而已,他们并不关心是UINavigationBar还是UIView还是UIImageView,那我们在实现的时候也不必那么拘束了。
我们就采用UIView来自定义一个导航栏,你完全可以按照自己项目的主题来构建它,比如它的标题在左侧,比如它的背景是一张完整图片或者渐变图层。
自定义UI
这里我们采用一个最基础的样式来进行举例,白色背景,标题居中,左侧带返回按钮,代码如下:
class PHNavigationBar: UIView {
/// 标题
var title: String? {
didSet {
titleLabel.text = title
}
}
/// 是否显示返回按钮
var isShowBackButton: Bool = false {
didSet {
backButton.isHidden = !isShowBackButton
}
}
/// 返回点击回调
var backButtonTouchBlock: (() -> Void)?
/// 标题
private let titleLabel = UILabel()
/// 返回按钮
private let backButton = UIButton()
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
setLayout()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupView() {
addSubview(titleLabel)
titleLabel.font = UIFont.systemFont(ofSize: 20, weight: .heavy)
titleLabel.textColor = .black
titleLabel.textAlignment = .center
addSubview(backButton)
backButton.isHidden = true
backButton.setImage(UIImage(named: "icon_back_18"), for: .normal)
backButton.addTarget(self, action: #selector(backButtonTouch), for: .touchUpInside)
}
func setLayout() {
titleLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.bottom.equalToSuperview().offset(-10)
}
backButton.snp.makeConstraints { make in
make.left.equalToSuperview().offset(10.0)
make.centerY.equalTo(titleLabel)
make.size.equalTo(CGSize(width: 30.0, height: 30.0))
}
}
@objc func backButtonTouch() {
backButtonTouchBlock?()
}
}
我们设置的样式非常基础,但是也十分通用,几乎可以适应大部分APP。
使用自定义导航栏
下面来看一下它是如何使用的,我们在PHBaseViewController中使用自定义导航栏,代码如下:
/// 导航栏
let customNavigationBar = PHNavigationBar()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
addCustomNavigationBar()
}
func addCustomNavigationBar() {
customNavigationBar.frame = CGRect(x: 0, y: 0, width: CS_SCREENWIDTH, height: cs_navigationBarHeight)
customNavigationBar.backgroundColor = .white
self.view.addSubview(customNavigationBar)
customNavigationBar.backButtonTouchBlock = { [weak self] in
self?.goBack()
}
}
func goBack() {
self.navigationController?.popViewController(animated: true)
}
这样设置之后,每个页面都有属于自己的导航栏UI,在任何页面对导航栏的属性进行设置和修改,都不会影响其它页面。
设置自定义导航栏
接下来在每个页面中都可以自定义自己的导航栏样式。
PHHomeViewController下代码:
func setNavigation() {
self.customNavigationBar.title = "Home"
}
PHSearchViewController下代码:
func setNavigation() {
self.customNavigationBar.backgroundColor = .red
self.customNavigationBar.title = "Search"
self.customNavigationBar.isShowBackButton = true
}
PHDetailViewController下代码:
func setNavigation() {
self.customNavigationBar.title = "详情"
self.customNavigationBar.backgroundColor = UIColor.orange.withAlphaComponent(0.3)
self.customNavigationBar.isShowBackButton = true
}
效果如下:
结语
通过本篇博客,我们展示了自定义导航栏的基本用法,但这只是一个开始。自定义导航栏继承自UIView,拥有强大的可扩展性和灵活性。无论是设置独特的背景图片,还是实现渐变效果,完全自定义的导航栏都能轻松应对。
更重要的是,自定义导航栏可以在不影响其他页面的情况下,动态调整透明度、高度和样式。这种灵活性使得每个页面都能拥有独特的导航体验。
希望这些示例能激发你在项目中大胆尝试和运用自定义导航栏的创意。让我们一起探索更多可能性,提升用户体验。