Changing the app font globally in for SwiftUI views — a workaround

Recently, while working on a side project, I decided that I didn’t want to use the default font provided by Apple. With UIKit views, it’s pretty easy to change the font globally. However, with SwiftUI, it’s a bit tricky. SwiftUI uses structs for almost everything, which means that method swizzling is not going to work here. Instead, we have to override some static methods that are used as view modifiers. The only drawback is that if you don’t use .font() with your views, you will get the default iOS font. Regardless, the process is pretty straightforward: Override Font variables and the two methods that are used to set the fonts.

static func system(Font.TextStyle, design: Font.Design?, weight: Font.Weight?) -> Font
//Gets a system font that uses the specified style, design, and weight.
static func system(size: CGFloat, weight: Font.Weight?, design: Font.Design?) -> Font
//Specifies a system font to use, along with the style, weight, and any design parameters you want applied to the text.

For more information you can visit: https://developer.apple.com/documentation/swiftui/font
I am using the NotoSans font from Google, but you can easily replace it with whatever font you want to use. Here is also an article from Apple about how to add font files to your Xcode project: https://developer.apple.com/documentation/swiftui/applying-custom-fonts-to-text/

Here is the complete gist:

import Foundation
import SwiftUI

extension Font {
    ///
    /// Use the closest weight if your typeface does not support a particular weight
    ///
    static private var regularFontName: String {
        "NotoSans-Regular"
    }
    
    static private var mediumFontName: String {
        "NotoSans-Regular"
    }
    
    static private var boldFontName: String {
        "NotoSans-Bold"
    }
    
    static private var semiBoldFontName: String {
        "NotoSans-Bold"
    }
    
    static private var extraBoldFontName: String {
        "NotoSans-ExtraBold"
    }
    
    static private var heavyFontName: String {
        "NotoSans-ExtraBold"
    }
    
    static private var lightFontName: String {
        "NotoSans-Light"
    }
    
    static private var thinFontName: String {
        "NotoSans-Light"
    }
    
    static private var ultraThinFontName: String {
        "NotoSans-Light"
    }
    
    /// Sizes
    private static var preferredSizeTitle: CGFloat {
        UIFont.preferredFont(forTextStyle: .title1).pointSize
    }
    
    private static var preferredLargeTitle: CGFloat {
        UIFont.preferredFont(forTextStyle: .largeTitle).pointSize
    }
        
    private static var preferredExtraLargeTitle: CGFloat {
        if #available(iOS 17.0, *) {
            UIFont.preferredFont(forTextStyle: .extraLargeTitle).pointSize
        } else {
            UIFont.preferredFont(forTextStyle: .largeTitle).pointSize
        }
    }
    
    private static var preferredExtraLargeTitle2: CGFloat {
        if #available(iOS 17.0, *) {
            UIFont.preferredFont(forTextStyle: .extraLargeTitle2).pointSize
        } else {
            UIFont.preferredFont(forTextStyle: .largeTitle).pointSize
        }
    }
    
    private static var preferredTitle1: CGFloat {
        UIFont.preferredFont(forTextStyle: .title1).pointSize
    }
    
    private static var preferredTitle2: CGFloat {
        UIFont.preferredFont(forTextStyle: .title2).pointSize
    }
    
    private static var preferredTitle3: CGFloat {
        UIFont.preferredFont(forTextStyle: .title3).pointSize
    }
    
    private static var preferredHeadline: CGFloat {
        UIFont.preferredFont(forTextStyle: .headline).pointSize
    }
    
    private static var preferredSubheadline: CGFloat {
        UIFont.preferredFont(forTextStyle: .subheadline).pointSize
    }
    
    private static var preferredBody: CGFloat {
        UIFont.preferredFont(forTextStyle: .body).pointSize
    }
    
    private static var preferredCallout: CGFloat {
        UIFont.preferredFont(forTextStyle: .callout).pointSize
    }
    
    private static var preferredFootnote: CGFloat {
        UIFont.preferredFont(forTextStyle: .footnote).pointSize
    }
    
    private static var preferredCaption: CGFloat {
        UIFont.preferredFont(forTextStyle: .caption1).pointSize
    }
    
    private static var preferredCaption2: CGFloat {
        UIFont.preferredFont(forTextStyle: .caption2).pointSize
    }
    
    /// Styles
    public static var title: Font {
        return Font.custom(regularFontName, size: preferredTitle1)
    }
    
    public static var title2: Font {
        return Font.custom(regularFontName, size: preferredTitle2)
    }
    
    public static var title3: Font {
        return Font.custom(regularFontName, size: preferredTitle3)
    }

    public static var largeTitle: Font {
        return Font.custom(regularFontName, size: preferredLargeTitle)
    }
    
    public static var body: Font {
        return Font.custom(regularFontName, size: preferredBody)
    }
    
    public static var headline: Font {
        return Font.custom(regularFontName, size: preferredHeadline)
    }
        
    public static var subheadline: Font {
        return Font.custom(regularFontName, size: preferredSubheadline)
    }
        
    public static var callout: Font {
        return Font.custom(regularFontName, size: preferredCallout)
    }
    
    public static var footnote: Font {
        return Font.custom(regularFontName, size: preferredFootnote)
    }
    
    public static var caption: Font {
        return Font.custom(regularFontName, size: preferredCaption)
    }
    
    public static var caption2: Font {
        return Font.custom(regularFontName, size: preferredCaption2)
    }
    
    public static func system(_ style: Font.TextStyle, design: Font.Design? = nil, weight: Font.Weight? = nil) -> Font {
        var size: CGFloat
        var font: String

        switch style {
        case .largeTitle:
            size = preferredLargeTitle
        case .title:
            size = preferredTitle1
        case .title2:
            size = preferredTitle2
        case .title3:
            size = preferredTitle3
        case .headline:
            size = preferredHeadline
        case .subheadline:
            size = preferredSubheadline
        case .body:
            size = preferredBody
        case .callout:
            size = preferredCallout
        case .footnote:
            size = preferredFootnote
        case .caption:
            size = preferredCaption
        case .caption2:
            size = preferredCaption2
        case .extraLargeTitle:
            size = preferredExtraLargeTitle
        case .extraLargeTitle2:
            size = preferredExtraLargeTitle2
        default:
            size = preferredBody
        }
                
        switch weight {
        case .bold:
            font = boldFontName
        case .regular:
            font = regularFontName
        case .heavy:
            font = heavyFontName
        case .light:
            font = lightFontName
        case .medium:
            font = mediumFontName
        case .semibold:
            font = semiBoldFontName
        case .thin:
            font = thinFontName
        case .ultraLight:
            font = ultraThinFontName
        default:
            font = regularFontName
        }
        
        return Font.custom(font, size: size)
    }
    
    public static func system(size: CGFloat, weight: Font.Weight = .regular, design: Font.Design = .default) -> Font {
        var font: String
        
        switch weight {
        case .bold:
            font = boldFontName
        case .regular:
            font = regularFontName
        case .heavy:
            font = heavyFontName
        case .light:
            font = lightFontName
        case .medium:
            font = mediumFontName
        case .semibold:
            font = semiBoldFontName
        case .thin:
            font = thinFontName
        case .ultraLight:
            font = ultraThinFontName
        default:
            font = regularFontName
        }
        
        return Font.custom(font, size: size)
    }
}

On Github: https://gist.github.com/maysamsh/c3b010b0039db2acd05de105af51c491

Let me know if you need any further adjustments!

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.