Search bar with corner radius in swift - swift

I want to create a view like the above image.it has a search bar with corner radius.but when i am trying to create, i am unable to make the search bar with corner radius.also i am unable to make the text field of the search bar with corner radius. i have writtenall my code in viewDidAppear method. It is ok or i have to write it in viewWillLayourSubview. so that i will be able to make the exact
same search bar like this image. also i want the seach icon to be placed slightly right.
My code is:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
for subView in searchBar.subviews {
for subsubView in subView.subviews {
if let textField = subsubView as? UITextField {
var bounds: CGRect
var placeHolder = NSMutableAttributedString()
let Name = "Search"
placeHolder = NSMutableAttributedString(string:Name, attributes: [NSAttributedString.Key.font:UIFont(name: "Helvetica", size: 15.0)!])
placeHolder.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.gray, range:NSRange(location:0,length:Name.count))
textField.attributedPlaceholder = placeHolder
if let leftView = textField.leftView as? UIImageView {
leftView.image = leftView.image?.withRenderingMode(.alwaysTemplate)
leftView.frame.size.width = 15.0
leftView.frame.size.height = 15.0
leftView.tintColor = UIColor.gray
}
textField.layer.cornerRadius = 50.0
bounds = textField.frame
bounds.size.width = searchBar.frame.width
bounds.size.height = searchBar.frame.height
textField.bounds = bounds
textField.borderStyle = UITextField.BorderStyle.roundedRect
searchBar.backgroundImage = UIImage()
textField.backgroundColor = UIColor.lightGray.withAlphaComponent(0.2)
searchBar.searchTextPositionAdjustment = UIOffset(horizontal: 5, vertical: 0)
}
}
}
}*

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.borderStyle = UITextField.BorderStyle.roundedRect
textField.layer.cornerRadius = textField.frame.size.height / 2
textField.layer.masksToBounds = true
textField.textColor = .white
textField.backgroundColor = .lightGray
}
searchBar.barTintColor = .white
searchBar.backgroundColor = .white
searchBar.searchTextPositionAdjustment = UIOffset(horizontal: 5, vertical: 0)
}
Doesn't look exactly like in the image that you linked, but actually fits better into the Apple design and works better than the code that you wrote.
For anything more sophisticated, I would advise to create a custom UISearchBar subclass.
Be aware of Apple's Human Interface Guidelines, so anything too crazy / different from default might not be accepted in the AppStore.

Related

Create UIButtons with dynamic font size but all share same font size in UIStackView

I am using UIStackView and adding three buttons to it. I want it so that the button with the most text (B1) will be auto resized to fit the width and the other buttons will share the same font size as B1.
#IBOutlet weak var stackView: UIStackView!
var btnTitles = [String]()
btnTitles.append("Practice Exams")
btnTitles.append("Test Taking Tips")
btnTitles.append("About")
createButtons(buttonTitles: btnTitles)
var min = CGFloat(Int.max) // keep track of min font
func createButtons(buttonTitles: [String]) {
var Buttons = [UIButton]()
for title in buttonTitles {
let button = makeButtonWithText(text: title)
// set the font to dynamically size
button.titleLabel!.numberOfLines = 1
button.titleLabel!.adjustsFontSizeToFitWidth = true
button.titleLabel!.baselineAdjustment = .alignCenters // I think it keeps it centered vertically
button.contentEdgeInsets = UIEdgeInsetsMake(5, 10, 5, 10); // set margins
if (button.titleLabel?.font.pointSize)! < min {
min = (button.titleLabel?.font.pointSize)! // to get the minimum font size of any of the buttons
}
stackView.addArrangedSubview(button)
Buttons.append(button)
}
}
func makeButtonWithText(text:String) -> UIButton {
var myButton = UIButton(type: UIButtonType.system)
//Set a frame for the button. Ignored in AutoLayout/ Stack Views
myButton.frame = CGRect(x: 30, y: 30, width: 150, height: 100)
// background color - light blue
myButton.backgroundColor = UIColor(red: 0.255, green: 0.561, blue: 0.847, alpha: 1)
//State dependent properties title and title color
myButton.setTitle(text, for: UIControlState.normal)
myButton.setTitleColor(UIColor.white, for: UIControlState.normal)
// set the font to dynamically size
myButton.titleLabel!.font = myButton.titleLabel!.font.withSize(70)
myButton.contentHorizontalAlignment = .center // align center
return myButton
}
I wanted to find the minimum font size and then set all the buttons to the minimum in viewDidAppear button the font prints as 70 for all of them even though they clearly appear different sizes (see image)
override func viewDidAppear(_ animated: Bool) {
print("viewDidAppear")
let button = stackView.arrangedSubviews[0] as! UIButton
print(button.titleLabel?.font.pointSize)
let button1 = stackView.arrangedSubviews[1] as! UIButton
print(button1.titleLabel?.font.pointSize)
let button2 = stackView.arrangedSubviews[2] as! UIButton
print(button2.titleLabel?.font.pointSize)
}
image
You can try playing around with this func to return the scaled-font-size of a label:
func actualFontSize(for aLabel: UILabel) -> CGFloat {
// label must have text, must have .minimumScaleFactor and must have .adjustsFontSizeToFitWidth == true
guard let str = aLabel.text,
aLabel.minimumScaleFactor > 0.0,
aLabel.adjustsFontSizeToFitWidth
else { return aLabel.font.pointSize }
let attributes = [NSAttributedString.Key.font : aLabel.font]
let attStr = NSMutableAttributedString(string:str, attributes:attributes as [NSAttributedString.Key : Any])
let context = NSStringDrawingContext()
context.minimumScaleFactor = aLabel.minimumScaleFactor
_ = attStr.boundingRect(with: aLabel.bounds.size, options: .usesLineFragmentOrigin, context: context)
return aLabel.font.pointSize * context.actualScaleFactor
}
On viewDidAppear() you would loop through the buttons, getting the smallest actual font size, then set the font size for each button to that value.
It will take some experimentation... For one thing, I've noticed in the past that font-sizes can get rounded - so setting a label's font point size to 20.123456789 won't necessarily give you that exact point size. Also, since this changes the actual font size assigned to the labels, you'll need to do some resetting if you change the button title dynamically. Probably also need to account for button frame changes (such as with device rotation, etc).
But... here is a quick test that you can run to see the approach:
class TestViewController: UIViewController {
let stackView: UIStackView = {
let v = UIStackView()
v.translatesAutoresizingMaskIntoConstraints = false
v.axis = .vertical
v.alignment = .center
v.distribution = .fillEqually
v.spacing = 8
return v
}()
var btnTitles = [String]()
var theButtons = [UIButton]()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
fixButtonFonts()
}
func setupUI() -> Void {
view.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40),
stackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 40),
stackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -40),
])
btnTitles.append("Practice Exams")
btnTitles.append("Test Taking Tips")
btnTitles.append("About")
createButtons(buttonTitles: btnTitles)
}
func fixButtonFonts() -> Void {
var minActual = CGFloat(70)
// get the smallest actual font size
theButtons.forEach { btn in
if let lbl = btn.titleLabel {
let act = actualFontSize(for: lbl)
// for debugging
//print("actual font size: \(act)")
minActual = Swift.min(minActual, act)
}
}
// set font size for each button
theButtons.forEach { btn in
if let lbl = btn.titleLabel {
lbl.font = lbl.font.withSize(minActual)
}
}
}
func createButtons(buttonTitles: [String]) {
for title in buttonTitles {
let button = makeButtonWithText(text: title)
// set the font to dynamically size
button.titleLabel!.numberOfLines = 1
button.titleLabel!.adjustsFontSizeToFitWidth = true
// .minimumScaleFactor is required
button.titleLabel!.minimumScaleFactor = 0.05
button.titleLabel!.baselineAdjustment = .alignCenters // I think it keeps it centered vertically
button.contentEdgeInsets = UIEdgeInsets(top: 5, left: 10, bottom: 5, right: 10); // set margins
stackView.addArrangedSubview(button)
theButtons.append(button)
}
}
func makeButtonWithText(text:String) -> UIButton {
let myButton = UIButton(type: UIButton.ButtonType.system)
//Set a frame for the button. Ignored in AutoLayout/ Stack Views
myButton.frame = CGRect(x: 30, y: 30, width: 150, height: 100)
// background color - light blue
myButton.backgroundColor = UIColor(red: 0.255, green: 0.561, blue: 0.847, alpha: 1)
//State dependent properties title and title color
myButton.setTitle(text, for: UIControl.State.normal)
myButton.setTitleColor(UIColor.white, for: UIControl.State.normal)
// set the font to dynamically size
myButton.titleLabel!.font = myButton.titleLabel!.font.withSize(70)
myButton.contentHorizontalAlignment = .center // align center
return myButton
}
func actualFontSize(for aLabel: UILabel) -> CGFloat {
// label must have text, must have .minimumScaleFactor and must have .adjustsFontSizeToFitWidth == true
guard let str = aLabel.text,
aLabel.minimumScaleFactor > 0.0,
aLabel.adjustsFontSizeToFitWidth
else { return aLabel.font.pointSize }
let attributes = [NSAttributedString.Key.font : aLabel.font]
let attStr = NSMutableAttributedString(string:str, attributes:attributes as [NSAttributedString.Key : Any])
let context = NSStringDrawingContext()
context.minimumScaleFactor = aLabel.minimumScaleFactor
_ = attStr.boundingRect(with: aLabel.bounds.size, options: .usesLineFragmentOrigin, context: context)
return aLabel.font.pointSize * context.actualScaleFactor
}
}
Result:

UINavigationBar not loading correctly

I know there are a lot of questions out there on this topic; however, none of those answers have helped me and I have tried so many ways of going about solving this. My problem is that my bar button will not show up, originaly, but when the viewcontroller is presented later on in the app it will show up then but the navigation title won't show up. I'm not sure why that is, but I believe it has something to do with the SwipeNavigationController framework that I'm using. My goal is to have the button show up as it's supposed to when the user swipes left to get to that view, and also when the view is later called and presented to look the same. The code for adding the navItem is below:
let cameraBarButton = UIBarButtonItem(image: #imageLiteral(resourceName: "cameraIcon"), style: .plain, target: self, action: #selector(goToCamera))
navigationItem.rightBarButtonItem = cameraBarButton
Please look at this other post to get a little better understanding of the framework. As well as here is the code on how I set up the navigation bar:
override func viewDidLoad() {
super.viewDidLoad()
setupView()
let barHeight: CGFloat = UIApplication.shared.statusBarFrame.size.height
let displayWidth: CGFloat = self.view.frame.width
let displayHeight: CGFloat = self.view.frame.height
messagesTableView = UITableView(frame: CGRect(x: 0, y: barHeight, width: displayWidth, height: displayHeight - barHeight))
messagesTableView.register(BlankCell.self, forCellReuseIdentifier: blankCellID)
messagesTableView.dataSource = self
messagesTableView.delegate = self
self.view.addSubview(messagesTableView)
view.backgroundColor = UIColor.white
setupNavButtons()
setupNavBar()
showNoMessagesLabel()
navigationController?.isNavigationBarHidden = false
if #available(iOS 11.0, *) {
navigationController?.navigationBar.prefersLargeTitles = true
self.navigationController?.navigationBar.largeTitleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.white]
} else {
// Fallback on earlier versions
}
}
override func viewWillAppear(_ animated: Bool) {
if #available(iOS 11.0, *) {
navigationController?.navigationBar.prefersLargeTitles = true
self.navigationController?.navigationBar.largeTitleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.white]
setupNavBar()
} else {
setupNavBar()
}
}
func setupNavBar() {
UIApplication.shared.statusBarStyle = .lightContent
self.navigationController?.isNavigationBarHidden = false
self.navigationController?.navigationBar.topItem?.title = "Messages"
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedStringKey.foregroundColor: UIColor.white]
self.navigationController?.navigationBar.barTintColor = UIColor.pinkNeonColor
self.navigationController?.navigationBar.tintColor = UIColor.white
}
func setupNavButtons() {
let cameraButton = UIButton(type: .system)
cameraButton.setImage(#imageLiteral(resourceName: "cameraIcon").withRenderingMode(.alwaysOriginal), for: .normal)
cameraButton.frame = CGRect(x: 0, y: 0, width: 34, height: 34)
navigationItem.leftBarButtonItem = UIBarButtonItem(customView: cameraButton)
}

find out the colour of the title inside a uibutton

You can set the colour of a button by doing setTitleColor(.white, for: .normal), which will set the button to white. But what if wanted to check the colour of the title inside the button?
I tried
let color = titleColor(for: .normal) and let color = titleLabel?.textColor but when I try to set that color elsewhere, nothing happens.
Use:
extension UIButton {
func loadingIndicator(show: Bool) {
let tag = 9876
var color: UIColor?
if show {
color = titleColor(for: .normal)
let indicator = UIActivityIndicatorView()
let buttonHeight = self.bounds.size.height
let buttonWidth = self.bounds.size.width
indicator.center = CGPoint(x: buttonWidth/2, y: buttonHeight/2)
indicator.tag = tag
indicator.color = UIColor.white
setTitleColor(.clear, for: .normal)
self.addSubview(indicator)
indicator.startAnimating()
} else {
if let indicator = self.viewWithTag(tag) as? UIActivityIndicatorView {
indicator.stopAnimating()
indicator.removeFromSuperview()
setTitleColor(color, for: .normal)
}
}
}
}
This is how you can get the color of the title of the UIButton with state and than you can used it anywhere you want.
let color = btn.titleColor(for: .normal);
label.textColor = color
As you can see my Button color is correctly used in Label TextColor in this image.
This is how you will do it in the Extension
extension UIButton {
func loadingIndicator(show: Bool) {
let tag = 9876
var color: UIColor?
if show {
color = titleColor(for: .normal)
let indicator = UIActivityIndicatorView()
let buttonHeight = self.bounds.size.height
let buttonWidth = self.bounds.size.width
indicator.center = CGPoint(x: buttonWidth/2, y: buttonHeight/2)
indicator.tag = tag
indicator.color = UIColor.white
setTitleColor(.clear, for: .normal)
self.addSubview(indicator)
indicator.startAnimating()
} else {
if let indicator = self.viewWithTag(tag) as? UIActivityIndicatorView {
indicator.stopAnimating()
indicator.removeFromSuperview()
setTitleColor(color, for: .normal)
}
}
}
}
And use it like that:
//This will set the saved color for `UIButton` title
btn.loadingIndicator(show: false)

Add subtitle under the title in navigation bar controller in Xcode

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
}

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.