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.
Related
This is how my app looks although I entered this code inside my ViewController class:
#IBOutlet var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
label.text = "Hello World"
label.adjustsFontSizeToFitWidth = true
label.numberOfLines = 1
label.minimumScaleFactor = 0.1
}
Your text data is not more than label width that's why label text font is same as already set.
IF your text data is more then label width then it will adjust font according to the width.
Please check with label text: "This is the demo to test label text is adjustable or not. You need to test it with this demo data"
Your label font will adjust according to the width.
The font will adjust if the given text is greater than the width of the label.
Try this in playground:
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let label = UILabel()
label.frame = CGRect(x: 150, y: 200, width: 200, height: 20)
label.adjustsFontSizeToFitWidth = true
label.numberOfLines = 1
label.backgroundColor = UIColor.lightGray
label.text = "Hello World! How are you doing today? "
label.textColor = .black
view.addSubview(label)
self.view = view
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
The result is the following:
I was able to get my UILabel font to dynamically adjust to the necessary size to fit into its parent by following this simple gitconnected article (See link to get all required code!!). I only needed to make two adjustments which were adding the lines label.baselineAdjustment = .alignCenters and label.numberOfLines = 1 so that my label creation now looked like this...
let dynamicFontLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 40)
label.textAlignment = .center
label.numberOfLines = 1;
label.textColor = .black
label.adjustsFontSizeToFitWidth = true
label.baselineAdjustment = .alignCenters
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
The label.baselineAdjustment = .alignCenters property ensured that if my font size was too large and needed to be downsized, my text would still remain centered vertically in the UILabel. I also only wanted my text to only span one line so if you want more than that you can just remove the label.numberOfLines = 1 property.
I am using this func inside a UIViewController extension to add a title that adjusts font to fit width.
extesion UIViewController {
func setTitleDifferentSizes(title: String){
self.title = title
guard let navigationBarHeight: CGFloat =
self.navigationController?.navigationBar.frame.height else{
return
}
let tlabel = UILabel(frame: CGRect(x: 0.0, y: 0.0, width:
200.0, height: navigationBarHeight))
tlabel.text = self.title
tlabel.textColor = UIColor.white
tlabel.font = font24
tlabel.backgroundColor = UIColor.clear
tlabel.adjustsFontSizeToFitWidth = true
self.navigationItem.titleView = tlabel
}
}
I took this solution from this SO question and changed it a little bit:
How to resize Title in a navigation bar dynamically
Now the issue I have is that the text of the title is not aligned vertically to the other navigation bar items, as you can see in the images, I show one where I just setup the title without using the above method, and the text there cannot fit but it is aligned properly, and the other image is using the method above where the text fits but it is not aligned.
try this:-
func setTitleDifferentSizes(title: String){
self.title = title
guard let navigationBarHeight: CGFloat =
self.navigationController?.navigationBar.frame.height else{
return
}
let attributedString = NSMutableAttributedString(string: title)
let myAttribute = [ NSForegroundColorAttributeName: UIColor.white ,NSFontAttributeName: font24]
attributedString.addAttributes(myAttribute, range: NSRange(location: 0, length: attributedString.string.characters.count))
attributedString.addAttributes([NSBaselineOffsetAttributeName:6.0], range: NSRange(location: 0, length: title.characters.count)
)
let tlabel = UILabel(frame: CGRect(x: 0.0, y: 0.0, width:
200.0, height: navigationBarHeight))
tlabel.attributedText = attributedString
tlabel.backgroundColor = UIColor.clear
tlabel.adjustsFontSizeToFitWidth = true
tlabel.minimumScaleFactor = 0.2
tlabel.textAlignment = .center
self.navigationItem.titleView = tlabel
}
if you want to adjust the position of text please change the float value of NSBaselineOffsetAttributeName to set the vertical alignment.
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
}
I would like to create a nav bar similar to what's in the image that's attached.
The title of the nav bar will be a combination of an image and text.
Should this be done per any best practice?
How can it be done?
As this answer shows, the easiest solution is to add the text to your image and add that image to the navigation bar like so:
var image = UIImage(named: "logo.png")
self.navigationItem.titleView = UIImageView(image: image)
But if you have to add text and an image separately (for example, in the case of localization), you can set your navigation bar's title view to contain both image and text by adding them to a UIView and setting the navigationItem's title view to that UIView, for example (assuming the navigation bar is part of a navigation controller):
// Only execute the code if there's a navigation controller
if self.navigationController == nil {
return
}
// Create a navView to add to the navigation bar
let navView = UIView()
// Create the label
let label = UILabel()
label.text = "Text"
label.sizeToFit()
label.center = navView.center
label.textAlignment = NSTextAlignment.Center
// Create the image view
let image = UIImageView()
image.image = UIImage(named: "Image.png")
// To maintain the image's aspect ratio:
let imageAspect = image.image!.size.width/image.image!.size.height
// Setting the image frame so that it's immediately before the text:
image.frame = CGRect(x: label.frame.origin.x-label.frame.size.height*imageAspect, y: label.frame.origin.y, width: label.frame.size.height*imageAspect, height: label.frame.size.height)
image.contentMode = UIViewContentMode.ScaleAspectFit
// Add both the label and image view to the navView
navView.addSubview(label)
navView.addSubview(image)
// Set the navigation bar's navigation item's titleView to the navView
self.navigationItem.titleView = navView
// Set the navView's frame to fit within the titleView
navView.sizeToFit()
Use horizontal UIStackView should be much cleaner and easier
Please add the next extension to UIViewController
extension UIViewController {
func setTitle(_ title: String, andImage image: UIImage) {
let titleLbl = UILabel()
titleLbl.text = title
titleLbl.textColor = UIColor.white
titleLbl.font = UIFont.systemFont(ofSize: 20.0, weight: .bold)
let imageView = UIImageView(image: image)
let titleView = UIStackView(arrangedSubviews: [imageView, titleLbl])
titleView.axis = .horizontal
titleView.spacing = 10.0
navigationItem.titleView = titleView
}
}
then use it inside your viewController:
setTitle("yourTitle", andImage: UIImage(named: "yourImage"))
(this will align the text and the icon together to the center, if you want the text to be centered and the icon in the left, just add an empty UIView with width constraint equal to the icon width)
here is my 2 cents for Swift 4, since accepted answer didn't work for me (was mostly off the screen):
// .. in ViewController
var navBar = CustomTitleView()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// =================== navBar =====================
navBar.loadWith(title: "Budget Overview", leftImage: Images.pie_chart)
self.navigationItem.titleView = navBar
}
class CustomTitleView: UIView
{
var title_label = CustomLabel()
var left_imageView = UIImageView()
override init(frame: CGRect){
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
setup()
}
func setup(){
self.addSubview(title_label)
self.addSubview(left_imageView)
}
func loadWith(title: String, leftImage: UIImage?)
{
//self.backgroundColor = .yellow
// =================== title_label ==================
//title_label.backgroundColor = .blue
title_label.text = title
title_label.font = UIFont.systemFont(ofSize: FontManager.fontSize + 5)
// =================== imageView ===================
left_imageView.image = leftImage
setupFrames()
}
func setupFrames()
{
let height: CGFloat = Navigation.topViewController()?.navigationController?.navigationBar.frame.height ?? 44
let image_size: CGFloat = height * 0.8
left_imageView.frame = CGRect(x: 0,
y: (height - image_size) / 2,
width: (left_imageView.image == nil) ? 0 : image_size,
height: image_size)
let titleWidth: CGFloat = title_label.intrinsicContentSize.width + 10
title_label.frame = CGRect(x: left_imageView.frame.maxX + 5,
y: 0,
width: titleWidth,
height: height)
contentWidth = Int(left_imageView.frame.width)
self.frame = CGRect(x: 0, y: 0, width: CGFloat(contentWidth), height: height)
}
var contentWidth: Int = 0 //if its CGFloat, it infinitely calls layoutSubviews(), changing franction of a width
override func layoutSubviews() {
super.layoutSubviews()
self.frame.size.width = CGFloat(contentWidth)
}
}
Swift 4.2 + Interface Builder Solution
As a follow-on to Lyndsey Scott's answer, you can also create a UIView .xib in Interface Builder, use that to lay out your title and image, and then update it on-the-fly via an #IBOutlet. This is useful for dynamic content, internationalization, maintainability etc.
Create a UIView subclass with a UILabel outlet and assign your new .xib to this class:
import UIKit
class FolderTitleView: UIView {
#IBOutlet weak var title : UILabel!
/// Create an instance of the class from its .xib
class func instanceFromNib() -> FolderTitleView {
return UINib(nibName: "FolderTitleView", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! FolderTitleView
}
}
Connect the label to your outlet (title in my example) in your .xib, then in your UIViewController:
/// Reference to the title view
var folderTitleView : FolderTitleView?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Set the screen title to match the active folder
updateTitle()
}
/// Updates the title of the navigation controller.
func updateTitle() {
self.title = ""
if folderTitleView == nil {
folderTitleView = FolderTitleView.instanceFromNib()
self.navigationItem.titleView = folderTitleView
}
folderTitleView!.title.text = "Listening"
folderTitleView!.layoutIfNeeded()
}
This results in a nice self-centering title bar with an embedded image that you can easily update from code.
// worked for me
create a view and set the frame
now add the image in the view and set the frame
after adding the image, add the label in same view and set the frame
after adding the image and label to view, add same view to navigationItem
let navigationView = UIView(frame: CGRect(x: 0, y: 0, width: 50 , height: 55))
let labell : UILabel = UILabel(frame: CGRect(x: -38, y: 25, width: 150, height: 25))
labell.text = "Your text"
labell.textColor = UIColor.black
labell.font = UIFont.boldSystemFont(ofSize: 10)
navigationView.addSubview(labell)
let image : UIImage = UIImage(named: ValidationMessage.headerLogoName)!
let imageView = UIImageView(frame: CGRect(x: -20, y: 0, width: 100, height: 30))
imageView.contentMode = .scaleAspectFit
imageView.image = image
//navigationItem.titleView = imageView
navigationView.addSubview(imageView)
navigationItem.titleView = navigationView
I'm trying to customise the appearance of the search bar in my search controller.
Setting the background and text colors works fine but I just didn't find a way to change the color of the icons in the text field, specifically the magnifying glass and the x button.
I've found this Objective-C code which should do what I want but I'm struggling to translate it to Swift:
(EDIT: Skip to the first answer for the working Swift 3 solution.)
UITextField *searchBarTextField = [self.searchDisplayController.searchBar valueForKey:#"_searchField"];
// Magnifying glass icon.
UIImageView *leftImageView = (UIImageView *)searchBarTextField.leftView;
leftImageView.image = [LeftImageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
leftImageView.tintColor = [UIColor whiteColor];
// Clear button
UIButton *clearButton = [searchBarTextField valueForKey:#"_clearButton"];
[clearButton setImage:[clearButton.imageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateNormal];
clearButton.tintColor = [UIColor whiteColor];
My attempt to translate to Swift:
let textField = searchController.searchBar.valueForKey("searchField") as! UITextField
// These two work fine.
textField.backgroundColor = UIColor.blackColor()
textField.textColor = UIColor.blackColor()
var glassIcon = textField.leftView
// This would work.
//glassIcon.hidden = true
// This does not have any effect.
//glassIcon?.tintColor = UIColor.whiteColor()
// My attempt to translate, but it gives an error.
glassIcon.image? = UIImage.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate)
var clearButton = textField.valueForKey("clearButton")!
clearButton.setImage(clearButton.imageWithRenderingMode(.AlwaysTemplate), forState: .Normal)
// This gives the error: "Cannot assign to property: 'clearButton' is immutable
clearButton.tintColor = UIColor.whiteColor()
// Sorry for the weird formatting, it glitches here in the editor.
The leftView does not seem to have an image property. How can I access that property as the Objective-C code does?
Also, if there is a better to achieve what I want please let me know.
Here is the solution:
// Text field in search bar.
let textField = searchController.searchBar.value(forKey: "searchField") as! UITextField
let glassIconView = textField.leftView as! UIImageView
glassIconView.image = glassIconView.image?.withRenderingMode(.alwaysTemplate)
glassIconView.tintColor = UIColor.white
let clearButton = textField.valueForKey("clearButton") as! UIButton
clearButton.setImage(clearButton.imageView?.image?.withRenderingMode(.alwaysTemplate), for: .normal)
clearButton.tintColor = UIColor.white
Here is the solution:
extension UITextField{
func setLeftIcon(_ icon: UIImage) {
let padding = 8
let size = 20
let outerView = UIView(frame: CGRect(x: 0, y: 0, width: size+padding, height: size) )
let iconView = UIImageView(frame: CGRect(x: padding, y: 0, width: size, height: size))
iconView.image = icon.withRenderingMode(.alwaysTemplate)
iconView.tintColor = UIColor.white
outerView.addSubview(iconView)
leftView = outerView
leftViewMode = .always
}
}