private let componentDimension: CGFloat = 40.0
private let componentCount: CGFloat = 5.0
private let componentActiveColor = UIColor.black
private let componentInactiveColor = UIColor.gray
func setup() {
//create 5 labels using for loop and store in array ///figure out how to add a number to it to identify
//creating custom control, using constraints, using a little bit of animation, uitextfielddelegate
for _ in 1...5 {
let label = UILabel()
addSubview(label)
for i in 0..<starArray.count {
starArray[i].tag = i
}
label.frame = label.frame.offsetBy(dx: 8.0, dy: 0)
label.frame.size = CGSize(width: componentDimension, height: componentDimension)
label.font = UIFont(name: "System", size: 32.0)
label.text = "✭"
if label.tag == 0 {
label.textColor = componentActiveColor
} else {
label.textColor = componentInactiveColor
}
starArray.append(label)
}
}
I am trying to create 5 labels that are equally spaced in a custom view. But I am unaware of how to equally space the labels in order to achieve that.
Here is a complete class code with stackView Starting point for you
import UIKit
class ViewController: UIViewController {
private lazy var stackView : UIStackView = {
let stack = UIStackView()
stack.axis = .horizontal
stack.spacing = 10
stack.distribution = .fillEqually
stack.alignment = .fill
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
override func viewDidLoad() {
super.viewDidLoad()
setUpCostraints()
for _ in 1...5 {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 30)
label.text = "✭"
if label.tag == 0 {
label.textColor = .red
} else {
label.textColor = .blue
}
stackView.addArrangedSubview(label)
}
}
override func viewDidLayoutSubviews() {
}
func setUpCostraints() {
view.addSubview(stackView)
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
stackView.heightAnchor.constraint(equalToConstant: 50).isActive = true
stackView.widthAnchor.constraint(equalToConstant: 200).isActive = true
}
}
I am trying to add an UIView with a label, and want to set constraints to place the label in the center horizontally as well as vertically. But the code doesn't place it as I want. Below is the code. The label is presented on the top left hand corner of the screen instead of the center of the screen.
class TestViewController: UIViewController {
var title = UIView()
override func viewDidLoad() {
super.viewDidLoad()
func presentTitle ()
}
func presentTitle () {
title.backgroundColor = UIColor.black
title.textColor = UIColor.white
title.font = UIFont(name: "Helvetica", size: 30)
title.textAlignment = .center
title.backgroundColor = .lightGray
title.numberOfLines = 1
title..text = vmTitleTransferredFromTaskAnalysisDetail!
self.view.addSubview(title)
title.translatesAutoresizingMaskIntoConstraints = false
title.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor).isActive = true
title.centerYAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerYAnchor).isActive = true
title.eadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
title.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
self.view = title
}
}
I am trying to implement XLPagerTabStrip in my application. All is good because I have followed the official Tutorial I have changed the color of my collectionView to red. When the app runs in the simulator I see that there is a blank space like image
I want to know how to remove that blank space? because it should be all red and how can I change the black line color?
this is my code:
override func viewDidLoad() {
super.viewDidLoad()
settings.style.buttonBarBackgroundColor = .red
settings.style.buttonBarItemBackgroundColor = .red
settings.style.selectedBarBackgroundColor = purpleInspireColor
settings.style.buttonBarItemFont = UIFont.systemFont(ofSize: 12, weight: UIFont.Weight.regular)
settings.style.selectedBarHeight = 0.5
settings.style.buttonBarMinimumLineSpacing = 0
settings.style.buttonBarItemTitleColor = .white
settings.style.buttonBarItemsShouldFillAvailiableWidth = true
settings.style.buttonBarLeftContentInset = 0
settings.style.buttonBarRightContentInset = 0
changeCurrentIndexProgressive = { [weak self] (oldCell: ButtonBarViewCell?, newCell: ButtonBarViewCell?, progressPercentage: CGFloat, changeCurrentIndex: Bool, animated: Bool) -> Void in
guard changeCurrentIndex == true else { return }
oldCell?.label.textColor = .white
newCell?.label.textColor = self?.purpleInspireColor
}
}
I solved my problem I forgot that this line super.viewDidLoad() should be at last of viewDidload method
I am in process of adding large title in navigation bar in one of the application. The issue is title is little long so I will require to add two lines in large title. How can I add large title with two lines in navigation bar?
This is not about default navigation bar title! This is about large title which is introduced in iOS 11. So make sure you add suggestions by considering large title. Thanks
Based in #krunal answer, this is working for me:
extension UIViewController {
func setupNavigationMultilineTitle() {
guard let navigationBar = self.navigationController?.navigationBar else { return }
for sview in navigationBar.subviews {
for ssview in sview.subviews {
guard let label = ssview as? UILabel else { break }
if label.text == self.title {
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.sizeToFit()
UIView.animate(withDuration: 0.3, animations: {
navigationBar.frame.size.height = 57 + label.frame.height
})
}
}
}
}
In the UIViewController:
override func viewDidLoad() {
super.viewDidLoad()
self.title = "This is a multiline title"
setupNavigationMultilineTitle()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
setupNavigationMultilineTitle()
}
And for setting font and color on the large title:
navigation.navigationBar.largeTitleTextAttributes = [NSAttributedStringKey.foregroundColor: .red, NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: 30)]
Get a navigation item subviews and locate UILabel from it.
Try this and see:
self.navigationController?.navigationBar.prefersLargeTitles = true
self.navigationController?.navigationItem.largeTitleDisplayMode = .automatic
self.title = "This is multiline title for navigation bar"
self.navigationController?.navigationBar.largeTitleTextAttributes = [
NSAttributedStringKey.foregroundColor: UIColor.black,
NSAttributedStringKey.font : UIFont.preferredFont(forTextStyle: .largeTitle)
]
for navItem in(self.navigationController?.navigationBar.subviews)! {
for itemSubView in navItem.subviews {
if let largeLabel = itemSubView as? UILabel {
largeLabel.text = self.title
largeLabel.numberOfLines = 0
largeLabel.lineBreakMode = .byWordWrapping
}
}
}
Here is result:
The linebreak solution seems to be problematic when there's a back button. So instead of breaking lines, I made the label auto adjust font.
func setupLargeTitleAutoAdjustFont() {
guard let navigationBar = navigationController?.navigationBar else {
return
}
// recursively find the label
func findLabel(in view: UIView) -> UILabel? {
if view.subviews.count > 0 {
for subview in view.subviews {
if let label = findLabel(in: subview) {
return label
}
}
}
return view as? UILabel
}
if let label = findLabel(in: navigationBar) {
if label.text == self.title {
label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.7
}
}
}
Then it needs to be called in viewDidLayoutSubviews() to make sure the label can be found, and we only need to call it once:
private lazy var setupLargeTitleLabelOnce: Void = {[unowned self] in
if #available(iOS 11.0, *) {
self.setupLargeTitleAutoAdjustFont()
}
}()
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let _ = setupLargeTitleLabelOnce
}
If there's any navigationController pop event back to this controller, we need to call it again in viewDidAppear(). I haven't found a better solution for this - there's a small glitch of label font changing when coming back from a pop event:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if #available(iOS 11.0, *) {
setupLargeTitleAutoAdjustFont()
}
}
You could try:
Create a custom UINavigationController
Add the protocol UINavigationBarDelegate to the class definition
Override the function navigationBar(_:shouldPush:)
Activate two lines mode using hidden variable item.setValue(true, forKey: "__largeTitleTwoLineMode")
Make navigationController.navigationBar.prefersLargeTitles = true
(Edit 7/13: I notice that this solution is not support scrollView, so now I'm in research)
I found a perfect solution on Swift5
but sorry for my poor English because I'm Japanese🇯🇵Student.
In case of 2 lines In case of 3 lines
At first, set navigation settings for largeTitle normally in viewDidLoad
//Set largeTitle
navigationItem.largeTitleDisplayMode = .automatic
navigationController?.navigationBar.prefersLargeTitles = true
navigationController?.navigationBar.largeTitleTextAttributes = [.font: UIFont.systemFont(ofSize: (fontSize + margin) * numberOfLines)]//ex) fontSize=26, margin=5, numberOfLines=2
//Set title
title = "multiple large\ntitle is working!"
It is most important point of this solution that font-size at largeTitleTextAttributes equals actual font-size(+margin) multiplied by number of lines.
Description image
Because, default specification of navigationBar attributes may be able to display only 1 line largeTitle.
Although, somehow, I did notice that in case of label-settings(the label which subview of subview of navigationBar) on direct, it can display any number of lines in 1 line of in case of navigationBar attributes.
So, we should do set big font in navigationbar attributes, and set small font in the label(subview of subview of navigationBar), and take into consideration the margins.
Do label settings direct in viewDidAppear like this:
//Find label
navigationController?.navigationBar.subviews.forEach({ subview in
subview.subviews.forEach { subsubview in
guard let label: UILabel = subsubview as? UILabel else { return }
//Label settings on direct.
label.text = title
label.font = UIFont.systemFont(ofSize: fontSize)
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.sizeToFit()
}
})
Therefore, in short, the solution at minimum code is given like this:
import UIKit
class ViewController: UIViewController {
private let fontSize: CGFloat = 26, margin: CGFloat = 5
private let numberOfLines: CGFloat = 2
override func viewDidLoad() {
super.viewDidLoad()
setUpNavigation()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
setMultipleLargeTitle()
}
private func setUpNavigation() {
//Set largeTitle
navigationItem.largeTitleDisplayMode = .automatic
navigationController?.navigationBar.prefersLargeTitles = true
navigationController?.navigationBar.largeTitleTextAttributes = [.font: UIFont.systemFont(ofSize: (fontSize + margin) * numberOfLines)]
//Set title
title = "multiple large\ntitle is working!"
}
private func setMultipleLargeTitle() {
//Find label
navigationController?.navigationBar.subviews.forEach({ subview in
subview.subviews.forEach { subsubview in
guard let label: UILabel = subsubview as? UILabel else { return }
//Label settings on direct.
label.text = title
label.font = UIFont.systemFont(ofSize: fontSize)
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.sizeToFit()
}
})
}
}
thank you for reading :)
Swift 4 : Multi line even though the sentence is only short
title = "You're \nWelcome"
for navItem in(self.navigationController?.navigationBar.subviews)! {
for itemSubView in navItem.subviews {
if let largeLabel = itemSubView as? UILabel {
largeLabel.text = self.title
largeLabel.numberOfLines = 0
largeLabel.lineBreakMode = .byWordWrapping
}
}
}
If anyone looking for Title Lable Not Large Title, then below code is working.
Swift 5.X
func setMultilineNavigationBar(topText: String, bottomText : String) {
let topTxt = NSLocalizedString(topText, comment: "")
let bottomTxt = NSLocalizedString(bottomText, comment: "")
let titleParameters = [NSAttributedString.Key.foregroundColor : UIColor.white,
NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16, weight: .semibold)]
let subtitleParameters = [NSAttributedString.Key.foregroundColor : UIColor.white,
NSAttributedString.Key.font : UIFont.systemFont(ofSize: 13, weight: .regular)]
let title:NSMutableAttributedString = NSMutableAttributedString(string: topTxt, attributes: titleParameters)
let subtitle:NSAttributedString = NSAttributedString(string: bottomTxt, attributes: subtitleParameters)
title.append(NSAttributedString(string: "\n"))
title.append(subtitle)
let size = title.size()
let width = size.width
guard let height = navigationController?.navigationBar.frame.size.height else {return}
let titleLabel = UILabel(frame: CGRect.init(x: 0, y: 0, width: width, height: height))
titleLabel.attributedText = title
titleLabel.numberOfLines = 0
titleLabel.textAlignment = .center
self.navigationItem.titleView = titleLabel
}
SWIFT 5 This UIViewController extension helped me. Scenario that I have is mixed with enabling and disabling large titles so FIRST ENABLE large title and then call this method. Call it in viewDidLoad, I have found bug with peeking back with swipe and then releasing touch, for some reason current navigation title become previous navigation title
extension UIViewController {
/// Sets two lines for navigation title if needed
/// - Parameter animated: used for changing titles on one controller,in that case animation is off
func multilineNavTitle(_ animated:Bool = true) {
if animated {
// setting initial state for animation of title to look more native
self.navigationController?.navigationBar.transform = CGAffineTransform.init(translationX: .screenWidth/2, y: 0)
self.navigationController?.navigationBar.alpha = 0
}
//Checks if two lines is needed
if self.navigationItem.title?.forTwoLines() ?? false {
// enabling multiline
navigationItem.setValue(true,
forKey: "__largeTitleTwoLineMode")
} else {
// disabling multiline
navigationItem.setValue(false,
forKey: "__largeTitleTwoLineMode")
}
// laying out title without animation
UIView.performWithoutAnimation {
self.navigationController?.navigationBar.layoutSubviews()
self.navigationController?.view.setNeedsLayout()
self.navigationController?.view.layoutIfNeeded()
}
if animated {
//animating title
UIView.animate(withDuration: 0.3) {
self.navigationController?.navigationBar.transform = CGAffineTransform.identity
self.navigationController?.navigationBar.alpha = 1
}
}
}
}
fileprivate extension String {
/// Checks if navigation title is wider than label frame
/// - Returns: `TRUE` if title cannot fit in one line of navigation title label
func forTwoLines() -> Bool {
let fontAttributes = [NSAttributedString.Key.font: SomeFont]
let size = self.size(withAttributes: fontAttributes)
return size.width > CGFloat.screenWidth - 40 //in my case
}
}
Just create a custom navigation controller. Rest will be handled by the OS itself
class MyNavigationViewController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
navigationBar.delegate = self
}
}
extension MyNavigationViewController: UINavigationBarDelegate {
func navigationBar(_ navigationBar: UINavigationBar, shouldPush item: UINavigationItem) -> Bool {
item.setValuesForKeys([
"__largeTitleTwoLineMode": true
])
return true
}
}
viewController.navigationItem
.setValuesForKeys(["__largeTitleTwoLineMode": true])
WARNING: This method does not work on older OS versions
I am setting a custom view as titleView of the navigation. When viewcontroller appear its title view comes at left side for a moment and then move to center,what could be wrong? I am using the following code
let itemImgs: [UIImage] = [UIImage(named: "MORE_Location")!, UIImage(named: "MORE_Department")!, UIImage(named: "By_Teams")!, UIImage(named: "MORE_Status")!]
self.navigationController?.navigationBar.isTranslucent = false
self.navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
menuView = BTNavigationDropdownMenu(navigationController: self.navigationController, containerView: self.navigationController!.view, title: AppMessage.EDEmployeePeople, items: items as [AnyObject], itemImgs: itemImgs)
menuView.cellHeight = 60
menuView.cellBackgroundColor = UIColor.red
menuView.cellSelectionColor = UIColor.clear
menuView.cellSeparatorColor = UIColor.clear
menuView.shouldKeepSelectedCellColor = false
menuView.cellTextLabelColor = UIColor.white
menuView.shouldChangeTitleText = false
menuView.cellTextLabelFont = UIFont(name: "Helvetica", size: 17)
if appNeedsAutoResize
{
menuView.cellTextLabelFont = UIUtils.getFontForApproprieteField(.subHeadline).font
}
menuView.cellTextLabelAlignment = .left // .Center // .Right // .Left
menuView.arrowPadding = 15
menuView.animationDuration = 0.5
menuView.maskBackgroundColor = UIColor.clear
menuView.maskBackgroundOpacity = 0.3
menuView.didSelectItemAtIndexHandler = {(indexPath: Int) -> () in
print("Did select item at index: \(indexPath)")
if indexPath == 3
{
let byStatusViewController = ByStatusViewController(nibName: "ByStatusViewController", bundle: nil)
//UIUtils.pushViewWhenHideBottom(self, anotherVC: byStatusViewController)
self.navigationController?.pushViewController(byStatusViewController, animated: true)
}
else
{
let dropVC = DepartmentViewController(nibName: "DepartmentViewController", bundle: nil)
switch indexPath
{
case 0:
dropVC.employeeGroupInfo = EmployeeGroupInfo.locationInfo
break
case 1:
dropVC.employeeGroupInfo = EmployeeGroupInfo.departmentInfo
break
default:
dropVC.employeeGroupInfo = EmployeeGroupInfo.teamInfo
break
}
// UIUtils.pushViewWhenHideBottom(self, anotherVC: dropVC)
self.navigationController?.pushViewController(dropVC, animated: true)
}
}
self.navigationItem.titleView = menuView
}
Try to add constraints of autoresizing masks to your menu view to keep the view centered.
E.g.
menuView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin, .flexibleBottomMargin]
I had this problem and changing my function that customize my navigationItem from viewWillAppear() to viewDidLoad() resolved