macosswiftuifont-sizedynamictype

How to make font size customizable in a macOS app


I’m creating a cross-platform app, iOS and macOS (not MacOS Catalyst), in SwiftUI.

I’m supporting Dynamic Type through the .font(.title) .font(.caption) etc. modifiers.

This works great on iOS, however MacOS doesn’t support Dynamic Type.

I know users can change the font size on their Mac system wide through the display settings, but I would like to add an app preference where users can change the font size only for my app. Something similar like, for example, the mail app offers (Format > Style > Bigger)

I’m quite an experienced iOS dev,but this is my first MacOS app and my first real SwiftUI-only app. I’ve searched google and SO for half a day but can not find a way to do this.

Can this be done? Perhaps there is some kind of modifier to scale the font with a factor (and I could make that factor a global variable that would increase or decrease by the user changing this setting), or perhaps there is another way?


Solution

  • Here is an approach with a view modifier .dynamicFont that takes a factor.

    Be aware that the standard sizes (for factor 1.0) reflect the standard macOS styles. You might cover iOS sizes as well there if you want to use it cross platform.

    enter image description here

    struct ContentView: View {
        var body: some View {
    
            HStack {
                GroupBox {
                    VStack(alignment: .leading) {
                        
                        Text("Hello, World!")
                            .font(.title)
                        
                        Text("Hello, World!")
                            .font(.body)
                        
                        Text("Hello, World!")
                            .font(.caption)
                    }
                }
                
                // example use
                GroupBox {
                    VStack(alignment: .leading) {
                        
                        Text("Hello, World!")
                            .dynamicFont(.title, factor: 1.2)
                        
                        Text("Hello, World!")
                            .dynamicFont(.body, factor: 1.2)
                        
                        Text("Hello, World!")
                            .dynamicFont(.caption, factor: 1.2)
                    }
                }
            }
        }
    }
    
    
    
    
    enum DynamicFontStyle {
        case largeTitle
        case title
        case headline
        case body
        case caption
        // ...
        
        // the standard sizes and weights are for MacOS!! and taken from
        // https://developer.apple.com/design/human-interface-guidelines/foundations/typography/#specifications
        
        func specs(factor: Double) -> Font {
            switch self {
            case .largeTitle:
                return Font.system(size: 26 * factor, weight: .regular)
            case .title:
                return Font.system(size: 22 * factor, weight: .regular)
            case .headline:
                return Font.system(size: 13 * factor, weight: .bold)
            case .body:
                return Font.system(size: 13 * factor, weight: .regular)
            case .caption:
                return Font.system(size: 10  * factor, weight: .regular)
            }
            // ...
        }
    }
    
    
    // View expension for ease of use
    extension View {
        func dynamicFont(_ style: DynamicFontStyle = .body, factor: Double = 1) -> some View {
            self
                .modifier(DynamicFontModifier(style: style, factor: factor))
        }
    }
    
    
    // View Modifier
    struct DynamicFontModifier: ViewModifier  {
        
        let style: DynamicFontStyle
        let factor: Double
        
        func body(content: Content) -> some View {
            content
                .font(style.specs(factor: factor))
        }
    }