全局向导航栏应用渐变并处理方向变化

Globally apply a gradient to the navigation bar and handle orientation changes

我需要在我的状态栏和导航栏上全局应用渐变,并让它根据方向变化进行适当调整。因为我希望它是全局的,所以我尝试使用 UIAppearance。令人惊讶的是,UIAppearance 并不容易。

纵向看起来很棒,但横向时渐变太高,所以您看不到整个东西:

这是我的代码:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    let navigationBarAppearance = UINavigationBar.appearance()
    navigationBarAppearance.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
    navigationBarAppearance.isTranslucent = false
    navigationBarAppearance.tintColor = UIColor.white

    let status_height = UIApplication.shared.statusBarFrame.size.height
    let gradientLayer = CAGradientLayer(frame: CGRect(x: 0, y: 0, width: 64, height: status_height + 44), colors: [UIColor.init(hex: "005382"), UIColor.init(hex: "00294B")])
    let layerImage = gradientLayer.createGradientImage()
    navigationBarAppearance.barTintColor = UIColor(patternImage: layerImage ?? UIImage())
}

我正在使用此扩展程序:

extension CAGradientLayer {
  convenience init(frame: CGRect, colors: [UIColor]) {
    self.init()
    self.frame = frame
    self.colors = []
    for color in colors {
      self.colors?.append(color.cgColor)
    }
    startPoint = CGPoint(x: 0, y: 0)
    endPoint = CGPoint(x: 0, y: 1)
  }

  func createGradientImage() -> UIImage? {

    var image: UIImage? = nil

    UIGraphicsBeginImageContext(bounds.size)

    if let context = UIGraphicsGetCurrentContext() {
      render(in: context)
      image = UIGraphicsGetImageFromCurrentImageContext()
    }

    UIGraphicsEndImageContext()

    return image
  } 
}

我知道我可以检查方向,然后相应地更改渐变,但我需要在每个视图控制器上都这样做,这样会破坏使用 UIAppearance 并能够在一处。

我发现的大多数 SO 线程都提供了在视图控制器级别而非全局级别制作顶部栏渐变的解决方案。

编辑: 在我的 UITabBarController 上试过 @Pan Surakami 的回答,但我仍然有白色导航栏:

这是我的故事板设置:

和代码:

class MenuTabBarController: UITabBarController {
    var notificationsVM = NotificationsVModel()
    var hasNewAlerts: Bool = false

    override func viewDidLoad() {
      super.viewDidLoad()

      setTabs()

      styleUI()

      notificationsVM.fetchData { (success, newNotifications) in
            if success {
                self.hasNewAlerts = newNotifications.count > 0 ? true : false
                DispatchQueue.main.async {
                    if let tabBarItems = self.tabBar.items {
                        for (_, each) in tabBarItems.enumerated() {
                            if each.tag == 999 { //only update the alerts tabBarItem tag == '999'
                                self.updateAlertBadgeIcon(self.hasNewAlerts, each)
                            }
                        }
                    }
                }
            }
        }
    }

  fileprivate func setTabs() {
    let tab1 = GradientNavigationController(rootViewController: FeedViewController())
    let tab2 = GradientNavigationController(rootViewController: NotificationsTableViewController())
    let tab3 = GradientNavigationController(rootViewController: SearchViewController())
    let tab4 = GradientNavigationController(rootViewController: ComposeDiscussionViewController())
    UITabBarController().setViewControllers([tab1, tab2, tab3, tab4], animated: false)
  }

    func updateAlertBadgeIcon(_ hasAlerts: Bool, _ item: UITabBarItem) {
        if hasAlerts {
            item.image = UIImage(named: "alert-unselected-hasAlerts")?.withRenderingMode(UIImage.RenderingMode.alwaysOriginal)
            item.selectedImage = UIImage(named: "alert-selected-hasAlerts")?.withRenderingMode(UIImage.RenderingMode.alwaysOriginal)
        } else {
            hasNewAlerts = false
            item.image = UIImage(named: "alert-unselected-noAlerts")?.withRenderingMode(UIImage.RenderingMode.alwaysOriginal)
            item.selectedImage = UIImage(named: "alert-selected-noAlerts")?.withRenderingMode(UIImage.RenderingMode.alwaysOriginal)
        }
    }

    // UITabBarDelegate
    override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
        if item.tag == 999 { //alerts tabBarItem tag == '999'
            updateAlertBadgeIcon(hasNewAlerts, item)
        }
      if item.tag == 0 { //Feed Item clicked
        if let feedNav = children[0] as? UINavigationController, let feedVC = feedNav.viewControllers[0] as? FeedViewController {
          feedVC.tableView.reloadData()
        }

      }
    }

    func styleUI() {
        UITabBar.appearance().backgroundImage = UIImage.colorForNavBar(color:.lightGrey4)
        UITabBar.appearance().shadowImage = UIImage.colorForNavBar(color:.clear) 
        tabBar.layer.shadowOffset = CGSize.zero
        tabBar.layer.shadowRadius = 2.0
        tabBar.layer.shadowColor = UIColor.black.cgColor
        tabBar.layer.shadowOpacity = 0.30
        UITabBarItem.appearance().setTitleTextAttributes([NSAttributedString.Key.foregroundColor : UIColor.grey2,
                                                          NSAttributedString.Key.font: UIFont(name: "AvenirNext-DemiBold", size: 12) as Any],
                                                         for: .normal)
        UITabBarItem.appearance().setTitleTextAttributes([NSAttributedString.Key.foregroundColor : UIColor.darkSapphire,
                                                          NSAttributedString.Key.font: UIFont(name: "AvenirNext-DemiBold", size: 12) as Any],
                                                         for: .selected)
    }
}

实现它的一种方法是继承 UINavigationController.

  1. 创建一个新的子类。
class GradientNavigationController: UINavigationController {}
  1. 覆盖 traitCollectionDidChange 方法。
class GradientNavigationController: UINavigationController {
    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        super.traitCollectionDidChange(previousTraitCollection)

        let status_height = UIApplication.shared.statusBarFrame.size.height
        let gradientLayer = CAGradientLayer(frame: CGRect(x: 0, y: 0, width: 64, height: status_height + 44), colors: [[UIColor.init(hex: "005382"), UIColor.init(hex: "00294B")])
        let layerImage = gradientLayer.createGradientImage()
        self.navigationBar.barTintColor = UIColor(patternImage: layerImage ?? UIImage())
    }
}
  1. 使用这个子类而不是 UINavigationController。更改故事板上的自定义子类或在代码中使用它。

编辑: 您的 UITabBarController 由故事板配置。所以setTabs()这样用是没有意义的。它只是创建 UITabBarController 的另一个副本,然后将其删除。我只是将其作为嵌入控制器的示例进行展示。

  1. 删除方法setTabs()
  2. 打开链接到您的选项卡的每个故事板。 (我看不到它们实际上是如何配置的,它们在情节提要参考后面。)
  3. 确保初始控制器是 GradientNavigationController

enter image description here