42

How can I change the font of the back button for my navigation bar.

The back button is either "back" or the title from the previous view controller.

I thought this viewDidLoad would work:

navigationController?.navigationItem.leftBarButtonItem?.setTitleTextAttributes([NSFontAttributeName: UIFont(name: "FONTNAME", size: 20)!], forState: UIControlState.Normal)

but the leftBarButton? optional returns nil.

Eric Aya
  • 69,473
  • 35
  • 181
  • 253
Kyle Goslan
  • 10,748
  • 7
  • 26
  • 41
  • Do you want the font to be the same for every bar button item in the app or just the back button? – Ian Dec 20 '14 at 19:27
  • I'm assuming you're going to offer setting it in the app delegate, both solutions would actually be useful to know :) – Kyle Goslan Dec 20 '14 at 19:28

6 Answers6

72

If you need to change font style entirely across you app (aka. each navigation button), preferred method is to use UIBarButtonItem.appearance() proxy.

Sample code snippet would look like this:

SWIFT 3.0+

//make sure font name u use below *actually* exists in the system !
//if it doesn't app will crash because we're force unwrapping an optional (see below) !!!
let customFont = UIFont(name: "customFontName", size: 17.0)!
UIBarButtonItem.appearance().setTitleTextAttributes([NSFontAttributeName: customFont], for: .normal)

It is wise to put this code snippet somewhere at the beginning of your AppDelegate.swift file because font stylization has to happen each time the app launches. Also, you're safe to put this code in any other place (eg. Presenter class) where you do your UI stylization and customization. As soon as this code gets executed, all your BarButtons will be customized thereafter.


BUT as a true Swift , you should first optionally unwrap your font and eventually fallback to system font if it's not found or any other possible issue arise during font loading.

var textAttributes: [String:Any]

//custom font setup
let fontColor = UIColor.purple
let barItemCustomFont = UIFont(name: "", size: 14)  //note we're not forcing anything here

//can we use our custom font 
if let customFont = barItemCustomFont {
    //hooray we can use our font 
    textAttributes = [NSForegroundColorAttributeName: fontColor, NSFontAttributeName: customFont]
} else {
    // not found -> omit setting font name and proceed with system font
    textAttributes = [NSForegroundColorAttributeName: fontColor]
}

//finally
UIBarButtonItem.appearance().setTitleTextAttributes(textAttributes, for: .normal)

Real world example

In a real app you would usually need to customize both UINavigationBar and UIBarButton font style (aside from other parameters) to make them visually consistent. Here is handy stylizeNavigationFontOfMyAmazingApp function you can use:

    func stylizeNavigationFontOfMyAmazingApp() {
        //custom font
        let customFont = UIFont(name: "someFancyFont", size: 16)!  //note we're force unwrapping here

        //navigation bar coloring:
        UINavigationBar.appearance().tintColor = UIColor.white
        UINavigationBar.appearance().barTintColor = UIColor.blue

        //unique text style:
        var fontAttributes: [String: Any]
        fontAttributes = [NSForegroundColorAttributeName: UIColor.red, NSFontAttributeName: customFont]

        //navigation bar & navigation buttons font style:
        UINavigationBar.appearance().titleTextAttributes = fontAttributes
        UIBarButtonItem.appearance().setTitleTextAttributes(fontAttributes, for: .normal)

   }

Advantages of using appearance() proxy

  • Two liner code snippet for stylizing every UIBarButtonItem in your app/project.
  • You can mix appearance() proxies of several classes to get truly unique visual style
  • If you need further customization control you can re-customize each button further (e.g. placing custom views, button, images, etc) - on specific instance of UIBarButtonItem.

Reference

Apple Docs on appearance protocol.

Vexy
  • 1,274
  • 1
  • 12
  • 17
32

Just tested your code and it seems the reason that line is returning nil is actually because name: "FONTNAME" returns nil. So if you set that name attribute to a valid font name, the code should run without an error -- even if navigationController?.navigationItem.leftBarButtonItem is explicitly set to nil.

But regardless, also as I've seen through testing, this line won't give you the result you apparently want. The leading navigationController optional shouldn't be there since it accesses your UINavigationController and not the current view. Simply use the UIViewController's own navigationItem property to access its leftBarButtonItem directly, ex:

let backButton = UIBarButtonItem(title: "< Back", style: UIBarButtonItemStyle.Plain, target: self, action: "goBack")
navigationItem.leftBarButtonItem = backButton
navigationItem.leftBarButtonItem?.setTitleTextAttributes([NSFontAttributeName: UIFont(name: "Chalkduster", size: 20)!], forState: UIControlState.Normal)

Edit: From the comment you posted under this answer, it seems as if you don't actually want to set the leftBarButtonItem but the backBarButtonItem because you don't want to implement a custom action beyond going back to the previous view controller.

So in that previous view controller (i.e. the view before you want to show your custom back button item), you can set your custom back button without an action like so:

let backButton = UIBarButtonItem(title: "< Back", style: UIBarButtonItemStyle.Plain, target: self, action: nil)
backButton.setTitleTextAttributes([NSFontAttributeName: UIFont(name: "Chalkduster", size: 20)!], forState: UIControlState.Normal)
navigationItem.backBarButtonItem = backButton
Lyndsey Scott
  • 37,080
  • 10
  • 92
  • 128
  • Hey thanks for your answer, I know having an invalid font name would cause a crash, I just put that there for a placeholder. I figured I would just be able to edit the setTitleTextAttributes() on the button that was already there, as that already navigates back to the previous view, your solution works, but now I have to go and set up the action for it, which seems a bit long winded for essentially what is already there... – Kyle Goslan Dec 20 '14 at 19:40
  • Then don't use `leftBarButtonItem`. Use `backBarButtonItem`. I'll update to explain in a sec... – Lyndsey Scott Dec 20 '14 at 19:43
22

If you want to apply this change to all of your application you can use below code:

Swift 4

I've added these lines to AppDelegate.swift in application function :

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
       UINavigationBar.appearance().titleTextAttributes = [
            NSAttributedStringKey.font: UIFont(name: "IranSansMobile", size: 20)!
        ]

        UIBarButtonItem.appearance().setTitleTextAttributes([NSAttributedStringKey.font: UIFont(name: "IranSansMobile", size: 15)!], for: UIControlState.normal)

  return true
}

Swift 3

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

    UINavigationBar.appearance().titleTextAttributes = [
        NSFontAttributeName: UIFont(name: "IranSansMobile", size: 20)!
    ]

    UIBarButtonItem.appearance().setTitleTextAttributes([NSFontAttributeName: UIFont(name: "IranSansMobile", size: 15)!], for: UIControlState.normal)


    return true
}
Milad Faridnia
  • 9,113
  • 13
  • 65
  • 78
  • 3
    It's necessary to also set the attribute `for: .selected` as well, or the button will show in the original font while pressed – Serdnad Aug 03 '18 at 20:09
  • 2
    Also need `for: .highlighted` to keep the same font during touch down. – Gobot Dec 24 '18 at 03:45
8

For iOS 13, Swift 5.1:

let fontAttr = [NSAttributedString.Key.font: \*your font*\]

let buttonAppearance = UIBarButtonItemAppearance()
buttonAppearance.normal.titleTextAttributes = fontAttr

let navbarAppearance = UINavigationBarAppearance()
navbarAppearance.buttonAppearance = buttonAppearance
UINavigationBar.appearance().standardAppearance = navbarAppearance
Sergey Gamayunov
  • 658
  • 7
  • 15
  • This worked for me! For anyone seeing broken fonts on nav bar buttons for iOS 13, wrapping this in an #ifavailable clause did the trick for me, and drop in the old way of doing it in the else block (For iOS 12 and below) – Orlando G Rodriguez Oct 08 '19 at 19:09
  • This worked but it overrides my title font any ideas? – Nico S. Oct 16 '19 at 12:42
  • 3
    Thanks, I wondered why setTitleTextAttribute wasn't working in my > iOS13 code. `buttonAppearance.normal.titleTextAttributes = fontAttr` was what I needed. – mushcraft Sep 22 '20 at 09:04
  • didn't work for me. in which lifecycle method do we need to write this? – Deepak Thakur Sep 30 '22 at 07:29
3

When you are sure you've already set a UIBarButtonItem you can do:

self.navigationItem.leftBarButtonItem!.title = "myTitle"

To change e.g. the color you can do the following:

self.navigationItem.leftBarButtonItem!.tintColor = UIColor.redColor()

When you haven't set it in your storyboard you can do:

self.navigationItem.leftBarButtonItem = UIBarButtonItem()

For changing the font, do the following:

self.navigationItem.leftBarButtonItem!.setTitleTextAttributes([NSFontAttributeName: UIFont(name: "FONTNAME", size: 20)!], forState: .Normal)

Maybe I should mention that you mustn't use self.navigationController because when setting the navigationController's buttons they aren't displayed on the screen so as a result the buttons which are on the screen and had been set in the storyboard have to be self.navigationItem...

borchero
  • 5,562
  • 8
  • 46
  • 72
  • Thanks for your reply, I've not the UIBarButtonItem in this view, its being inherited from the previous view in this case. Even when using the last two lines of your answer (without self) its still returning nil – Kyle Goslan Dec 20 '14 at 19:26
  • Actually it cannot happen that it is returning nil when you set it manually. You said it's inherited from the previous view - could you give us that info? – borchero Dec 20 '14 at 19:46
3

SWIFT 3

    UINavigationBar.appearance().titleTextAttributes = [NSFontAttributeName: UIFont(name: "Open Sans", size: 15)!,NSForegroundColorAttributeName: UIColor.white]
    UIBarButtonItem.appearance().setTitleTextAttributes([NSFontAttributeName: UIFont(name: "Open Sans", size: 15)!], for: UIControlState.normal)
Ahmed Safadi
  • 4,402
  • 37
  • 33