如何在导航栏中设置多行大标题?(iOS 11的新功能)

37

我正在为一个应用程序的导航栏添加一个大标题。问题是标题有点长,因此我需要在大标题中添加两行。如何在导航栏中添加包含两行的大标题呢?

这不是默认的导航栏标题!这是关于 iOS 11 中引入的大标题。所以请确保您考虑到大标题后再提出建议。谢谢

Title text truncated with 3 dots in the navigation bar


2
在一个UIView中放置一个标签,将行数设置为0,然后将该视图添加为导航栏的TitleView。问题解决了。 - Sandeep Bhandari
1
@SandeepBhandari 这个在导航栏项目变化时(如返回按钮标签长度、左/右边栏项目)是否能正常工作(像原始标题标签一样)? - meaning-matters
@meaning-matters:如果titleView与左/右栏按钮重叠,您可以始终指定其框架 :) - Sandeep Bhandari
@Richie Rich 这不是重复的,请再仔细阅读描述 :) - Jigar Thakkar
@JigarThakkar,标题和返回按钮有什么进展吗?我也遇到了同样的问题。 - Spark
显示剩余2条评论
10个回答

15

基于@krunal的答案,这对我是有效的:

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
                })
            }
        }
    }
}

在UIViewController中:

override func viewDidLoad() {
    super.viewDidLoad()
    self.title = "This is a multiline title"
    setupNavigationMultilineTitle()
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    setupNavigationMultilineTitle()
}

对于设置大标题的字体和颜色:

navigation.navigationBar.largeTitleTextAttributes = [NSAttributedStringKey.foregroundColor: .red, NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: 30)]

1
这个非常有效。当我尝试这段代码时,我确实注意到动画中有一些微小的间隙。可能是因为这里的常量“57”不够。 - Ilias Karim
1
是的,我通过根据设备更改值来修复它:let offset: CGFloat = UIDevice.current.iPhone5 ? 53 : 57 - Patricio Bravo
1
你在viewDidAppear中看到了一个小故障吗?先生,有更好的建议吗?@PatricioBravo - tryKuldeepTanwar
5
在iOS 13和Xcode 11中,这个解决方案似乎无法正常工作。 - Rodrigo Guimarães
1
无法在iOS 16上工作。 - Jalil
显示剩余3条评论

14
从导航栏项子视图中获取并定位其UILabel。
尝试一下,看看:
self.navigationController?.navigationBar.prefersLargeTitles = true
self.navigationController?.navigationItem.largeTitleDisplayMode = .automatic

self.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
         }
     }
}

这是结果:

在此输入图像描述


12
在没有返回按钮的情况下,这个很好用。你试过在有返回按钮的情况下使用吗? - Jigar Thakkar
2
我同意Jigar的观点。我有一个返回按钮,但它不起作用。当我从左侧滑动页面一点点(好像要返回)然后让它弹回原位时,它才能正常工作。 - Coltuxumab
1
我看到了和JigarThakkar以及Coltuxumab一样的行为。@PatricioBravo你的建议在我的情况下没有帮助。 - Ilias Karim
1
@IliasKarim 把我的代码放在一个新的答案里,希望能有所帮助。 - Patricio Bravo
1
有没有人遇到这样的问题,当你导航到下一页时,标题很小,栏会折叠,当你回来时,标题会切换回一行,后面跟着省略号。 - tryKuldeepTanwar
显示剩余8条评论

5
你可以尝试以下步骤:
  1. 创建自定义的 UINavigationController
  2. 将协议 UINavigationBarDelegate 添加到类定义中
  3. 覆盖函数 navigationBar(_:shouldPush:)
  4. 使用隐藏变量 item.setValue(true, forKey: "__largeTitleTwoLineMode") 激活两行模式
  5. navigationController.navigationBar.prefersLargeTitles = true

2
上帝保佑你,伙计,你救了我的命。非常好用!!! :D 但是我在ViewController中使用了navigationItem.setValue(1, forKey: "__largeTitleTwoLineMode"),所以你不需要使用UINavigationBarDelegate - Oleksandr Vaker
这真是黑魔法,@OleksandrVaker的答案完美地解决了问题! - yaozhang
作为更新,这个关键字确实有效,但由于某些原因,当新视图被推入时,大标题默认是折叠的。滚动可以显示完整的标题,但这很奇怪。我尝试了所有的方法,如sizetofit(),async,preferLargeTitle等。这只发生在跨多行的标题上。 OleksandrVaker先生,你们遇到过这个问题吗? - yaozhang
@yaozhang 实际上,这个东西在发布时不起作用。因为这是私有API密钥,苹果会拒绝您的发布申请。我通过添加一个标签来完成此操作,该标签将通过滚动进行滑动。 - Oleksandr Vaker
@OleksandrVaker 感谢您的提示!您能提供更多有关您的解决方案的详细信息吗?您是否向导航栏添加了标签?这是否提供与此API相同的视觉效果?谢谢。 - yaozhang

4

当存在后退按钮时,换行方案似乎存在问题。因此,我没有采用断行方式,而是使标签自动调整字体。

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
        }
    }
}

然后需要在viewDidLayoutSubviews()中调用它,以确保标签可以被找到,并且我们只需要调用一次:

private lazy var setupLargeTitleLabelOnce: Void = {[unowned self] in
    if #available(iOS 11.0, *) {
        self.setupLargeTitleAutoAdjustFont()
    }
}()

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    let _ = setupLargeTitleLabelOnce
}

如果有任何navigationController pop事件回到此控制器,我们需要在viewDidAppear()中再次调用它。 我还没有找到更好的解决方案- 当从pop事件返回时,标签字体会有一个小故障:
override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    if #available(iOS 11.0, *) {
        setupLargeTitleAutoAdjustFont()
    }
}

1

(编辑于7月13日:我注意到这个解决方案不支持scrollView,所以现在我正在研究)

我在Swift5上找到了一个完美的解决方案。

但是抱歉我的英语很差,因为我是日本学生。

如果是两行的情况 如果是三行的情况

首先,在viewDidLoad中正常设置大标题的导航设置。

//Set largeTitle
navigationItem.largeTitleDisplayMode = .automatic
navigationController?.navigationBar.prefersLargeTitles = true
navigationController?.navigationBar.largeTitleTextAttributes = [.font: UIFont.systemFont(ofSize: (fontSize + margin) * numberOfLines)]//ex) fontSize=26, margin=5, numberOfLines=2
        
//Set title
title = "multiple large\ntitle is working!"

这个解决方案最重要的一点是,largeTitleTextAttributes 中的字体大小应该等于实际字体大小(+边距)乘以行数。

描述图片

因为导航栏属性的默认规范可能只能显示一行的大标题。
尽管如此,我确实注意到,在标签设置(子视图的子视图的标签)直接设置时,它可以在导航栏属性中显示任意数量的行
因此,我们应该在导航栏属性中设置大号字体,在标签(导航栏的子视图的子视图)中设置小号字体,并考虑边距。
像这样在 viewDidAppear 中直接进行标签设置:
//Find label
navigationController?.navigationBar.subviews.forEach({ subview in
        subview.subviews.forEach { subsubview in
        guard let label: UILabel = subsubview as? UILabel else { return }
        //Label settings on direct.
        label.text = title
        label.font = UIFont.systemFont(ofSize: fontSize)
        label.numberOfLines = 0
        label.lineBreakMode = .byWordWrapping
        label.sizeToFit()
    }
})

因此,简而言之,最小代码的解决方案如下:
import UIKit

class ViewController: UIViewController {
    
    private let fontSize: CGFloat = 26, margin: CGFloat = 5
    private let numberOfLines: CGFloat = 2

    override func viewDidLoad() {
        super.viewDidLoad()

        setUpNavigation()
    }
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        setMultipleLargeTitle()
    }
    private func setUpNavigation() {
        //Set largeTitle
        navigationItem.largeTitleDisplayMode = .automatic
        navigationController?.navigationBar.prefersLargeTitles = true
        navigationController?.navigationBar.largeTitleTextAttributes = [.font: UIFont.systemFont(ofSize: (fontSize + margin) * numberOfLines)]
        
        //Set title
        title = "multiple large\ntitle is working!"
    }
    private func setMultipleLargeTitle() {
        //Find label
        navigationController?.navigationBar.subviews.forEach({ subview in
            subview.subviews.forEach { subsubview in
                guard let label: UILabel = subsubview as? UILabel else { return }
                //Label settings on direct.
                label.text = title
                label.font = UIFont.systemFont(ofSize: fontSize)
                label.numberOfLines = 0
                label.lineBreakMode = .byWordWrapping
                label.sizeToFit()
            }
        })
    }
}

谢谢阅读 :)

我无法使其正常工作,我的标题只是变大了(因为字体大小),而没有分成多行,您是否缺少某些实现,或者在scrollview的调查方面有任何消息? - MXNMike

0

SWIFT 5 这个UIViewController扩展帮了我。我的情况是混合启用和禁用大标题所以首先启用大标题,然后调用这个方法。在viewDidLoad中调用它,我发现通过滑动并释放触摸来窥视回退时出现了错误,因为某种原因当前导航标题变成了上一个导航标题。

    extension UIViewController {

/// Sets two lines for navigation title if needed
/// - Parameter animated: used for changing titles on one controller,in that case animation is off
func multilineNavTitle(_ animated:Bool = true) {
    
    if animated {
        // setting initial state for animation of title to look more native
        self.navigationController?.navigationBar.transform = CGAffineTransform.init(translationX: .screenWidth/2, y: 0)
        self.navigationController?.navigationBar.alpha = 0
    }
    
    //Checks if two lines is needed
    if self.navigationItem.title?.forTwoLines() ?? false {
        
        // enabling multiline
        navigationItem.setValue(true,
                                forKey: "__largeTitleTwoLineMode")
    } else {
        
        // disabling multiline
        navigationItem.setValue(false,
                                forKey: "__largeTitleTwoLineMode")
    }
    
    // laying out title without animation
    UIView.performWithoutAnimation {
        self.navigationController?.navigationBar.layoutSubviews()
        self.navigationController?.view.setNeedsLayout()
        self.navigationController?.view.layoutIfNeeded()
    }
    
    if animated {
        //animating title
        UIView.animate(withDuration: 0.3) {
            self.navigationController?.navigationBar.transform = CGAffineTransform.identity
            self.navigationController?.navigationBar.alpha = 1
        }
    }

}


}


fileprivate extension String {

/// Checks if navigation title is wider than label frame
/// - Returns: `TRUE` if title cannot fit in one line of navigation title label
func forTwoLines() -> Bool {
    
    let fontAttributes = [NSAttributedString.Key.font: SomeFont]
    let size = self.size(withAttributes: fontAttributes)
    return size.width > CGFloat.screenWidth - 40 //in my case
    
}


}

0
如果有人在寻找“标题标签”而不是大标题,则以下代码可行。
Swift 5.X
func setMultilineNavigationBar(topText:  String, bottomText : String) {
     let topTxt = NSLocalizedString(topText, comment: "")
     let bottomTxt = NSLocalizedString(bottomText, comment: "")
        
     let titleParameters = [NSAttributedString.Key.foregroundColor : UIColor.white,
                               NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16, weight: .semibold)]
     let subtitleParameters = [NSAttributedString.Key.foregroundColor : UIColor.white,
                                  NSAttributedString.Key.font : UIFont.systemFont(ofSize: 13, weight: .regular)]
        
     let title:NSMutableAttributedString = NSMutableAttributedString(string: topTxt, attributes: titleParameters)
     let subtitle:NSAttributedString = NSAttributedString(string: bottomTxt, attributes: subtitleParameters)
        
     title.append(NSAttributedString(string: "\n"))
     title.append(subtitle)
        
     let size = title.size()
        
     let width = size.width
     guard let height = navigationController?.navigationBar.frame.size.height else {return}
        
      let titleLabel = UILabel(frame: CGRect.init(x: 0, y: 0, width: width, height: height))
      titleLabel.attributedText = title
      titleLabel.numberOfLines = 0
      titleLabel.textAlignment = .center
      self.navigationItem.titleView = titleLabel 
    }

1
这只是在默认标题视图中放置一个多行标签。问题是如何拥有一个多行标题视图。 - Sherwin Zadeh

0

Swift 4:即使句子很短也可以多行显示

title = "You're \nWelcome"

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
         }
     }
}

Proof


请告诉我在哪里添加这段代码,稍微解释一下,这样我就可以使用它了。谢谢。 - Ravindra_Bhati
如果你有按钮,这个方法就不起作用了,对我来说它只是切掉了第二部分,而且它也不存在。 - Async-
如果您正在使用SwiftUI HostingView,这也无法正常工作。 - WikipediaBrown

-1
viewController.navigationItem
    .setValuesForKeys(["__largeTitleTwoLineMode": true])

警告:此方法不适用于旧的操作系统版本。

这似乎会破坏下拉刷新功能,至少在 iOS 16.1 上是如此。 - MarkFisher
这个需要的最低操作系统版本是多少?有人知道吗? - husharoonie

-1
只需创建一个自定义导航控制器,其余的操作将由操作系统自行处理。
class MyNavigationViewController: UINavigationController {

    override func viewDidLoad() {
        super.viewDidLoad()
        navigationBar.delegate = self 
    }

}

extension MyNavigationViewController: UINavigationBarDelegate {
    func navigationBar(_ navigationBar: UINavigationBar, shouldPush item: UINavigationItem) -> Bool {
        item.setValuesForKeys([
            "__largeTitleTwoLineMode": true
        ])
        return true
    }
}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接