📋 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
Phase 2: Analysis
Phase 3: Advanced Features
🧪 Testing Requirements
📚 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
📋 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:
Currently, developers manually inspect each text element or use external design tools, which is inefficient during development.
✨ Proposed Features
Core Functionality
Font Analysis
Export Options
🎨 UI/UX Design
Font Inspector Screen
Font Detail View
Dynamic Type Preview
🔧 Technical Implementation
Architecture
Font Extraction
Typography Analysis
Dynamic Type Preview
Export Functions
📝 Implementation Checklist
Phase 1: Basic Inspection
Phase 2: Analysis
Phase 3: Advanced Features
🧪 Testing Requirements
📚 Documentation
🎯 Success Criteria
📊 Priority
Low-Medium - Useful for typography audits.
🏷️ Labels
enhancement,feature,interface,typography,accessibility,low-priority