Swift 4 最新版“沉浸式”效果,导航栏下滑改变透明度

绝地灬酷狼 2023-07-03 08:16 64阅读 0赞

代码地址:GitHub 仓库 - HideNavigationbarDemo

20200204214200446.gif20200204214226209.gif

以前的沉浸式做法有很多,比如拿到 navigationBarbackgroundImage 修改透明度,或者往 navigationBarview层加一个占位的视图,修改这个占位视图的透明度在视觉上形成直接修改navigationBar 的效果。

上面的做法我都试过,也许是版本过时了。第一种 navigationBar.subviews.first已经不是backgroundImage (似乎IOS11之后已经被修改了);第二种方法,占位视图通过insertSubview加入后,会阻挡navigationItem上的标题和图标。

我在第二种方法的基础上做了修改,既然直接在 navigationBar添加占位视图会阻挡标题,那就直接内容视图里添加,反正导航栏已经设置成透明的,只要这个占位视图在放在导航栏正下方,视觉上其实是一样的。

在这里插入图片描述

但做下去发现,每次退出页面都要恢复导航栏的样式,毕竟导航栏是多个页面共用(除非你每一个页面前都新建一个Navigation Controller,那就当我没说~),可是恢复透明度、背景图片巴拉巴拉的又是一堆代码,而且还有视觉上的巨大不协调性,如果每次退出都要重设一大堆东西,那也太不灵活了。我相信90%的程序员都会被这个恢复搞到头昏脑胀(包括我)。

于是想到,既然背景的透明渐变效果我都能用占位视图模拟,那干脆隐藏系统的导航栏,用新的NavigationBar,这样就不需要考虑恢复透明度、重设背景图片、shadow图片巴拉巴拉的问题,而只是隐藏和不隐藏的操作,一行代码的事。嗯,真香。

在这里插入图片描述

代码

如果你很聪明,只看下面的代码自己就能完成所有的效果。如果你和我一样懒,不想管那么多逻辑,直接Copy保存到一个swift文件,然后跳过这一节,直接看Demo是怎么使用的吧。

  1. private var navBarHolderViewKey = "navBarHolderViewKey"
  2. private var navBarBackgroundViewKey = "navBarBackgroundViewKey"
  3. private var navBarViewKey = "navBarViewKey"
  4. extension UIViewController {
  5. fileprivate var navBarHolder: UIView? {
  6. set {
  7. objc_setAssociatedObject(self, &navBarHolderViewKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  8. }
  9. get {
  10. return objc_getAssociatedObject(self, &navBarHolderViewKey) as? UIView
  11. }
  12. }
  13. // 计算属性保存自定义的导航栏
  14. var customNavigationBar: UINavigationBar? {
  15. set {
  16. objc_setAssociatedObject(self, &navBarViewKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  17. }
  18. get {
  19. return objc_getAssociatedObject(self, &navBarViewKey) as? UINavigationBar
  20. }
  21. }
  22. // 计算属性保存导航栏背景占位view
  23. fileprivate var navBarBackgroundView: UIView? {
  24. set {
  25. objc_setAssociatedObject(self, &navBarBackgroundViewKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  26. }
  27. get {
  28. return objc_getAssociatedObject(self, &navBarBackgroundViewKey) as? UIView
  29. }
  30. }
  31. func setCustomNavigationBar(_ color: UIColor) {
  32. if customNavigationBar == nil {
  33. // 隐藏导航栏
  34. navigationController?.navigationBar.isHidden = true
  35. let statusBarHeight = UIApplication.shared.statusBarFrame.height
  36. let navigationBarHeight = (navigationController?.navigationBar.frame.height)!
  37. navBarHolder = UIView(frame: CGRect(x: 0, y: 0,
  38. width: view.frame.width,
  39. height: statusBarHeight + navigationBarHeight))
  40. navBarHolder?.autoresizingMask = [.flexibleHeight, .flexibleWidth]
  41. navBarBackgroundView = UIView(frame: navBarHolder!.frame)
  42. navBarBackgroundView?.backgroundColor = color
  43. navBarHolder?.addSubview(navBarBackgroundView!)
  44. let navBar = UINavigationBar(frame: CGRect(x: 0, y: statusBarHeight, width: view.frame.width, height: navigationBarHeight))
  45. navBar.setBackgroundImage(UIImage(), for: .default)
  46. navBar.shadowImage = UIImage()
  47. navBar.isTranslucent = true
  48. navBarHolder?.addSubview(navBar)
  49. view.addSubview(navBarHolder!)
  50. customNavigationBar = navBar
  51. }
  52. }
  53. // 调用此方法修改导航栏透明度
  54. func setCustomNavigationBarColor(alpha: CGFloat) {
  55. let backgroundView = self.customNavigationBar
  56. if(backgroundView != nil) {
  57. navBarBackgroundView?.alpha = alpha
  58. // 确保处于view的最顶层,防止被覆盖
  59. view.bringSubviewToFront(navBarHolder!)
  60. }
  61. }
  62. func restoreNavigationBar() {
  63. navigationController?.navigationBar.isHidden = false
  64. }
  65. }

使用

透明度根据滑动的距离实时计算,注释比代码还多,看不懂就真该打了,哈哈~

  1. class SecondViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
  2. @IBOutlet weak var tableView: UITableView!
  3. override func viewDidLoad() {
  4. super.viewDidLoad()
  5. // 设置导航栏颜色为白色
  6. setCustomNavigationBar(.white)
  7. // 导航栏的一些图标
  8. let mNavigationItem = UINavigationItem()
  9. let lbtn = UIBarButtonItem(title: "返回", style: .plain, target: self, action: #selector(back))
  10. mNavigationItem.setLeftBarButton(lbtn, animated: true)
  11. customNavigationBar?.items = [mNavigationItem]
  12. ... // 省略非关键代码
  13. }
  14. ... // 省略非关键代码
  15. // MARK: 重写该方法,以便根据滑动距离确认透明度的取值。能下拉的空间肯定有 Scroll View,比如 Table View
  16. func scrollViewDidScroll(_ scrollView: UIScrollView) {
  17. if(navigationController == nil) {
  18. return
  19. }
  20. // 状态栏高度
  21. let statusBarHeight = UIApplication.shared.statusBarFrame.height
  22. // 最大偏移量: scrollView向下滑动该距离 后 导航栏全不透明
  23. let maxAlphaOffset = CGFloat(200)
  24. // 最小偏移量: scrollView向下滑动该距离 前 导航栏全透明
  25. let minAlphaOffset = CGFloat(0)
  26. // 已滑动距离: 加上状态栏的高度是因为scrollView一开始
  27. // 已经默认向上滑动了这段距离,可以自己打印看看偏移量
  28. let offset = scrollView.contentOffset.y + statusBarHeight
  29. // 按比例计算alpha,且取值必须在 [0,1]
  30. let a = max(min((offset - minAlphaOffset) / (maxAlphaOffset - minAlphaOffset), 1), 0)
  31. // 根据计算得到的alpha修改导航栏透明度
  32. setCustomNavigationBarColor(alpha: a)
  33. // 导航栏字体或者图标的颜色也可以跟着修改
  34. customNavigationBar!.tintColor = UIColor(red: 1 - a, green: 1 - a, blue: 1 - a, alpha: 1)
  35. }
  36. override func viewWillDisappear(_ animated: Bool) {
  37. super.viewWillDisappear(animated)
  38. // 离开当前页面需要恢复导航栏样式,不影响其他页面
  39. restoreNavigationBar()
  40. }
  41. }

注意

暂时还不支持 UITableViewController,虽然 UITableViewController 继承了 UIViewController,但是你会发现,如果你调用setCustomNavigationBar,导航栏被直接添加到了Scroll View 上,也就是说导航栏会跟着页面滑动。

如果你已经写好了UITableViewController,其实很简单,把Table View嵌入到一个UIViewController上。千万别说只会用UITableViewController,不知道怎么取出里面的Table View,那你就真的还要修炼修炼了。


具体的使用还是推荐看我在文首给出的Demo地址。

如果觉得对你有帮助请求下你的Star。有问题也请及时和我沟通,欢迎指正。

第一张动图是我开源的入门项目,无须后台,即下即用,供大家学习、作业使用,哈哈

地址:一个在线购书App

发表评论

表情:
评论列表 (有 0 条评论,64人围观)

还没有评论,来说两句吧...

相关阅读