How to set multi line Large title in navigation bar? ( New feature of iOS 11) - ios

I am in process of adding large title in navigation bar in one of the application. The issue is title is little long so I will require to add two lines in large title. How can I add large title with two lines in navigation bar?
This is not about default navigation bar title! This is about large title which is introduced in iOS 11. So make sure you add suggestions by considering large title. Thanks

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

You could try:
Create a custom UINavigationController
Add the protocol UINavigationBarDelegate to the class definition
Override the function navigationBar(_:shouldPush:)
Activate two lines mode using hidden variable item.setValue(true, forKey: "__largeTitleTwoLineMode")
Make navigationController.navigationBar.prefersLargeTitles = true

(Edit 7/13: I notice that this solution is not support scrollView, so now I'm in research)
I found a perfect solution on Swift5
but sorry for my poor English because I'm Japanese🇯🇵Student.
In case of 2 lines In case of 3 lines
At first, set navigation settings for largeTitle normally in 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!"
It is most important point of this solution that font-size at largeTitleTextAttributes equals actual font-size(+margin) multiplied by number of lines.
Description image
Because, default specification of navigationBar attributes may be able to display only 1 line largeTitle.
Although, somehow, I did notice that in case of label-settings(the label which subview of subview of navigationBar) on direct, it can display any number of lines in 1 line of in case of navigationBar attributes.
So, we should do set big font in navigationbar attributes, and set small font in the label(subview of subview of navigationBar), and take into consideration the margins.
Do label settings direct in viewDidAppear like this:
//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()
}
})
Therefore, in short, the solution at minimum code is given like this:
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()
}
})
}
}
thank you for reading :)

Swift 4 : Multi line even though the sentence is only short
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
}
}
}

If anyone looking for Title Lable Not Large Title, then below code is working.
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
}

SWIFT 5 This UIViewController extension helped me. Scenario that I have is mixed with enabling and disabling large titles so FIRST ENABLE large title and then call this method. Call it in viewDidLoad, I have found bug with peeking back with swipe and then releasing touch, for some reason current navigation title become previous navigation title
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
}
}

Just create a custom navigation controller. Rest will be handled by the OS itself
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
}
}

viewController.navigationItem
.setValuesForKeys(["__largeTitleTwoLineMode": true])
WARNING: This method does not work on older OS versions

Related

swift i want to change search bar background colour white with black border

here the screenshot that I want output please check
I want the result like this search bar with background color white and black border
here my code is
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
for subView in searchBar.subviews {
if !subView.subviews.contains(where: { $0 as? UITextField != nil }) { continue }
guard let textField = subView.subviews.first(where: { $0 as? UITextField != nil }) as? UITextField else { return }
let placeholder = NSMutableAttributedString(
string: "Search",
attributes: [.font: UIFont(name: "Helvetica", size: 15.0)!,
.foregroundColor: UIColor.gray
])
textField.attributedPlaceholder = placeholder
textField.layer.cornerRadius = textField.frame.size.height / 2
textField.layer.masksToBounds = true
textField.textColor = .black
textField.backgroundColor = .white
}
this is my output screenshot please check it
I am unable to set search bar, please suggest me
Good day! If I understand you correctly, then this may solve your problem. Extension for UISearchBar (Swift 5):
import UIKit
extension UISearchBar {
func setupSearchBar(background: UIColor = .white, inputText: UIColor = .black, placeholderText: UIColor = .gray, image: UIColor = .black) {
self.searchBarStyle = .minimal
self.barStyle = .default
// IOS 12 and lower:
for view in self.subviews {
for subview in view.subviews {
if subview is UITextField {
if let textField: UITextField = subview as? UITextField {
// Background Color
textField.backgroundColor = background
// Text Color
textField.textColor = inputText
// Placeholder Color
textField.attributedPlaceholder = NSAttributedString(string: textField.placeholder ?? "", attributes: [NSAttributedString.Key.foregroundColor : placeholderText])
// Default Image Color
if let leftView = textField.leftView as? UIImageView {
leftView.image = leftView.image?.withRenderingMode(.alwaysTemplate)
leftView.tintColor = image
}
let backgroundView = textField.subviews.first
backgroundView?.backgroundColor = background
backgroundView?.layer.cornerRadius = 10.5
backgroundView?.layer.masksToBounds = true
}
}
}
}
// IOS 13 only:
if let textField = self.value(forKey: "searchField") as? UITextField {
// Background Color
textField.backgroundColor = background
// Text Color
textField.textColor = inputText
// Placeholder Color
textField.attributedPlaceholder = NSAttributedString(string: textField.placeholder ?? "", attributes: [NSAttributedString.Key.foregroundColor : placeholderText])
// Default Image Color
if let leftView = textField.leftView as? UIImageView {
leftView.image = leftView.image?.withRenderingMode(.alwaysTemplate)
leftView.tintColor = image
}
}
}
}
Also, you can try to do it with an image.
The way to do this only using Apple APIs is to create an image and use setSearchFieldBackgroundImage:
self.searchBar.setSearchFieldBackgroundImage(UIImage(named: "SearchFieldBackground"), for: UIControlState.normal)
Source: Cannot change search bar background color

UISearchbar UI in iOS 12 and 13 are different and not correct

The UI in my searchbar is behaving differently than expected.
I want it to look like this:
In iOS 13 it looks like this:
And in iOS 12 it looks like this:
I am configuring the searchbar in ViewDidLoad with this:
guard let searchField = searchBar.value(forKey: "searchField") as? UITextField else { return }
searchBar.backgroundColor = .red
searchField.backgroundColor = UIColor.black.withAlphaComponent(0.3)
searchField.textColor = .white
searchField.leftView?.tintColor = .white
searchField.attributedPlaceholder = NSAttributedString(string: "Search", attributes: [NSAttributedString.Key.foregroundColor: UIColor.white])
I've tried to create your searchBar but I didn't test it. You can find answers to your questions from the code snippet. You need to add the UISearchBar extension also to your project to use some methods in the snippet.
searchBar.tintColor = UIColor.white
searchBar.searchBarStyle = .default
searchBar.placeholder = "Search"
searchBar.setTextFieldPlaceholderColor(color: UIColor.white)
searchBar.backgroundColor = UIColor.red
searchBar.setImage(UIImage(named:"searchIcon"), for: .search, state: .normal)
if #available(iOS 13.0, *) {
searchBar.searchTextField.backgroundColor = UIColor.black.withAlphaComponent(0.3)
searchBar.setBackgroundImage(UIImage(), for: .any, barMetrics: .default)
searchBar.searchTextField.textColor = UIColor.white
searchBar.searchTextField.font = UIFont.systemFont(ofSize: 12)
} else {
searchBar.removeBackgroundImageView()
searchBar.setTextFieldBackground(color: UIColor.black.withAlphaComponent(0.3))
}
UISearchBar extension
extension UISearchBar {
//use for iOS 12 and below
func removeBackgroundImageView(){
if let view:UIView = self.subviews.first {
for curr in view.subviews {
guard let searchBarBackgroundClass = NSClassFromString("UISearchBarBackground") else {
return
}
if curr.isKind(of:searchBarBackgroundClass){
if let imageView = curr as? UIImageView{
imageView.removeFromSuperview()
break
}
}
}
}
}
func setTextFieldBackground(color : UIColor) {
for subView in self.subviews {
for subView1 in subView.subviews {
if subView1.isKind(of: UITextField.self) {
subView1.backgroundColor = color
(subView1 as? UITextField)?.font = UIFont.systemFont(ofSize: 12)
}
}
}
}
func setTextFieldPlaceholderColor(color : UIColor) {
for subView in self.subviews {
for subView1 in subView.subviews {
if subView1.isKind(of: UITextField.self) {
if let placeholder = (subView1 as! UITextField).placeholder {
(subView1 as! UITextField).attributedPlaceholder = NSAttributedString(string:placeholder,attributes: [NSAttributedString.Key.foregroundColor: color])
}
}
}
}
}
}

UISearchBar color in NavigationBar

I feel like I’m taking crazy pills. I want to change the color of the textfield within a UISearchBar, which is hosted in a UINavigationBar. The problem I’m having is that the UISearchBar gets the color of the UINavigationBar but the TextField doesn’t change color. I’ve tried several solutions here, but none of them seem to solve the issue. Any ideas how to change the TextField’s color?
I know there are other questions out there similar to this but I’ve tried every possible solution I’ve come across and haven’t found one that solves my issue.
Current Code:
let sc = UISearchController(searchResultsController: nil)
sc.delegate = self
let scb = sc.searchBar
scb.tintColor = UIColor.green
scb.barTintColor = UIColor.yellow
if let textfield = scb.value(forKey: "searchField") as? UITextField {
textfield.textColor = UIColor.blue
if let backgroundview = textfield.subviews.first {
// Background color
backgroundview.backgroundColor = UIColor.red
// Rounded corner
backgroundview.layer.cornerRadius = 5;
backgroundview.clipsToBounds = true;
}
}
navigationController?.navigationBar.barTintColor = UIColor.blue
navigationItem.searchController = sc
navigationItem.hidesSearchBarWhenScrolling = false
I’ve created a sample project to show it in action. Any help or guidance would be greatly appreciated!
Maybe the best solution is for creating an extension for UISearchBar if you want to use these settings in more controllers. Here are some example.
extension UISearchBar {
private func getViewElement<T>(type: T.Type) -> T? {
let svs = subviews.flatMap { $0.subviews }
guard let element = (svs.filter { $0 is T }).first as? T else { return nil }
return element
}
func getSearchBarTextField() -> UITextField? {
return getViewElement(type: UITextField.self)
}
func setTextColor(color: UIColor) {
if let textField = getSearchBarTextField() {
textField.textColor = color
}
}
func setTextFieldColor(color: UIColor) {
if let textField = getViewElement(type: UITextField.self) {
switch searchBarStyle {
case .minimal:
textField.layer.backgroundColor = color.cgColor
textField.layer.cornerRadius = 6
case .prominent, .default:
textField.backgroundColor = color
#unknown default:
print("something")
}
}
}
func setPlaceholderTextColor(color: UIColor) {
if let textField = getSearchBarTextField() {
textField.attributedPlaceholder = NSAttributedString(string: self.placeholder != nil ? self.placeholder! : "", attributes: [NSAttributedString.Key.foregroundColor: color])
}
}
func setTextFieldClearButtonColor(color: UIColor) {
if let textField = getSearchBarTextField() {
let button = textField.value(forKey: "clearButton") as! UIButton
if let image = button.imageView?.image {
button.setImage(image.transform(withNewColor: color), for: .normal)
}
}
}
func setSearchImageColor(color: UIColor) {
if let imageView = getSearchBarTextField()?.leftView as? UIImageView {
imageView.image = imageView.image?.transform(withNewColor: color)
}
}
}
Update:
Change navigationItem.searchController = sc to navigationItem.titleView = sc.searchBar.

Can't change UINavigationBar prompt color and font

I can change title appearance and its work very well but I can't change prompt font and color
Try this code in viewWillAppear(_ animated: Bool):
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.font = UIFont.systemFont(ofSize: 30)
}
}

My navigation bar's large title is too wide. How to fix that?

I am using navigation controller, and I've set to true its navigation bar's prefersLargeTitle property. Everything works fine, but when the text of my title becomes too big, it doesn't fit in space. Here how it looks:
Is it possible to somehow make the title (while the navigation bar's prefersLargeTitle property is set to true) dynamically adjust its font size, and if it is so, how to achieve that?
This is the workaround that I found
override func viewDidLoad() {
super.viewDidLoad()
title = yourTitle
adjustLargeTitleSize()
}
extension UIViewController {
func adjustLargeTitleSize() {
guard let title = title, #available(iOS 11.0, *) else { return }
let maxWidth = UIScreen.main.bounds.size.width - 60
var fontSize = UIFont.preferredFont(forTextStyle: .largeTitle).pointSize
var width = title.size(withAttributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: fontSize)]).width
while width > maxWidth {
fontSize -= 1
width = title.size(withAttributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: fontSize)]).width
}
navigationController?.navigationBar.largeTitleTextAttributes =
[NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: fontSize)
]
}
}
All you need is:
UILabel.appearance(whenContainedInInstancesOf: [UINavigationBar.self]).adjustsFontSizeToFitWidth = true
Working also with SwiftUI on iOS 15.
This question is somewhat answered here: How to resize Title in a navigation bar dynamically.
self.title = "Your TiTle Text"
let tlabel = UILabel(frame: CGRectMake(0, 0, 200, 40))
tlabel.text = self.title
tlabel.textColor = UIColor.whiteColor()
tlabel.font = UIFont(name: "Helvetica-Bold", size: 30.0)
tlabel.backgroundColor = UIColor.clearColor()
tlabel.adjustsFontSizeToFitWidth = true
self.navigationItem.titleView = tlabel
That being said, this is slightly different, in that you have the prefersLargeTitle property set.
Now, I am not sure whether the tlabel.adjustsFontSizeToFitWidth = true overrides the prefersLargeTitle property, but try it out and see if it works. There is also some additional information regarding navigation item large titles here: https://developer.apple.com/documentation/uikit/uinavigationitem/2909056-largetitledisplaymode. Hope this helps.
Tested for iOS12 ~ iOS14
extension UINavigationController {
func adjustFontSize(with title: String) {
let insetToEdge: CGFloat = 16
let maxWidth = navigationBar.bounds.width - insetToEdge - insetToEdge
let largeTitleFont = UIFont.preferredFont(forTextStyle: .largeTitle)
var fontSize = largeTitleFont.pointSize
var largeTitleTextAttributes: [NSAttributedString.Key: Any] = [:]
var largeTitleSize: CGSize
if #available(iOS 13.0, *) {
largeTitleSize = NSAttributedString(
string: title,
attributes: navigationBar.standardAppearance.largeTitleTextAttributes)
.size()
} else {
largeTitleTextAttributes = [NSAttributedString.Key.font: largeTitleFont]
largeTitleSize = NSAttributedString(
string: title,
attributes: largeTitleTextAttributes)
.size()
}
guard largeTitleSize.width > maxWidth else { return }
while largeTitleSize.width > maxWidth {
fontSize -= 1
if #available(iOS 13.0, *) {
largeTitleTextAttributes = navigationBar.standardAppearance.largeTitleTextAttributes
}
largeTitleTextAttributes[NSAttributedString.Key.font] = UIFont.BO.font(
ofSize: fontSize,
weight: .semiBold)
largeTitleSize = NSAttributedString(
string: title,
attributes: largeTitleTextAttributes)
.size()
}
if #available(iOS 13.0, *) {
navigationBar.standardAppearance.largeTitleTextAttributes = largeTitleTextAttributes
} else {
navigationBar.largeTitleTextAttributes = largeTitleTextAttributes
}
}
}
call from viewDidLoad()
Made an edit to #vicente.fava answer - this works great.
self.title = longTitle
self.navigationController?.navigationBar.prefersLargeTitles = true
adjustLargeTitleSize()
extension UIViewController {
func adjustLargeTitleSize() {
guard let title = title, #available(iOS 11.0, *) else { return }
let maxWidth = UIScreen.main.bounds.size.width - 60
var fontSize = UIFont.preferredFont(forTextStyle: .largeTitle).pointSize
var width = title.size(withAttributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: fontSize)]).width
while width > maxWidth {
fontSize -= 1
width = title.size(withAttributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: fontSize)]).width
}
navigationController?.navigationBar.largeTitleTextAttributes =
[NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: fontSize)
]
}
}

Resources