【问题标题】:Can't change UINavigationBar prompt color无法更改 UINavigationBar 提示颜色
【发布时间】:2018-02-26 05:23:09
【问题描述】:

我无法更改导航栏上的提示颜色。我在viewDidLoad 中尝试了下面的代码,但没有任何反应。

self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.white]

我错过了什么吗?上面的代码错了吗?

【问题讨论】:

  • 试试navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
  • 在 swift 4 中,titleTextAttributes 的类型为 [NSAttributedStringKey: Any]?NSForegroundColorAttributeName 是一个字符串,所以这种方式不会构建。

标签: ios swift cocoa-touch uikit uinavigationbar


【解决方案1】:

我能够在 iOS 11 上将提示颜色设置为白色,将 barStyle 设置为黑色。我使用外观代理设置了其他颜色属性(如所需的背景颜色):

myNavbar.barStyle = UIBarStyleBlack; // Objective-C
myNavbar.barStyle = .black // Swift

【讨论】:

  • 明确说明将栏样式设置为 .black 是否意味着您的导航栏颜色将为黑色。当您使用 Markus 提到的外观代理时,它将是您设置的任何颜色。 .black 设置是导航栏的样式
  • 效果很好,这应该是公认的答案 - 它很简单,没有副作用或使用私有方法。
  • barstyle 设置为.black 也会影响嵌入式搜索栏。所以搜索栏的背景是黑色的。当您使用 UISearchController 时,几乎肯定不是您想要的。
【解决方案2】:

看来你是对的。您需要使用UIAppearance 来设置 iOS 11 上的提示文本样式。

我已提交雷达 #34758558,titleTextAttributes 属性刚刚停止在 iOS 11 中提示。

好消息是有几个变通方法,我们可以通过使用 Xcode 的视图层次调试器来发现:

// 1. This works, but potentially changes *all* labels in the navigation bar. 
// If you want this, it works.
UILabel.appearance(whenContainedInInstancesOf: [UINavigationBar.self]).textColor = UIColor.white

提示只是一个UILabel。如果我们使用 UIAppearance 的 whenContainedInInstancesOf:,我们可以很容易地按照我们想要的方式更新颜色。

如果您仔细观察,您会注意到 UILabel 上还有一个包装器视图。它有自己的类,可能会响应 UIAppearance...

// 2. This is a more precise workaround but it requires using a private class.

if let promptClass = NSClassFromString("_UINavigationBarModernPromptView") as? UIAppearanceContainer.Type
{
  UILabel.appearance(whenContainedInInstancesOf: [promptClass]).textColor = UIColor.white
}

我建议坚持使用更通用的解决方案,因为它不使用私有 API。 (应用审查等)看看这两种解决方案中的任何一种都有什么效果:

【讨论】:

  • @fivewood 哪个部分不起作用?第 1 部分还是第 2 部分?
  • 条件为真,if语句的主体似乎执行了,但我的提示文本没有变化。
  • 哪个版本的iOS?这个答案适用于 iOS 11。我很好奇 iOS 12 中的私有 API (SPI) 或内部类名是否发生了变化。这是我首先要看的地方。
  • iOS 12.1.1 beta 3
  • 请记住,这也会改变搜索栏文本字段提示的颜色。如果是这样,您的解决方案 2
【解决方案3】:

你可以使用

for view in self.navigationController?.navigationBar.subviews ?? [] {
    let subviews = view.subviews
    if subviews.count > 0, let label = subviews[0] as? UILabel {
        label.textColor = UIColor.white
        label.backgroundColor = UIColor.red
    }
}

这将是一个临时的解决方法,直到他们修复它

【讨论】:

    【解决方案4】:

    支持新旧iOS的更复杂的版本

        func updatePromptUI(for state: State) {
        if (state != .Online) {
            //workaround for SOFT-7019 (iOS 11 bug - Offline message has transparent background)
            if #available(iOS 11.0, *) {
                showPromptView()
            } else {
                showOldPromptView()
            }
        }
        else {
            self.navigationItem.prompt = nil
            if #available(iOS 11.0, *) {
                self.removePromptView()
            } else {
                self.navigationController?.navigationBar.titleTextAttributes = nil
                self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor:UIColor.lightGray]
            }
        }
    }
    
    private func showOldPromptView() {
        self.navigationItem.prompt = "Network Offline. Some actions may be unavailable."
        let navbarFont = UIFont.systemFont(ofSize: 16)
        self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.font: navbarFont, NSAttributedStringKey.foregroundColor:UIColor.white]
    }
    
    private func showPromptView() {
        self.navigationItem.prompt = String()
        self.removePromptView()
    
        let promptView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 18))
        promptView.backgroundColor = .red
        let promptLabel = UILabel(frame: CGRect(x: 0, y: 2, width: promptView.frame.width, height: 14))
        promptLabel.text = "Network Offline. Some actions may be unavailable."
        promptLabel.textColor = .white
        promptLabel.textAlignment = .center
        promptLabel.font = promptLabel.font.withSize(13)
        promptView.addSubview(promptLabel)
        self.navigationController?.navigationBar.addSubview(promptView)
    }
    
    private func removePromptView() {
        for view in self.navigationController?.navigationBar.subviews ?? [] {
            view.removeFromSuperview()
        }
    }
    

    【讨论】:

      【解决方案5】:

      我建议使用自定义 UINavigationBar 子类并覆盖 layoutSubviews

      - (void)layoutSubviews {
          [super layoutSubviews];
      
          if (self.topItem.prompt) {
              UILabel *promptLabel = [[self recursiveSubviewsOfKind:UILabel.class] selectFirstObjectUsingBlock:^BOOL(UILabel *label) {
                  return [label.text isEqualToString:self.topItem.prompt];
              }];
              promptLabel.textColor = self.tintColor;
          }
      }
      

      基本上我会枚举子视图层次结构中的所有 UILabel 并检查它们的文本是否与提示文本匹配。然后我们将 textColor 设置为 tintColor(随意使用自定义颜色)。这样,我们不必将私有 _UINavigationBarModernPromptView 类硬编码为提示标签的超级视图。因此,代码更具前瞻性。

      将代码转换为 Swift 并实现辅助方法 recursiveSubviewsOfKind:selectFirstObjectUsingBlock: 留给读者作为练习 ?。

      【讨论】:

      • 终于真正解决了这个问题! 10x
      【解决方案6】:

      你可以试试这个:

      import UIKit
      class ViewController: UITableViewController {
          override func viewWillAppear(_ animated: Bool) {
              super.viewWillAppear(animated)
              updatePrompt()
          }
          override func viewDidAppear(_ animated: Bool) {
              super.viewDidAppear(animated)
              updatePrompt()
          }
          func updatePrompt() {
              navigationItem.prompt = " "
              for view in navigationController?.navigationBar.subviews ?? [] where NSStringFromClass(view.classForCoder) == "_UINavigationBarModernPromptView" {
                  if let prompt = view.subviews.first as? UILabel {
                      prompt.text = "Hello Red Prompt"
                      prompt.textColor = .red
                  }
              }
              navigationItem.title = "This is the title (Another color)"
          }
      }
      

      【讨论】:

      • 您可以检查 label.adjustsFontSizeToFitWidth 而不是 _UINavigationBarModernPromptView 。此标签是导航栏中唯一的自动收缩
      【解决方案7】:

      试试这个:->

      navController.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor.rawValue: UIColor.red]
      

      【讨论】:

      • 在 Swift 4 中 titleTextAttributes 的类型为 [NSAttributedStringKey: Any]?NSAttributedStringKey.foregroundColor.rawValueString。您的代码不会在 Swift 4 中编译。
      • navigationController?.navigationBar.prefersLargeTitles = true 编辑 如果需要更改大标题的文本属性,则需要使用 UINavigationBar 上的新 largeTitleTextAttributes 属性: UINavigationBar.appearance().largeTitleTextAttributes = [ NSForegroundColorAttributeName : UIColor.white ]
      • 它似乎不适用于 iOS11 (xCode 9.0.0)。大标题改变颜色但不提示!
      【解决方案8】:

      Moshe 的第一个答案对我不起作用,因为它更改了系统 VC 内部的标签,例如邮件和文本撰写 VC。我可以改变那些导航栏的背景,但这会打开另一罐蠕虫。我不想走私人课程路线,所以我只更改了自定义导航栏子类中包含的 UILabel。

      UILabel.appearance(whenContainedInInstancesOf: [NavigationBar.self]).textColor = UIColor.white
      

      【讨论】:

        【解决方案9】:

        我找到了 iOS 11 的下一个解决方法。 您需要设置在viewDidLoad navigationItem.prompt = UINavigationController.fakeUniqueText 然后放下一件事

            navigationController?.promptLabel(completion: { label in
            label?.textColor = .white
            label?.font = Font.regularFont(size: .p12)
                })
        
            extension UINavigationController {
            public static let fakeUniqueText = "\n\n\n\n\n"
            func promptLabel(completion: @escaping (UILabel?) -> Void) {
                gloabalThread(after: 0.5) { [weak self] in
                    guard let `self` = self else {
                        return
                    }
                    let label = self.findPromptLabel(at: self.navigationBar)
                    mainThread {
                        completion(label)
                    }
                }
            }
        
            func findPromptLabel(at view: UIView) -> UILabel? {
                if let label = view as? UILabel {
                    if label.text == UINavigationController.fakeUniqueText {
                        return label
                    }
                }
                var label: UILabel?
                view.subviews.forEach { subview in
                    if let promptLabel = findPromptLabel(at: subview) {
                        label = promptLabel
                    }
                }
                return label
            }
            }
        
        
            public func mainThread(_ completion: @escaping SimpleCompletion) {
                DispatchQueue.main.async(execute: completion)
            }
        
        
            public func gloabalThread(after: Double, completion: @escaping SimpleCompletion) {
               DispatchQueue.global().asyncAfter(deadline: .now() + after) {
               completion()
            }
        }

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-08-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-06-20
          相关资源
          最近更新 更多