Add subtitle under the title in navigation bar controller in Xcode - swift

So I'm wanting to add a "subtitle" under the title in the navigation bar in navigation controller.
Mostly everything I look up so far wants me to use CGRect. I don't know a whole lot what that is and it sounds like its wanting me to create an entire new view which is not what I am wanting to do.
My question is, is there a dot method to adding a subtitle view easily?
The closest thing I found was posted on stack overflow and here is the link:
Create a subtitle in navigationbar
Apparently last year this worked but now I am getting errors and it's in my viewDidLoad...
I tried this:
self.navigationController?.navigationItem.prompt = "Subtitle Here"
It's the only thing that won't show any errors but still doesn't work. It literally does nothing. At least nothing visible at run time.
On a side note, swift is preferred. Thanks!

Here is my version using a stack view on an extension.
extension UINavigationItem {
func setTitle(title:String, subtitle:String) {
let one = UILabel()
one.text = title
one.font = UIFont.systemFont(ofSize: 17)
one.sizeToFit()
let two = UILabel()
two.text = subtitle
two.font = UIFont.systemFont(ofSize: 12)
two.textAlignment = .center
two.sizeToFit()
let stackView = UIStackView(arrangedSubviews: [one, two])
stackView.distribution = .equalCentering
stackView.axis = .vertical
stackView.alignment = .center
let width = max(one.frame.size.width, two.frame.size.width)
stackView.frame = CGRect(x: 0, y: 0, width: width, height: 35)
one.sizeToFit()
two.sizeToFit()
self.titleView = stackView
}
}

Though there is a solution but it has some known issues
Solution is writing a function like this
func setTitle(title:String, subtitle:String) -> UIView {
let titleLabel = UILabel(frame: CGRectMake(0, -2, 0, 0))
titleLabel.backgroundColor = UIColor.clearColor()
titleLabel.textColor = UIColor.grayColor()
titleLabel.font = UIFont.boldSystemFontOfSize(17)
titleLabel.text = title
titleLabel.sizeToFit()
let subtitleLabel = UILabel(frame: CGRectMake(0, 18, 0, 0))
subtitleLabel.backgroundColor = UIColor.clearColor()
subtitleLabel.textColor = UIColor.blackColor()
subtitleLabel.font = UIFont.systemFontOfSize(12)
subtitleLabel.text = subtitle
subtitleLabel.sizeToFit()
let titleView = UIView(frame: CGRectMake(0, 0, max(titleLabel.frame.size.width, subtitleLabel.frame.size.width), 30))
titleView.addSubview(titleLabel)
titleView.addSubview(subtitleLabel)
let widthDiff = subtitleLabel.frame.size.width - titleLabel.frame.size.width
if widthDiff < 0 {
let newX = widthDiff / 2
subtitleLabel.frame.origin.x = abs(newX)
} else {
let newX = widthDiff / 2
titleLabel.frame.origin.x = newX
}
return titleView
}
Using this function for custom navigation title view in viewDidLoad
self.navigationItem.titleView = setTitle("Title", subtitle: "SubTitle")
Only known issue is that if subtitle becomes very large than the misplacement occurs.
Final Outcome
Source: https://gist.github.com/nazywamsiepawel/0166e8a71d74e96c7898

#iosjillian's Swift 4 extension works great, adding a bit more to honor the bar's appearance and user font preferences:
import UIKit
extension UINavigationItem {
func setTitle(_ title: String, subtitle: String) {
let appearance = UINavigationBar.appearance()
let textColor = appearance.titleTextAttributes?[NSAttributedString.Key.foregroundColor] as? UIColor ?? .black
let titleLabel = UILabel()
titleLabel.text = title
titleLabel.font = .preferredFont(forTextStyle: UIFont.TextStyle.headline)
titleLabel.textColor = textColor
let subtitleLabel = UILabel()
subtitleLabel.text = subtitle
subtitleLabel.font = .preferredFont(forTextStyle: UIFont.TextStyle.subheadline)
subtitleLabel.textColor = textColor.withAlphaComponent(0.75)
let stackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel])
stackView.distribution = .equalCentering
stackView.alignment = .center
stackView.axis = .vertical
self.titleView = stackView
}
}

Thanks a lot for your answer! #RajanMaheshwari
Your coding worked perfectly except the if statement you made with the widthDiff..
I adjusted it a little bit and everything worked smoothly.
if widthDiff < 0 {
let newX = widthDiff / 2
subtitleLabel.frame.origin.x = abs(newX)
} else {
let newX = widthDiff / 2
titleLabel.frame.origin.x = newX
}
Thanks again for your response!

I really liked #user2325031's answer, but found that sizing the labels to fit and setting the frame wasn't needed. I also set the stackView's alignment to .center per #GerardoMR's suggestion.
extension UINavigationItem {
func setTitle(_ title: String, subtitle: String) {
let titleLabel = UILabel()
titleLabel.text = title
titleLabel.font = .systemFont(ofSize: 17.0)
titleLabel.textColor = .black
let subtitleLabel = UILabel()
subtitleLabel.text = subtitle
subtitleLabel.font = .systemFont(ofSize: 12.0)
subtitleLabel.textColor = .gray
let stackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel])
stackView.distribution = .equalCentering
stackView.alignment = .center
stackView.axis = .vertical
self.titleView = stackView
}
}

In case anyone looking for Objective-C code of the above mentioned solution:
UILabel *title = [[UILabel alloc]init];
UILabel *subtitle = [[UILabel alloc]init];
[title setFont:[UIFont systemFontOfSize:12]];
[title setTextColor:[UIColor whiteColor]];
[title setFont:[UIFont systemFontOfSize:17]];
[title sizeToFit];
title.text = #"Title";
[subtitle setTextColor:[UIColor whiteColor]];
[subtitle setFont:[UIFont systemFontOfSize:12]];
[subtitle setTextAlignment:NSTextAlignmentCenter];
[subtitle sizeToFit];
subtitle.text = #"Subtitle Title";
UIStackView *stackVw = [[UIStackView alloc]initWithArrangedSubviews:#[title,subtitle]];
stackVw.distribution = UIStackViewDistributionEqualCentering;
stackVw.axis = UILayoutConstraintAxisVertical;
stackVw.alignment =UIStackViewAlignmentCenter;
[stackVw setFrame:CGRectMake(0, 0, MAX(title.frame.size.width, subtitle.frame.size.width), 35)];
self.navigationItem.titleView = stackVw;

Thanks for the answer #RajanMaheshwari
If anyone is having the issue where the title becomes misaligned when the subtitle text is longer than the title text, I added the following code to the Rajan's answer above just below where the subtitleLabel is instantiated:
// Fix incorrect width bug
if (subtitleLabel.frame.size.width > titleLabel.frame.size.width) {
var titleFrame = titleLabel.frame
titleFrame.size.width = subtitleLabel.frame.size.width
titleLabel.frame = titleFrame
titleLabel.textAlignment = .center
}
Hope this helps someone who encountered the same issue as me

Another solution, using only one label and NSAttributedString to differentiate between title and subtitle (with different font sizes, weights, colors, etc.) instead. Removes the problem of different label alignment.
extension UIViewController {
func setTitle(_ title: String, subtitle: String) {
let rect = CGRect(x: 0, y: 0, width: 400, height: 50)
let titleSize: CGFloat = 20 // adjust as needed
let subtitleSize: CGFloat = 15
let label = UILabel(frame: rect)
label.backgroundColor = .clear
label.numberOfLines = 2
label.textAlignment = .center
label.textColor = .black
let text = NSMutableAttributedString()
text.append(NSAttributedString(string: title, attributes: [.font : UIFont.boldSystemFont(ofSize: titleSize)]))
text.append(NSAttributedString(string: "\n\(subtitle)", attributes: [.font : UIFont.systemFont(ofSize: subtitleSize)]))
label.attributedText = text
self.navigationItem.titleView = label
}
}
Custom titleView based in part on https://stackoverflow.com/a/34298491/3918865

Swift 4:
import UIKit
class NavigationTitleView: UIView {
private var contentStackView = UIStackView()
private var titleLabel = UILabel()
private var subTitleLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
viewConfig()
addViewsConfig()
layoutViewsConfig()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func set(title: String, subTitle: String){
self.titleLabel.text = title
self.subTitleLabel.text = subTitle
}
private func viewConfig() {
contentStackView.axis = .vertical
contentStackView.alignment = .center
contentStackView.distribution = .fill
contentStackView.spacing = 5
self.backgroundColor = .clear
self.titleLabel.textColor = .white
self.self.subTitleLabel.textColor = .white
}
private func addViewsConfig() {
contentStackView.addArrangedSubview(subTitleLabel)
contentStackView.addArrangedSubview(titleLabel)
self.addSubview(contentStackView)
}
private func layoutViewsConfig(){
contentStackView.translatesAutoresizingMaskIntoConstraints = false
contentStackView.centerXAnchor.constraint(equalTo: self.centerXAnchor, constant: 0.0).isActive = true
contentStackView.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 0.0).isActive = true
}
}
Use:
import UIKit
class ViewController: UIViewController {
private var navigationTitleView = NavigationTitleView()
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.titleView = navigationTitleView
navigationTitleView.set(title: "title", subTitle: "subTitle")
}
}

Easy fix for iOS 16. Following the #iosjillian's / #Dan's approx, calling layoutSubviews() on stack view does the trick.
extension UINavigationItem {
func setTitle(_ title: String, subtitle: String) {
let textColor = getTextColor()
let titleLabel = UILabel()
titleLabel.text = title
titleLabel.font = .preferredFont(forTextStyle: UIFont.TextStyle.headline)
titleLabel.textColor = textColor
let subtitleLabel = UILabel()
subtitleLabel.text = subtitle
subtitleLabel.font = .preferredFont(forTextStyle: UIFont.TextStyle.subheadline)
subtitleLabel.textColor = textColor.withAlphaComponent(0.75)
let stackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel])
stackView.distribution = .equalCentering
stackView.alignment = .center
stackView.axis = .vertical
stackView.layoutSubviews()
self.titleView = stackView
}
}

Working iOS 16 solution. Swift 5.7
With SnapKit library. If you are not using SnapKit lib, just make both views (titleLabel and subtitleLabel) translatesAutoresizingMaskIntoConstraints = false and replace SnapKit Constraints with native constraints.
func setTitle(title: String, subtitle: String, view: UIView) -> UIView {
let appearance = UINavigationBar.appearance()
let titleColor = appearance.titleTextAttributes?[NSAttributedString.Key.foregroundColor] as? UIColor ?? .black
let titleLabel = UILabel()
titleLabel.backgroundColor = UIColor.clear
titleLabel.textColor = titleColor
titleLabel.adjustsFontSizeToFitWidth = false
titleLabel.font = .preferredFont(forTextStyle: UIFont.TextStyle.headline)
titleLabel.lineBreakMode = .byTruncatingTail
titleLabel.textAlignment = .center
titleLabel.text = title
let subtitleLabel = UILabel()
subtitleLabel.backgroundColor = UIColor.clear
subtitleLabel.textColor = UIColor.init(hexString: "#808890")
subtitleLabel.adjustsFontSizeToFitWidth = false
subtitleLabel.lineBreakMode = .byTruncatingTail
subtitleLabel.textAlignment = .center
subtitleLabel.font = UIFont.systemFont(ofSize: 11)
subtitleLabel.text = subtitle
let titleView = UIView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 30))
titleView.addSubview(titleLabel)
titleView.addSubview(subtitleLabel)
titleLabel.snp.makeConstraints { make in
make.horizontalEdges.equalToSuperview()
make.top.equalToSuperview().offset(-20)
make.height.equalTo(20)
}
subtitleLabel.snp.makeConstraints { make in
make.horizontalEdges.equalToSuperview()
make.top.equalTo(titleLabel.snp.bottom).offset(4)
make.height.equalTo(10)
}
return titleView
}

Related

How to display image view amongst stack view

I am trying to display an image view to the right side of Root stack view. I have set the size and width but for some reason it’s not showing my guess is because the rootstock is in a vertical position. If I put the rootstock view in a horizontal position, the whole thing scatters. How can I fix this? Image below show how it looks like
This how it look like if stack view is in horizontal position.
class UserCell: UIView {
var rootStack = UIStackView()
var userInfoStackView = UIStackView()
var subtitleStackView = UIStackView()
var dateInfoStackView = UIStackView()
var nameLabel = UILabel()
var compCode = UILabel()
var captionLabel = UILabel()
var dateLabel2 = UILabel()
var imageView = UIImageView()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
setUPViews()
addComponents()
layoutComponents()
}
required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
private func setUPViews(){
rootStack.translatesAutoresizingMaskIntoConstraints = false
rootStack.alignment = .center
rootStack.axis = .vertical
rootStack.alignment = .leading
rootStack.spacing = 3
subtitleStackView.translatesAutoresizingMaskIntoConstraints = false
subtitleStackView.spacing = 2
dateInfoStackView.translatesAutoresizingMaskIntoConstraints = false
dateInfoStackView.spacing = 2
nameLabel.font = UIFont.systemFont(ofSize: 20, weight: .bold)
nameLabel.text = "Rolls Royce"
compCode.font = UIFont.systemFont(ofSize: 20, weight: .semibold)
compCode.text = "(RTYD8NTV001)"
captionLabel.font = UIFont.systemFont(ofSize: 9, weight: .semibold)
captionLabel.textColor = .darkGray
captionLabel.text = "Best Customer Since 07/01/2019"
dateLabel2.font = UIFont.systemFont(ofSize: 13, weight: .bold)
dateLabel2.text = "July 2022"
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.image = UIImage(systemName: "print")
imageView.layer.cornerRadius = 20
imageView.clipsToBounds = true
}
private func addComponents() {
rootStack.addArrangedSubview(imageView)
userInfoStackView.addArrangedSubview(nameLabel)
userInfoStackView.addArrangedSubview(compCode)
subtitleStackView.addArrangedSubview(captionLabel)
dateInfoStackView.addArrangedSubview(dateLabel2)
dateInfoStackView.addArrangedSubview(imageView)
addSubview(rootStack)
}
private func layoutComponents() {
rootStack.addArrangedSubview(userInfoStackView)
rootStack.addArrangedSubview(subtitleStackView)
rootStack.addArrangedSubview(dateInfoStackView)
rootStack.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}
imageView.snp.makeConstraints { (make) in
make.width.height.equalTo(40)
}
}
}
let userCell = UserCell(frame: CGRect(x: 0, y: 0, width: 500, height: 60))
PlaygroundPage.current.liveView = userCell

Access and update label.text or other UILabel properties inside a stackview programatically in Swift

I created a stackview with labels programatically in Swift. However, I was trying to find out how i can update the labels programmatically? (I did not use storyboard or IBOutlets)
let LabelStack: UIStackView = {
let label1: UILabel = {
let label = UILabel()
label.text = "Label 1"
label.font = .systemFont(ofSize: 14, weight: .bold)
label.numberOfLines = 0
label.backgroundColor = .clear
label.textAlignment = .left
label.sizeToFit()
return label
}()
let label2: UILabel = {
let label = UILabel()
label.text = "Label 2"
label.font = .systemFont(ofSize: 14, weight: .bold)
label.numberOfLines = 0
label.backgroundColor = .clear
label.textAlignment = .left
label.sizeToFit()
return label
}()
let stack = UIStackView(arrangedSubviews: [label1, label2])
stack.distribution = .equalSpacing
stack.spacing = 4.0
return stack
}()
When trying to update the label text with a function, I wasnt sure how to access the label properties to make this change. Normally, for a label created outside of the stack i could simply use:
func updateLabel() {
label1.text = "Updated Label 1 text"
label2.text = "Updated Label 2 text"
}
What is the syntax to use to access these label properties sitting inside the UIStackview with labels?
You can make them outside let labelStack: UIStackView = {
let label1: UILabel = {
let label = UILabel()
label.text = "Label 1"
label.font = .systemFont(ofSize: 14, weight: .bold)
label.numberOfLines = 0
label.backgroundColor = .clear
label.textAlignment = .left
label.sizeToFit()
return label
}()
let label2: UILabel = {
let label = UILabel()
label.text = "Label 2"
label.font = .systemFont(ofSize: 14, weight: .bold)
label.numberOfLines = 0
label.backgroundColor = .clear
label.textAlignment = .left
label.sizeToFit()
return label
}()
let labelStack: UIStackView = {
let stack = UIStackView(arrangedSubviews: [label1, label2])
stack.distribution = .equalSpacing
stack.spacing = 4.0
return stack
}()
Or do this
if let label1 = labelStack.arrangedSubviews.first as? UILabel {
// proceed
}
if let label2 = labelStack.arrangedSubviews.last as? UILabel {
// proceed
}
You can create them outside. But that will require you to add the arrangedSubviews later as it has not been initialized yet. So you could make the stackView Lazy, which waits for init to be run:
let label1: UILabel = {
let label = UILabel()
label.text = "Label 1"
label.font = .systemFont(ofSize: 14, weight: .bold)
label.numberOfLines = 0
label.backgroundColor = .clear
label.textAlignment = .left
label.sizeToFit()
return label
}()
let label2: UILabel = {
let label = UILabel()
label.text = "Label 2"
label.font = .systemFont(ofSize: 14, weight: .bold)
label.numberOfLines = 0
label.backgroundColor = .clear
label.textAlignment = .left
label.sizeToFit()
return label
}()
lazy var labelStack: UIStackView = {
let stack = UIStackView(arrangedSubviews: [label1, label2])
stack.distribution = .equalSpacing
stack.spacing = 4
return stack
}()
If you have to labels and 2 texts statically you can do this:
zip(labelStack.arrangedSubviews, ["textupdate1", "textupdate2"]).forEach { (element value) in
(element as? UILabel)?.text = value
}

Stackview in navigation bar

Is it possible to place UIStackView in NavigationBar programmaticaly using swift? I want to place there StackView with two arranged stackviews. But when i do that , it shows nothing in navigation bar. If it is possible, please provide example. Thanks
Finally I found solution
let btnSort = UIButton(type: .system)
btnSort.frame = CGRect(x: 0, y: 0, width: 120, height: 40)
btnSort.tintColor = UIColor.white
btnSort.setImage(UIImage(named:"ic_controls_icon.png"), for: .normal)
btnSort.imageEdgeInsets = UIEdgeInsets(top: 6,left: -10,bottom: 6,right: 34)
btnSort.titleEdgeInsets = UIEdgeInsets(top: 0,left: 0,bottom: 0,right: 14)
btnSort.setTitle("SORT", for: .normal)
btnSort.layer.borderWidth = 1.0
btnSort.backgroundColor = UIColor.red //--> set the background color and check
btnSort.layer.borderColor = UIColor.white.cgColor
let btnControl = UIButton(type: .system)
btnControl.frame = CGRect(x: 0, y: 0, width: 120, height: 40)
btnControl.tintColor = UIColor.white
btnControl.setImage(UIImage(named:"ic_controls_icon.png"), for: .normal)
btnControl.imageEdgeInsets = UIEdgeInsets(top: 6,left: -10,bottom: 6,right: 34)
btnControl.titleEdgeInsets = UIEdgeInsets(top: 0,left: 0,bottom: 0,right: 14)
btnControl.setTitle("SORT", for: .normal)
btnControl.layer.borderWidth = 1.0
btnControl.backgroundColor = UIColor.red //--> set the background color and check
btnControl.layer.borderColor = UIColor.white.cgColor
let view = UIStackView(frame: CGRect(x: 0, y: 0, width: 300, height: 50))
view.axis = .horizontal
view.distribution = .fillEqually
view.spacing = 5
view.addArrangedSubview(btnSort)
view.addArrangedSubview(btnControl)
let mainTitleView = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 50))
mainTitleView.addSubview(view)
navigationItem.titleView = mainTitleView
You can make any UIView subclass (of which UIStackView is one) the navigation bar's title using your view controller's navigationItem.titleView property.
You can test this out in a playground…
import UIKit
import PlaygroundSupport
let vc = UIViewController()
vc.view.backgroundColor = .white
let nav = UINavigationController(rootViewController: vc)
let topLabel = UILabel()
topLabel.font = UIFont.boldSystemFont(ofSize: 20)
topLabel.text = "Hello"
let bottomLabel = UILabel()
bottomLabel.font = UIFont.systemFont(ofSize: 16)
bottomLabel.text = "World!"
let stackView = UIStackView(arrangedSubviews: [topLabel, bottomLabel])
stackView.axis = .vertical
vc.navigationItem.titleView = stackView
PlaygroundPage.current.liveView = nav.view
PlaygroundPage.current.needsIndefiniteExecution = true
var navTitle: String? = "Preview Checklist"
var navSubTitle: String? = "Edit Checklist >"
lazy var titleStackView: UIStackView = {
let titleLabel = UILabel()
titleLabel.textAlignment = .left
titleLabel.text = navTitle
//titleLabel.font = UIFont(name: "RawlineMedium-Regular", size:CGFloat(15))
titleLabel.textColor = .white
let subtitleLabel = UILabel()
subtitleLabel.textAlignment = .left
subtitleLabel.text = navSubTitle
//subtitleLabel.font = UIFont(name: "RawlineMedium-Regular", size:CGFloat(11))
subtitleLabel.textColor = .white
let stackView = UIStackView(arrangedSubviews: [ titleLabel, subtitleLabel])
stackView.axis = .vertical
stackView.backgroundColor = .blue
return stackView
}()
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.titleView = titleStackView
}

How do I dynamically adjust the height of a UIView when a member stack view is no longer shown?

I am super new iOS development and StackViews in general and need help calculating dynamic height in instances where a stack view will not be shown. There are cases where certain elements will not be shown depending on what I get back from the server.
However, when I call removeArrangedSubview the element is removed but the height isn't adjusted dynamically. How can I fix this?
I would like to avoid the Interface Builder all together and just do this programmatically. I have been using layout anchors for constraints.
Here's my code. You can put in a playground to see it.
//: Playground - noun: a place where people can play
import UIKit
import Foundation
let view = UIView(frame: CGRect(x: 0, y: 0, width: 800, height: 140))
let firstStackView = UIStackView(frame: CGRectZero)
//firstStackView.heightAnchor.constraintGreaterThanOrEqualToConstant(40).active = true
firstStackView.axis = .Vertical
firstStackView.alignment = .Fill
firstStackView.distribution = .EqualSpacing
let titleStackView = UIStackView(frame: CGRectZero)
titleStackView.axis = .Horizontal
titleStackView.alignment = .Fill
titleStackView.distribution = .Fill
titleStackView.spacing = 3
firstStackView.addArrangedSubview(titleStackView)
let productStackView = UIStackView(frame: .zero)
productStackView.axis = .Horizontal
productStackView.alignment = .Leading
productStackView.distribution = .Fill
productStackView.spacing = 3
firstStackView.addArrangedSubview(productStackView)
//firstStackView.removeArrangedSubview(productStackView)
let secondStackView = UIStackView(frame: CGRectZero)
//secondStackView.heightAnchor.constraintEqualToConstant(30).active = true
secondStackView.axis = .Horizontal
secondStackView.distribution = .EqualSpacing
let title = UILabel(frame: CGRectZero)
title.text = "test1"
title.textColor = .blackColor()
//labelOne.backgroundColor = .blueColor()
let size = title.sizeThatFits(CGSizeZero)
print("\(size)")
title.widthAnchor.constraintEqualToConstant(size.width).active = true
//labelOne.heightAnchor.constraintEqualToConstant(30).active = true
titleStackView.addArrangedSubview(title)
let assigneeLabel = UILabel(frame: CGRectZero)
assigneeLabel.text = "test2"
assigneeLabel.textColor = .blackColor()
//labelTest.backgroundColor = .redColor()
assigneeLabel.textAlignment = .Left
//labelTest.heightAnchor.constraintEqualToConstant(30).active = true
titleStackView.addArrangedSubview(assigneeLabel)
let actions = UIButton(type: .Custom)
//buttonOne.backgroundColor = .redColor()
actions.setTitle("some button", forState: .Normal)
actions.setTitleColor(.blackColor(), forState: .Normal)
titleStackView.addArrangedSubview(actions)
let productOne = UILabel(frame: CGRectZero)
productOne.text = "something1"
productOne.numberOfLines = 0
let productLabelSize = productOne.sizeThatFits(CGSizeZero)
productOne.widthAnchor.constraintEqualToConstant(productLabelSize.width).active = true
productOne.textColor = .blackColor()
//labelTwo.backgroundColor = .blueColor()
productStackView.removeArrangedSubview(productOne)
//productStackView.addArrangedSubview(productOne)
let productTwo = UILabel(frame: CGRectZero)
productTwo.text = "something2"
productTwo.numberOfLines = 0
//productTwo.heightAnchor.constraintEqualToConstant(30).active = true
productTwo.textColor = .blackColor()
//labelTwo.backgroundColor = .blueColor()
productStackView.removeArrangedSubview(productTwo)
//productStackView.addArrangedSubview(productTwo)
let labelThree = UILabel(frame: CGRectZero)
labelThree.text = "sometime"
//labelThree.heightAnchor.constraintEqualToConstant(30).active = true
labelThree.textColor = .blackColor()
//labelThree.backgroundColor = .blueColor()
firstStackView.addArrangedSubview(labelThree)
let descriptionView = UILabel(frame: CGRectZero)
descriptionView.text = "some description about something"
descriptionView.textColor = .blackColor()
//descriptionView.backgroundColor = .redColor()
secondStackView.addArrangedSubview(descriptionView)
let tagsView = UILabel(frame: CGRectZero)
tagsView.text = "some more things"
tagsView.textColor = .blackColor()
secondStackView.addArrangedSubview(tagsView)
secondStackView.trailingAnchor.constraintEqualToAnchor(tagsView.trailingAnchor).active = true
let stackView = UIStackView(arrangedSubviews: [firstStackView, secondStackView])
stackView.layoutMargins = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
stackView.layoutMarginsRelativeArrangement = true
stackView.axis = .Vertical
stackView.frame = view.bounds
stackView.distribution = .FillProportionally
view.addSubview(stackView)
Before element is removed:
After element is removed:
I would like that gap to be gone and the height adjusted dynamically.
You can add a constraint for the height of the view if you are using auto layout.
So like for initial setup, you could do something like:
class YourClass : UIViewController() {
var heightConstraint = NSLayoutConstraint()
func someMethod () {
// load your View
// get the height of view.
heightConstraint = yourView.heightAnchor.constraintEqualToConstant(height)
self.view.addConstraint(heightConstraint)
}
func deleteMemberStackView() {
/// After deleting the member, get the new height of the view and do this
self.view.removeConstraint(heightConstraint)
heightConstraint = yourView.heightAnchor.constraintEqualToConstant(height)
self.view.addConstraint(heightConstraint)
UIView.animateViewDuration(0.3, completion: {
self.view.layoutIfNeeded()
})
}
}

How to resize Title in a navigation bar dynamically

I have some views that show up in a navigation controller. Two of these views have a longer title for the navigation bar.
The problem is that when the title is too long to fit, some characters are truncated and "..." is added.
Is there any way I can tell the Navigation bar to re-size the title text automatically to fit?
Used the below code in ViewDidload .
Objective C
self.title = #"Your TiTle Text";
UILabel* tlabel=[[UILabel alloc] initWithFrame:CGRectMake(0,0, 200, 40)];
tlabel.text=self.navigationItem.title;
tlabel.textColor=[UIColor whiteColor];
tlabel.font = [UIFont fontWithName:#"Helvetica-Bold" size: 30.0];
tlabel.backgroundColor =[UIColor clearColor];
tlabel.adjustsFontSizeToFitWidth=YES;
tlabel.textAlignment = NSTextAlignmentCenter;
self.navigationItem.titleView=tlabel;
Swift Version
self.title = "Your Title Text"
let tlabel = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 40))
tlabel.text = self.title
tlabel.textColor = UIColor.white
tlabel.font = UIFont.systemFont(ofSize: 30, weight: .bold)
tlabel.backgroundColor = UIColor.clear
tlabel.adjustsFontSizeToFitWidth = true
tlabel.textAlignment = .center
self.navigationItem.titleView = tlabel
Hope it works for you.Thanks
Swift version of Accepted Answer + putting the label text on center :
Swift 2.3:
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.boldSystemFontOfSize(17) //UIFont(name: "Helvetica", size: 17.0)
tlabel.backgroundColor = UIColor.clearColor()
tlabel.adjustsFontSizeToFitWidth = true
tlabel.textAlignment = .Center
self.navigationItem.titleView = tlabel
And Swift 3 :
self.title = "Your TiTle Text"
let frame = CGRect(x: 0, y: 0, width: 200, height: 40)
let tlabel = UILabel(frame: frame)
tlabel.text = self.title
tlabel.textColor = UIColor.white
tlabel.font = UIFont.boldSystemFont(ofSize: 17) //UIFont(name: "Helvetica", size: 17.0)
tlabel.backgroundColor = UIColor.clear
tlabel.adjustsFontSizeToFitWidth = true
tlabel.textAlignment = .center
self.navigationItem.titleView = tlabel
This works for me
Objective C
[UILabel appearanceWhenContainedInInstancesOfClasses:#[[UINavigationBar class]]].adjustsFontSizeToFitWidth = YES;
Swift Version
UILabel.appearance(whenContainedInInstancesOf: [UINavigationBar.self]).adjustsFontSizeToFitWidth = true
In case you have a view added into titleView, and you want to resize the view, you can use this code (Swift 3):
self.translatesAutoresizingMaskIntoConstraints = false
self.layoutIfNeeded()
self.sizeToFit()
self.translatesAutoresizingMaskIntoConstraints = true
None of the above solutions seam to work reliably for me.
However I found a solution by using different elements of the provides answers, its in Swift 2 and is really elegant as it does not require any custom code each time you change the label, it just uses property observers on the title.
Note that in my case, I had a back button on the left side of the navigation bar, which putted the text out of the center of the screen, to fix this I am using attributed text and the tailIndent. All comments/info in the code below :
class VCHowToTopic : UIViewController {
//add handlers so that any manipulation of the title is caught and transferred to the custom drawn UILabel
override var title : String? {
set {
super.title = newValue
configureTitleView()
}
get {
return super.title
}
}
//MARK: - lifecycle
func configureTitleView() {
//some large number that makes the navigationbar schrink down our view when added
let someVeryLargeNumber = CGFloat(4096)
//create our label
let titleLabel = UILabel(frame: CGRect(x: 0, y: 0, width: someVeryLargeNumber, height: someVeryLargeNumber))
//0 means unlimited number of lines
titleLabel.numberOfLines = 0
//define style of the text (we will be using attributed text)
let style = NSMutableParagraphStyle()
style.alignment = .Center
//top compensate for the backbutton which moves the centered text to the right side of the screen
//we introduce a negative tail indent, the number of 56 has been experimentally defined and might
//depend on the size of your custom back button (if you have one), mine is 22x22 px
style.tailIndent = -56
//create attributed text also with the right color
let attrText = NSAttributedString(string: title!, attributes: [NSParagraphStyleAttributeName : style,
NSForegroundColorAttributeName : UIColor.whiteColor()])
//configure the label to use the attributed text
titleLabel.attributedText = attrText
//add it as the titleview
navigationItem.titleView = titleLabel
}
}
You can create a UILabel as UINavigationItem's titleView and set it's adjustsFontSizeToFitWidth to true.
class MyViewController: UIViewController {
override var title: String? {
didSet {
(self.navigationItem.titleView as? UILabel)?.text = self.title
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.titleView = UILabel().apply {
$0.font = .boldSystemFont(ofSize: 18)
$0.minimumScaleFactor = 0.5
$0.adjustsFontSizeToFitWidth = true
$0.text = self.title
}
}
}
Easy to use:
myViewController.title = "This is a long title, but don’t worry."
The apply closure in the above code is a trick, in order to make the programming experience better. There is also a with closure. Recommend to everyone.
protocol ScopeFunc {}
extension ScopeFunc {
#inline(__always) func apply(_ block: (Self) -> ()) -> Self {
block(self)
return self
}
#inline(__always) func with<R>(_ block: (Self) -> R) -> R {
return block(self)
}
}
extension NSObject: ScopeFunc {}
Swift 5 and iOS 13 / iOS 14
The answers from above don't work if you have a large title in Swift 5 and iOS 13 because they simply add another title to your navigation bar. Instead you could use the largeTitleTextAttributes property (available since iOS 11) to shrink your title when needed.
Assuming you have set your large title via storyboard or code already, you can use the following method:
private func configureNavigationTitle(_ title: String) {
let tempLabel = UILabel()
tempLabel.font = UIFont.systemFont(ofSize: 34, weight: .bold)
tempLabel.text = title
if tempLabel.intrinsicContentSize.width > UIScreen.main.bounds.width - 30 {
var currentTextSize: CGFloat = 34
for _ in 1 ... 34 {
currentTextSize -= 1
tempLabel.font = UIFont.systemFont(ofSize: currentTextSize, weight: .bold)
if tempLabel.intrinsicContentSize.width < UIScreen.main.bounds.width - 30 {
break
}
}
navigationController?.navigationBar.largeTitleTextAttributes = [NSAttributedString.Key.font : UIFont.systemFont(ofSize: currentTextSize, weight: .bold)]
}
self.title = title
}
So essentially we are ussing a helper label in order to get the width of our title and then we are going to shrink the font size until the title fits in our navigation bar.
Call it from viewDidLoad():
override func viewDidLoad() {
super.viewDidLoad(
configureNavigationTitle("A very long title which fits perfectly fine")
}
you need to customize the navigation bar title view with uilabel and provide adjust font size..
[self.navigationItem setTitleView:<"Include any UI View subclass">];
Just calling sizeToFit() on my view after the change worked for me
Here's an example in Swift that also allows for multiple lines. Using PureLayout to simplify auto layout.
override func viewDidLoad() {
super.viewDidLoad()
configureTitleView()
}
func configureTitleView() {
let titleLabel = UILabel()
titleLabel.numberOfLines = 0
titleLabel.textAlignment = .Center
titleLabel.font = UIFont.boldSystemFontOfSize(17.0)
titleLabel.text = searchLoc.mapItem.name
navigationItem.titleView = titleLabel
titleLabel.autoPinEdgesToSuperviewMargins() // PureLayout method
titleLabel.adjustsFontSizeToFitWidth = true
}
And a usage example:
Swift 4 and iOS 13
Adding this so my future self can find it. Views added to titleView for some reason don't like to automatically resize themselves. So you have to do it manually.
Example
(navigationItem.titleView as? UILabel)?.text = "A longer string..." // label not resized and text is cut off
Solution
navigationItem.titleView?.translatesAutoresizingMaskIntoConstraints = false
navigationItem.titleView?.setNeedsLayout()
navigationItem.titleView?.layoutIfNeeded()
navigationItem.titleView?.translatesAutoresizingMaskIntoConstraints = true
Thanks to #Paolo Musolino for leading me here.