全局向导航栏应用渐变并处理方向变化
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
.
- 创建一个新的子类。
class GradientNavigationController: UINavigationController {}
- 覆盖
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())
}
}
- 使用这个子类而不是
UINavigationController
。更改故事板上的自定义子类或在代码中使用它。
编辑:
您的 UITabBarController
由故事板配置。所以setTabs()
这样用是没有意义的。它只是创建 UITabBarController
的另一个副本,然后将其删除。我只是将其作为嵌入控制器的示例进行展示。
- 删除方法
setTabs()
。
- 打开链接到您的选项卡的每个故事板。
(我看不到它们实际上是如何配置的,它们在情节提要参考后面。)
- 确保初始控制器是
GradientNavigationController
。
enter image description here
我需要在我的状态栏和导航栏上全局应用渐变,并让它根据方向变化进行适当调整。因为我希望它是全局的,所以我尝试使用 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
.
- 创建一个新的子类。
class GradientNavigationController: UINavigationController {}
- 覆盖
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())
}
}
- 使用这个子类而不是
UINavigationController
。更改故事板上的自定义子类或在代码中使用它。
编辑:
您的 UITabBarController
由故事板配置。所以setTabs()
这样用是没有意义的。它只是创建 UITabBarController
的另一个副本,然后将其删除。我只是将其作为嵌入控制器的示例进行展示。
- 删除方法
setTabs()
。 - 打开链接到您的选项卡的每个故事板。 (我看不到它们实际上是如何配置的,它们在情节提要参考后面。)
- 确保初始控制器是
GradientNavigationController
。
enter image description here