Dynamically resize navigation bar title during Large Titles mode - ios

How do I dynamically resize my navigation bar title while in iOS 11's Large Title mode?
Previously you could define your own UILabel as the titleView and have it resize. All it does now is add another titleView on top.
override func viewDidLoad() {
super.viewDidLoad()
self.title = "A very long title. Really long. Dont truncate me though. I want everyone to see me."
let frame = CGRect(x: 0, y: 0, width: 200, height: 40)
let tlabel = UILabel(frame: frame)
tlabel.text = self.title
tlabel.textColor = UIColor.black
tlabel.font = UIFont.boldSystemFont(ofSize: 17)
tlabel.backgroundColor = UIColor.clear
tlabel.adjustsFontSizeToFitWidth = true
tlabel.textAlignment = .center
self.navigationItem.titleView = tlabel
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.largeTitleDisplayMode = .automatic
}

Related

Custom Navigation Title in iOS 12

I am trying to implement a custom Navigation Title on an iOS app.
The StoryBoard looks like this:
The place that I want to have the custom Navigation Title is the last view ( the message view ), and because I use an image and text this means that I need to have custom width and height. By needing this if I do in viewDidLoad:
let rect = CGRect(x: 0, y:0, width: 150, height: 88)
titleView = UIView(frame: rect)
......
titleView?.addSubview(imageView)
......
titleView?.addSubview(label)
navigationItem.titleView = titleView
The height of the title is blocked to 44pt.
But how I managed to do it is adding the subViews to the navigation bar:
var navigationBar: MessagesNavigationBar? {
guard let navigationBar = navigationController?.navigationBar as? MessagesNavigationBar else {
return nil
}
return navigationBar
}
And in viewDidLoad
let rect = CGRect(x: 0, y:0, width: 150, height: 88)
titleView = UIView(frame: rect)
......
titleView?.addSubview(imageView)
......
titleView?.addSubview(label)
navigationBar?.addSubview(titleView!)
But the problem is that I have to remove the subviews when I leave the view, otherwise whatever I add there will be present in the table view as well.
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if navigationBar != nil {
titleView?.removeFromSuperview()
}
}
Which kinda makes me feel that I'm not doing the right thing and I find difficult to add a fade out animation to those subViews when I leave the conversation. (i.e. native messages app on iOS).
So what is the right way of creating a custom Title Navigation Bar in iOS 12?
Scenes
Creating your custom titleView and assigning it to navigationItem.titleView is what you want. On older systems (pre iOS 11) you just might need to call sizeToFit() on the titleView.
This way you can create this titleView
Swift
override func viewDidLoad() {
super.viewDidLoad()
let imageView = UIImageView()
NSLayoutConstraint.activate([
imageView.heightAnchor.constraint(equalToConstant: 20),
imageView.widthAnchor.constraint(equalToConstant: 20)
])
imageView.backgroundColor = .red
let titleLabel = UILabel()
titleLabel.text = "Custom title"
let hStack = UIStackView(arrangedSubviews: [imageView, titleLabel])
hStack.spacing = 5
hStack.alignment = .center
navigationItem.titleView = hStack
}
Obj-C
- (void)viewDidLoad {
[super viewDidLoad];
UIImageView *imageView = [[UIImageView alloc] init];
[NSLayoutConstraint activateConstraints:#[
[imageView.heightAnchor constraintEqualToConstant:20],
[imageView.widthAnchor constraintEqualToConstant:20]
]];
imageView.backgroundColor = [UIColor redColor];
UILabel *titleLabel = [[UILabel alloc] init];
titleLabel.text = #"Custom title";
UIStackView *hStack = [[UIStackView alloc] initWithArrangedSubviews:#[imageView, titleLabel]];
hStack.spacing = 5;
hStack.alignment = UIStackViewAlignmentCenter;
self.navigationItem.titleView = hStack;
}
You might also need to have the right set of autolayout constraints or use UIStackView.
These lines have no effect on the size of a title view:
let rect = CGRect(x: 0, y:0, width: 150, height: 88)
titleView = UIView(frame: rect)
Instead (or in addition) give your title view a width constraint and a height constraint. That is how the runtime knows what size you want.

Use large navigation title and fit text

I have issue with navigation bar title. First of all I am using large titles with:
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.largeTitleDisplayMode = .always
Is it possible to have a large title with resizable fonts?
There is one property called largeTitleTextAttributes. I think it will solve your problem.
Write the following code in view controller's viewDidLoad method.
self.navigationItem.largeTitleDisplayMode = .always
self.navigationController?.navigationBar.prefersLargeTitles = true
self.navigationController?.navigationBar.largeTitleTextAttributes = [NSAttributedStringKey.font : UIFont.systemFont(ofSize: 100)]
You have to resize your title view. Try this
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(name: "Helvetica-Bold", size: 30.0)
tlabel.backgroundColor = UIColor.clear
tlabel.adjustsFontSizeToFitWidth = true
tlabel.textAlignment = .center;
self.navigationItem.titleView = tlabel

Why setting titleView lowers header text?

I'm trying to dynamically set my navigation bar's text so that the header text always fits. I'm acccomplishing that like this:
// Pet's Day text "Joy's Day"
if let range = currentPet.range(of: "_") {
let petsName = currentPet[range.upperBound..<currentPet.endIndex]
let deviceWidth = UIScreen.main.bounds.size.width
let titleLabel = UILabel(frame: CGRect(x: 0, y: 0, width: deviceWidth, height: 40))
titleLabel.text = "\(petsName)'s Day"
titleLabel.font = UIFont.systemFont(ofSize: 30)
titleLabel.backgroundColor = UIColor.clear
titleLabel.textColor = UIColor.white
titleLabel.adjustsFontSizeToFitWidth = true
titleLabel.minimumScaleFactor = 0.5
titleLabel.textAlignment = .center
self.navBar.topItem?.titleView = titleLabel
}
However, as seen by this picture, this lowers the header text below its natural height:
The navigation bar on the left is from one of my app's other views, while the one on the right is the one I'm setting.
Both of these navigation bars are navigation bars that I've dragged in and made the prompt equal to an empty string to increase their height:
Can anybody please help me implement my code above so that it doesn't drop down the header text?
**Edit: Here are screenshots from Xcode's debug hierarchy:
This is the normal navigation bar:
This is the one I'm setting:
You need to set the baseline, by default is Align Baseline, you need to change to Align Centers
Code example:
class TestViewController: UIViewController {
#IBOutlet weak var navBar: UINavigationBar!
override func viewDidLoad() {
super.viewDidLoad()
// Change the height of the navbar
self.navBar.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 68)
let deviceWidth = UIScreen.main.bounds.size.width
let titleLabel = UILabel(frame: CGRect(x: 0, y: 0, width: deviceWidth, height: 40))
titleLabel.text = "Log Events" // Change to Joy's Day and check the result
titleLabel.font = UIFont.systemFont(ofSize: 30)
titleLabel.baselineAdjustment = .alignBaselines
titleLabel.textColor = UIColor.white
titleLabel.adjustsFontSizeToFitWidth = true
titleLabel.minimumScaleFactor = 0.5
titleLabel.textAlignment = .center
self.navBar.topItem?.titleView = titleLabel
}
}

How to make iOS navigationItem.titleView align to the left?

navigationItem.hidesBackButton = true
navigationItem.leftBarButtonItem = nil
head = UIView()
head.frame = CGRectMake(0, 0, 200, 44)
head.frame.origin.x = CGFloat(0)
navigationItem.titleView = head
I attempt to align the titleView to the left, but it still remains in the middle.
Try this:
let title = UILabel()
title.text = "TITLE"
let spacer = UIView()
let constraint = spacer.widthAnchor.constraint(greaterThanOrEqualToConstant: CGFloat.greatestFiniteMagnitude)
constraint.isActive = true
constraint.priority = .defaultLow
let stack = UIStackView(arrangedSubviews: [title, spacer])
stack.axis = .horizontal
navigationItem.titleView = stack
The idea is that main view (in this case title) will take all the space it needs and spacer view will take all the free space left.
I figured it out.
I just need to set my custom UIView as the leftBarButtonItem.
I had a similar requirement of adding the Title along with the subtitle to the left of the navbar. I couldn't achieve it with a TitleView since it cannot be aligned left.
So I took #TIMEZ and #Wain's answers, along with responses from the thread here and added a complete answer, in case it helps anyone :
let titleLabel = UILabel()
titleLabel.text = "Pillars"
titleLabel.textAlignment = .center
titleLabel.font = .preferredFont(forTextStyle: UIFont.TextStyle.headline)
let subtitleLabel = UILabel()
subtitleLabel.text = "How did you do today?"
subtitleLabel.textAlignment = .center
subtitleLabel.font = .preferredFont(forTextStyle: UIFont.TextStyle.subheadline)
let stackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel])
stackView.distribution = .equalSpacing
stackView.alignment = .leading
stackView.axis = .vertical
let customTitles = UIBarButtonItem.init(customView: stackView)
self.navigationItem.leftBarButtonItems = [customTitles]
You can't align a title view to the left. You can create a title view and add a subview positioned to its left. If you're looking to display in place of the back button then you should be using a bar button item instead of title view.
I don't think Apple wants you to do that. Navigation bars have a pretty specific purpose that often involves having something else in the top left corner like a Back button. You might be better off making a custom UIView or UIToolbar that looks like the navigation bar.
If it's a custom UIView, override intrinsicContentSize and return
CGSize(width: .greatestFiniteMagnitude, height: UIView.noIntrinsicMetric)
This will stretch the view to the entire width between left and right bar button items.
You can constraint the titleView to the navigationBars leftAnchor.
private func setupNavigationBarTitleView() {
let titleView = YourCustomTitleView()
navigationBarTitleView = titleView
navigationBarTitleView?.translatesAutoresizingMaskIntoConstraints = false
navigationItem.titleView = navigationBarTitleView
if let navigationBar = navigationController?.navigationBar {
NSLayoutConstraint.activate([
titleView.leadingAnchor.constraint(equalTo: navigationBar.leadingAnchor, constant: 16),
titleView.heightAnchor.constraint(equalToConstant: 36)
])
}
}
The simplest solution is to add low priority constraint for title width.
let titleLabel = UILabel()
titleLabel.textAlignment = .left
...
let c = titleLabe.widthAnchor.constraint(equalToConstant: 10000)
c.priority = .required - 1
c.isActive = true
navigationItem.titleView = titleLabel
Hey guys after trying most of the solutions above. I found that most of em still did not meet my prod requirements. Here is a solution I came up with after trying out different solutions.
func setLeftAlignTitleView(font: UIFont, text: String, textColor: UIColor) {
guard let navFrame = navigationController?.navigationBar.frame else{
return
}
let parentView = UIView(frame: CGRect(x: 0, y: 0, width: navFrame.width*3, height: navFrame.height))
self.navigationItem.titleView = parentView
let label = UILabel(frame: .init(x: parentView.frame.minX, y: parentView.frame.minY, width: parentView.frame.width, height: parentView.frame.height))
label.backgroundColor = .clear
label.numberOfLines = 2
label.font = font
label.textAlignment = .left
label.textColor = textColor
label.text = text
parentView.addSubview(label)
}
let navLabel = UILabel(frame: CGRect(x: 0, y: 0, width: view.frame.width - 32, height: view.frame.height))
navLabel.text = "Hi, \(CurrentUser.firstName)"
navLabel.textColor = UIColor.white
navLabel.font = UIFont.systemFont(ofSize: 20)
navigationItem.titleView = navLabel

Customize navigation bar by adding two labels instead of title in Swift

I am trying to add two labels in the place where the title is shown in navigation bar, but I am struggling to do so. It would be very nice if I could achieve this with storyboard but as I can see I cannot do it.
As I have seen I need to use navigationItem but I do not know how exactly to do that. If anyone have any example or if anyone could explain me more specifically how to do so would be wonderful.
And I need to mention that I am completely unfamiliar with Obj-C, so any help would need to be in Swift.
I am not sure if you can do it from the storyboard, but if you want to add two title labels, you can do the following in the viewDidLoad() method of the view controller for which you want the two titles:
if let navigationBar = self.navigationController?.navigationBar {
let firstFrame = CGRect(x: 0, y: 0, width: navigationBar.frame.width/2, height: navigationBar.frame.height)
let secondFrame = CGRect(x: navigationBar.frame.width/2, y: 0, width: navigationBar.frame.width/2, height: navigationBar.frame.height)
let firstLabel = UILabel(frame: firstFrame)
firstLabel.text = "First"
let secondLabel = UILabel(frame: secondFrame)
secondLabel.text = "Second"
navigationBar.addSubview(firstLabel)
navigationBar.addSubview(secondLabel)
}
In this way you can add as many subviews as you want to the navigation bar
Here's an implementation that uses a stack view instead, which also gives you some versatility with layout of the labels:
class ViewController: UIViewController {
lazy var titleStackView: UIStackView = {
let titleLabel = UILabel()
titleLabel.textAlignment = .center
titleLabel.text = "Title"
let subtitleLabel = UILabel()
subtitleLabel.textAlignment = .center
subtitleLabel.text = "Subtitle"
let stackView = UIStackView(arrangedSubviews: [titleLabel, subtitleLabel])
stackView.axis = .vertical
return stackView
}()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.titleView = titleStackView
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if view.traitCollection.horizontalSizeClass == .compact {
titleStackView.axis = .vertical
titleStackView.spacing = UIStackView.spacingUseDefault
} else {
titleStackView.axis = .horizontal
titleStackView.spacing = 20.0
}
}
}

Resources