Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Feature: Font Inspector #320

Copy link
Copy link
@MaatheusGois

Description

@MaatheusGois
Issue body actions

📋 Feature Description

Add a Font Inspector to DebugSwift that lists all fonts used in the current view, shows font families, weights, sizes, identifies custom vs system fonts, and previews Dynamic Type scaling.

🎯 Motivation

Typography consistency is essential for good UI. Developers need to:

  • Audit fonts used across screens
  • Identify font inconsistencies
  • Verify custom font loading
  • Test Dynamic Type scaling
  • Export typography tokens

Currently, developers manually inspect each text element or use external design tools, which is inefficient during development.

✨ Proposed Features

Core Functionality

  • Font Extraction: List all fonts in current view
  • Font Details: Family, weight, size, style
  • Usage Statistics: Most/least used fonts
  • Custom Font Detection: Identify custom vs system fonts
  • Dynamic Type Preview: See how text scales
  • Font Hierarchy: Show text style usage

Font Analysis

  • Consistency Check: Find similar sizes that should be unified
  • Accessibility Check: Verify minimum font sizes
  • Dynamic Type Support: Check adjustsFontForContentSizeCategory
  • Font Loading Status: Verify custom fonts loaded
  • Weight Distribution: Show font weight usage

Export Options

  • Swift Code: Font definitions
  • CSS: Font styles and sizes
  • Design Tokens: Typography system
  • Style Guide: Generate documentation

🎨 UI/UX Design

Font Inspector Screen

┌─────────────────────────────────────────┐
│ Font Inspector              [Inspect]    │
├─────────────────────────────────────────┤
│ Current Screen: Profile                  │
│ Total Fonts: 8                          │
├─────────────────────────────────────────┤
│ System Fonts (5)                        │
│                                          │
│ SF Pro Text - Regular                   │
│ • 17pt  Used 12 times                   │
│ • 15pt  Used 8 times                    │
│ • 14pt  Used 5 times                    │
│   Dynamic Type: ✅ 10/12 elements       │
│                                          │
│ SF Pro Text - Bold                      │
│ • 20pt  Used 3 times                    │
│ • 18pt  Used 2 times                    │
│   Dynamic Type: ✅ All elements         │
│                                          │
│ SF Pro Text - Semibold                  │
│ • 16pt  Used 4 times                    │
│   Dynamic Type: ⚠️ Not enabled          │
├─────────────────────────────────────────┤
│ Custom Fonts (3)                        │
│                                          │
│ Montserrat-Bold                         │
│ • 24pt  Used 1 time                     │
│   Status: ✅ Loaded                     │
│   Dynamic Type: ❌ Not supported        │
│                                          │
│ Montserrat-Regular                      │
│ • 16pt  Used 6 times                    │
│   Status: ✅ Loaded                     │
│   Dynamic Type: ❌ Not supported        │
├─────────────────────────────────────────┤
│ ⚠️ Issues (3)                           │
│ • 3 similar sizes found (16, 15, 17pt)  │
│ • Dynamic Type not enabled: 8 elements  │
│ • Small font size: 11pt (min: 11pt)    │
├─────────────────────────────────────────┤
│ Actions                                  │
│ [Preview Dynamic Type]                   │
│ [Export Typography]                      │
│ [Generate Style Guide]                   │
└─────────────────────────────────────────┘

Font Detail View

┌─────────────────────────────────────────┐
│ < Fonts    SF Pro Text - Regular        │
├─────────────────────────────────────────┤
│ Font Preview                            │
│ ┌─────────────────────────────────────┐ │
│ │ The quick brown fox jumps           │ │
│ │ over the lazy dog                   │ │
│ │ 0123456789                          │ │
│ └─────────────────────────────────────┘ │
├─────────────────────────────────────────┤
│ Font Information                         │
│ Family:    SF Pro Text                  │
│ Style:     Regular                      │
│ Weight:    400 (Regular)                │
│ Type:      System Font                  │
├─────────────────────────────────────────┤
│ Size Usage                              │
│ • 17pt - Body text (12 occurrences)    │
│ • 15pt - Secondary text (8)            │
│ • 14pt - Caption (5)                   │
├─────────────────────────────────────────┤
│ Usage Locations                          │
│ • UILabel "Welcome" (17pt)             │
│ • UILabel "Description..." (15pt)      │
│ • UIButton "Edit Profile" (17pt)       │
│ • UITextView (15pt)                    │
│   [View All 25 locations]              │
├─────────────────────────────────────────┤
│ Dynamic Type                            │
│ Text Style: .body                       │
│ Supports scaling: ✅ 20/25 elements    │
│ ⚠️ 5 elements not scaled               │
│   [Show Elements]                       │
├─────────────────────────────────────────┤
│ Accessibility                            │
│ Min size: 17pt  ✅ Meets requirements  │
│ Contrast: Varies by background         │
│ Legibility: Good                        │
├─────────────────────────────────────────┤
│ Export Code                             │
│ Swift:                                   │
│ .font(.system(size: 17, weight: .regular))│
│                                          │
│ CSS:                                     │
│ font: 400 17px -apple-system;          │
│                                          │
│ [Copy]                                  │
└─────────────────────────────────────────┘

Dynamic Type Preview

┌─────────────────────────────────────────┐
│ Dynamic Type Preview                     │
├─────────────────────────────────────────┤
│ Size: ◀────●────▶                       │
│       XS   M   XXXL                     │
├─────────────────────────────────────────┤
│ Preview:                                │
│ ┌─────────────────────────────────────┐ │
│ │ Profile                   [Current] │ │
│ │                                     │ │
│ │ Welcome Back!  [17→22pt]           │ │
│ │                                     │ │
│ │ Your recent activity shows          │ │
│ │ that you're... [15→19pt]           │ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────┘

🔧 Technical Implementation

Architecture

public class FontInspector {
    public static let shared = FontInspector()
    
    public func inspectFonts(in view: UIView) -> FontReport
    public func previewDynamicType(_ size: UIContentSizeCategory)
    public func exportTypography(_ report: FontReport, format: ExportFormat) -> String
}

struct FontReport {
    let fonts: [FontUsage]
    let issues: [TypographyIssue]
    let statistics: TypographyStatistics
}

struct FontUsage: Identifiable {
    let id: UUID
    let fontName: String
    let family: String
    let weight: UIFont.Weight
    let size: CGFloat
    let isCustomFont: Bool
    let occurrences: Int
    let locations: [FontLocation]
    let supportsDynamicType: Bool
}

struct FontLocation {
    let view: UIView
    let text: String?
    let textStyle: UIFont.TextStyle?
}

struct TypographyIssue {
    enum IssueType {
        case inconsistentSizes
        case noDynamicType
        case smallFont
        case customFontNotLoaded
        case tooManyFonts
    }
    
    let type: IssueType
    let description: String
    let affectedElements: [UIView]
    let suggestion: String
}

Font Extraction

func inspectFonts(in view: UIView) -> FontReport {
    var fontMap: [String: FontUsage] = [:]
    
    func traverse(_ view: UIView) {
        var font: UIFont?
        var textStyle: UIFont.TextStyle?
        var text: String?
        var supportsDynamicType = false
        
        // Extract font from different view types
        if let label = view as? UILabel {
            font = label.font
            text = label.text
            supportsDynamicType = label.adjustsFontForContentSizeCategory
        } else if let textView = view as? UITextView {
            font = textView.font
            text = textView.text
            supportsDynamicType = textView.adjustsFontForContentSizeCategory
        } else if let textField = view as? UITextField {
            font = textField.font
            text = textField.text
            supportsDynamicType = textField.adjustsFontForContentSizeCategory
        } else if let button = view as? UIButton {
            font = button.titleLabel?.font
            text = button.title(for: .normal)
            supportsDynamicType = button.titleLabel?.adjustsFontForContentSizeCategory ?? false
        }
        
        if let font = font {
            recordFont(
                font,
                view: view,
                text: text,
                textStyle: textStyle,
                supportsDynamicType: supportsDynamicType,
                in: &fontMap
            )
        }
        
        view.subviews.forEach { traverse($0) }
    }
    
    traverse(view)
    
    let fonts = Array(fontMap.values).sorted { $0.occurrences > $1.occurrences }
    let issues = analyzeTypography(fonts)
    let statistics = calculateStatistics(fonts)
    
    return FontReport(fonts: fonts, issues: issues, statistics: statistics)
}

func recordFont(
    _ font: UIFont,
    view: UIView,
    text: String?,
    textStyle: UIFont.TextStyle?,
    supportsDynamicType: Bool,
    in fontMap: inout [String: FontUsage]
) {
    let key = "\(font.fontName)-\(font.pointSize)"
    
    if var existing = fontMap[key] {
        existing.occurrences += 1
        existing.locations.append(FontLocation(
            view: view,
            text: text,
            textStyle: textStyle
        ))
        fontMap[key] = existing
    } else {
        let isCustom = !font.familyName.hasPrefix(".")
        
        fontMap[key] = FontUsage(
            id: UUID(),
            fontName: font.fontName,
            family: font.familyName,
            weight: getFontWeight(font),
            size: font.pointSize,
            isCustomFont: isCustom,
            occurrences: 1,
            locations: [FontLocation(
                view: view,
                text: text,
                textStyle: textStyle
            )],
            supportsDynamicType: supportsDynamicType
        )
    }
}

func getFontWeight(_ font: UIFont) -> UIFont.Weight {
    let traits = font.fontDescriptor.object(forKey: .traits) as? [UIFontDescriptor.TraitKey: Any]
    let weight = traits?[.weight] as? CGFloat ?? 0
    
    switch weight {
    case ...(-0.8): return .ultraLight
    case -0.6..< -0.4: return .thin
    case -0.4..< -0.2: return .light
    case -0.2..<0.0: return .regular
    case 0.0..<0.23: return .medium
    case 0.23..<0.3: return .semibold
    case 0.3..<0.4: return .bold
    case 0.4..<0.56: return .heavy
    default: return .black
    }
}

Typography Analysis

func analyzeTypography(_ fonts: [FontUsage]) -> [TypographyIssue] {
    var issues: [TypographyIssue] = []
    
    // Check for inconsistent sizes
    let sizes = fonts.map { $0.size }
    let similarSizes = findSimilarSizes(sizes)
    if !similarSizes.isEmpty {
        issues.append(TypographyIssue(
            type: .inconsistentSizes,
            description: "Similar font sizes found: \(similarSizes)",
            affectedElements: [],
            suggestion: "Consider unifying to standard text styles"
        ))
    }
    
    // Check Dynamic Type support
    let noDynamicType = fonts.filter { !$0.supportsDynamicType }
    if !noDynamicType.isEmpty {
        let elements = noDynamicType.flatMap { $0.locations.map { $0.view } }
        issues.append(TypographyIssue(
            type: .noDynamicType,
            description: "\(elements.count) text elements don't support Dynamic Type",
            affectedElements: elements,
            suggestion: "Enable adjustsFontForContentSizeCategory"
        ))
    }
    
    // Check for small fonts
    let minSize: CGFloat = 11.0
    let smallFonts = fonts.filter { $0.size < minSize }
    if !smallFonts.isEmpty {
        let elements = smallFonts.flatMap { $0.locations.map { $0.view } }
        issues.append(TypographyIssue(
            type: .smallFont,
            description: "Font sizes below recommended minimum (\(minSize)pt)",
            affectedElements: elements,
            suggestion: "Increase font size for better readability"
        ))
    }
    
    // Check custom font loading
    let customFonts = fonts.filter { $0.isCustomFont }
    for customFont in customFonts {
        if UIFont(name: customFont.fontName, size: 12) == nil {
            issues.append(TypographyIssue(
                type: .customFontNotLoaded,
                description: "Custom font '\(customFont.fontName)' may not be loaded",
                affectedElements: customFont.locations.map { $0.view },
                suggestion: "Verify font is included in app bundle and Info.plist"
            ))
        }
    }
    
    return issues
}

func findSimilarSizes(_ sizes: [CGFloat]) -> [CGFloat] {
    var similar: [CGFloat] = []
    let threshold: CGFloat = 2.0
    
    for i in 0..<sizes.count {
        for j in (i+1)..<sizes.count {
            if abs(sizes[i] - sizes[j]) <= threshold && abs(sizes[i] - sizes[j]) > 0 {
                if !similar.contains(sizes[i]) { similar.append(sizes[i]) }
                if !similar.contains(sizes[j]) { similar.append(sizes[j]) }
            }
        }
    }
    
    return similar.sorted()
}

Dynamic Type Preview

func previewDynamicType(_ size: UIContentSizeCategory) {
    let originalCategory = UIApplication.shared.preferredContentSizeCategory
    
    // Temporarily change content size
    UIApplication.shared.setValue(size, forKey: "preferredContentSizeCategory")
    
    // Force update
    NotificationCenter.default.post(
        name: UIContentSizeCategory.didChangeNotification,
        object: nil
    )
    
    // Restore after delay (for preview)
    DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
        UIApplication.shared.setValue(originalCategory, forKey: "preferredContentSizeCategory")
        NotificationCenter.default.post(
            name: UIContentSizeCategory.didChangeNotification,
            object: nil
        )
    }
}

Export Functions

func exportAsSwift(_ report: FontReport) -> String {
    var code = "import UIKit\n\nextension UIFont {\n"
    
    for font in report.fonts.prefix(10) {
        let methodName = generateMethodName(for: font)
        
        if font.isCustomFont {
            code += """
                
                static func \(methodName)() -> UIFont {
                    return UIFont(name: "\(font.fontName)", size: \(font.size)) ?? .systemFont(ofSize: \(font.size))
                }
            """
        } else {
            code += """
                
                static func \(methodName)() -> UIFont {
                    return .systemFont(ofSize: \(font.size), weight: .\(font.weight))
                }
            """
        }
    }
    
    code += "\n}\n"
    return code
}

func exportAsCSS(_ report: FontReport) -> String {
    var css = ":root {\n"
    
    for (index, font) in report.fonts.prefix(10).enumerated() {
        let name = "font-\(index + 1)"
        let weight = Int(fontWeightToCSS(font.weight))
        
        css += "  --\(name): \(weight) \(font.size)px '\(font.family)';\n"
    }
    
    css += "}\n"
    return css
}

📝 Implementation Checklist

Phase 1: Basic Inspection

  • Font extraction from views
  • Usage counting
  • Custom vs system font detection
  • Basic font information display
  • Font location tracking

Phase 2: Analysis

  • Inconsistency detection
  • Dynamic Type support check
  • Font size analysis
  • Custom font loading validation
  • Typography issues reporting

Phase 3: Advanced Features

  • Dynamic Type preview
  • Export to multiple formats
  • Style guide generation
  • Font usage visualization
  • Typography recommendations

🧪 Testing Requirements

  • Font extraction accuracy
  • Weight detection tests
  • Dynamic Type detection
  • Export format validation
  • Performance with complex views

📚 Documentation

### Font Inspector
- Extract all fonts from current screen
- Identify custom vs system fonts
- Check Dynamic Type support
- Analyze typography consistency
- Export typography tokens

// Inspect fonts
let report = DebugSwift.FontInspector.inspect(view)

// Preview Dynamic Type
DebugSwift.FontInspector.previewDynamicType(.extraLarge)

// Export
let code = report.export(as: .swift)

🎯 Success Criteria

  • Extract all fonts accurately
  • Identify font inconsistencies
  • Verify Dynamic Type support
  • Preview Dynamic Type scaling
  • Export typography system
  • Provide actionable insights

📊 Priority

Low-Medium - Useful for typography audits.

🏷️ Labels

enhancement, feature, interface, typography, accessibility, low-priority

Reactions are currently unavailable

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    Backlog
    Show more project fields

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      Morty Proxy This is a proxified and sanitized view of the page, visit original site.