Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI - Override the system font with multiple styles

So SwiftUI gives us a nice .font() extension that lets us set the font size, style, weight, etc. of text. This mirrors CSS's font attributes nicely, and even comes with nice shorthands like .font(.title), .font(.body), .font(.caption).

CSS shines when it comes to custom fonts, allowing us to import font faces from one or more files that collectively define several weights and styles within the same family. You can then use that font everywhere automatically by declaring a font family at the top level of the document, and then all font declarations will use that font automatically.

iOS allows us to import custom fonts, but it seems to fall short when it comes to using them. I'm looking for a way for iOS to mirror CSS's behavior.

I have 10 font files for a single font family: 5 different weights (light, regular, semi-bold, bold, and extra-bold) each with a regular and italic style. I am able to instantiate UIFonts for these fonts, but I'm looking for a way to set this font family globally so that I can declare .font(.title, weight: .bold) and have it implicitly use my font family's bold variant with the title size.

I am aware of ways to globally override the font, but I am specifically looking for a way to use multiple fonts from the same family to have automatic bold and italic capabilities. If there is a nice SwiftUI way to do this, that would be preferred, but a UIKit solution is fine as well.

EDIT: Another thing I should have mentioned is that I would like the default font of every View to use my font even with no .font() modifier. So if I have

Text("Hello, world")

it should use my font with the default weight and size (I think this is .body).

like image 244
jchitel Avatar asked Dec 13 '22 08:12

jchitel


2 Answers

You can start be defining a custom ViewModifier to handle the logic of choosing the right type and size of a font:

struct MyFont: ViewModifier {
    
    @Environment(\.sizeCategory) var sizeCategory
    
    public enum TextStyle {
        case title
        case body
        case price
    }
    
    var textStyle: TextStyle

    func body(content: Content) -> some View {
       let scaledSize = UIFontMetrics.default.scaledValue(for: size)
       return content.font(.custom(fontName, size: scaledSize))
    }
    
    private var fontName: String {
        switch textStyle {
        case .title:
            return "TitleFont-oBld"
        case .body:
            return "MyCustomFont-Regular"
        case .price:
            return "MyCustomFont-Mono"
        }
    }
    
    private var size: CGFloat {
        switch textStyle {
        case .title:
            return 26
        case .body:
            return 16
        case .price:
            return 14
        }
    }
    
}

Inside it you define an enum for all the text styles you would like to reuse across your app. You can use the same names as native Font.TextStyle such as .title, .body or define your own such as .price in the example above.

For each case of TextStyle you have to declare the name of the font and its default size. If you include sizeCategory as in my example the font would adapt for dynamic type.

You can also define an extension on View to be able to apply this modifier in the same way as the native font(_ font: Font?).

extension View {
    
    func myFont(_ textStyle: MyFont.TextStyle) -> some View {
        self.modifier(MyFont(textStyle: textStyle))
    }
}

Now you can apply it like this:

Text("Any text will do").myFont(.title)
Text("£3.99").myFont(.price)

For the default Text without a modifier applied you could either use Mahdi BM's solution or define your own type:

struct MyText: View {
    
    let text: String
    
    init(_ text: String) {
        self.text = text
    }
    
    var body: some View {
        Text(text).myFont(.body)
    }
    
}
like image 147
LuLuGaGa Avatar answered Jan 12 '23 17:01

LuLuGaGa


You can override system's default environment font by using a .environment() modifier on top of a view. you can use it on top of your first view so it will be applied to all your views.

.environment(\.font, .custom("Your_Font_Name", size: theFontSizeYouPrefer))
like image 21
Mahdi BM Avatar answered Jan 12 '23 16:01

Mahdi BM