How to set multi line Large title in navigation bar? ( New feature of iOS 11)
Based in @krunal answer, this is working for me:
extension UIViewController {func setupNavigationMultilineTitle() { guard let navigationBar = self.navigationController?.navigationBar else { return } for sview in navigationBar.subviews { for ssview in sview.subviews { guard let label = ssview as? UILabel else { break } if label.text == self.title { label.numberOfLines = 0 label.lineBreakMode = .byWordWrapping label.sizeToFit() UIView.animate(withDuration: 0.3, animations: { navigationBar.frame.size.height = 57 + label.frame.height }) } } }}
In the UIViewController:
override func viewDidLoad() { super.viewDidLoad() self.title = "This is a multiline title" setupNavigationMultilineTitle()}override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) setupNavigationMultilineTitle()}
And for setting font and color on the large title:
navigation.navigationBar.largeTitleTextAttributes = [NSAttributedStringKey.foregroundColor: .red, NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: 30)]
Get a navigation item subviews and locate UILabel from it.
Try this and see:
self.navigationController?.navigationBar.prefersLargeTitles = trueself.navigationController?.navigationItem.largeTitleDisplayMode = .automaticself.title = "This is multiline title for navigation bar"self.navigationController?.navigationBar.largeTitleTextAttributes = [ NSAttributedStringKey.foregroundColor: UIColor.black, NSAttributedStringKey.font : UIFont.preferredFont(forTextStyle: .largeTitle) ]for navItem in(self.navigationController?.navigationBar.subviews)! { for itemSubView in navItem.subviews { if let largeLabel = itemSubView as? UILabel { largeLabel.text = self.title largeLabel.numberOfLines = 0 largeLabel.lineBreakMode = .byWordWrapping } }}
Here is result:
The linebreak solution seems to be problematic when there's a back button. So instead of breaking lines, I made the label auto adjust font.
func setupLargeTitleAutoAdjustFont() { guard let navigationBar = navigationController?.navigationBar else { return } // recursively find the label func findLabel(in view: UIView) -> UILabel? { if view.subviews.count > 0 { for subview in view.subviews { if let label = findLabel(in: subview) { return label } } } return view as? UILabel } if let label = findLabel(in: navigationBar) { if label.text == self.title { label.adjustsFontSizeToFitWidth = true label.minimumScaleFactor = 0.7 } }}
Then it needs to be called in viewDidLayoutSubviews() to make sure the label can be found, and we only need to call it once:
private lazy var setupLargeTitleLabelOnce: Void = {[unowned self] in if #available(iOS 11.0, *) { self.setupLargeTitleAutoAdjustFont() }}()override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() let _ = setupLargeTitleLabelOnce}
If there's any navigationController pop event back to this controller, we need to call it again in viewDidAppear(). I haven't found a better solution for this - there's a small glitch of label font changing when coming back from a pop event:
override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) if #available(iOS 11.0, *) { setupLargeTitleAutoAdjustFont() }}