Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>

This commit is contained in:
Matt Bruce 2025-09-11 13:35:50 -05:00
parent 4e02cb1336
commit 4ea684cbda
4 changed files with 58 additions and 50 deletions

View File

@ -11,25 +11,25 @@ import UIKit
/// Font sizing and typography utilities /// Font sizing and typography utilities
struct FontUtils { struct FontUtils {
static func weightedFontName( static func weightedFontName(
name: String, name: String,
weight: Font.Weight, weight: Font.Weight,
design: Font.Design design: Font.Design
) -> String { ) -> String {
let weightSuffix = weight.uiFontWeightSuffix let weightSuffix = weight.uiFontWeightSuffix
switch design { switch design {
case .rounded: case .rounded:
if name.lowercased() == "system" { return "System" } if name.lowercased() == "system" { return "System" }
return name return name
+ (weightSuffix.isEmpty ? "-Rounded" : weightSuffix + "Rounded") + (weightSuffix.isEmpty ? "-Rounded" : weightSuffix + "Rounded")
case .monospaced: case .monospaced:
if name.lowercased() == "system" { return "Courier" } if name.lowercased() == "system" { return "Courier" }
return name == "Courier" return name == "Courier"
? name + weightSuffix ? name + weightSuffix
: name : name
+ (weightSuffix.isEmpty ? "-Mono" : weightSuffix + "Mono") + (weightSuffix.isEmpty ? "-Mono" : weightSuffix + "Mono")
case .serif: case .serif:
if name.lowercased() == "system" { return "TimesNewRomanPS" } if name.lowercased() == "system" { return "TimesNewRomanPS" }
return name + weightSuffix return name + weightSuffix
@ -37,7 +37,7 @@ struct FontUtils {
return name + weightSuffix return name + weightSuffix
} }
} }
static func calculateOptimalFontSize( static func calculateOptimalFontSize(
digit: String, digit: String,
fontName: FontFamily, fontName: FontFamily,
@ -47,7 +47,7 @@ struct FontUtils {
) -> CGFloat { ) -> CGFloat {
var low: CGFloat = 1.0 var low: CGFloat = 1.0
var high: CGFloat = 2000.0 var high: CGFloat = 2000.0
while high - low > 0.01 { while high - low > 0.01 {
let mid = (low + high) / 2 let mid = (low + high) / 2
let testFont = createUIFont( let testFont = createUIFont(
@ -57,17 +57,17 @@ struct FontUtils {
size: mid size: mid
) )
let textSize = tightBoundingBox(for: digit, withFont: testFont) let textSize = tightBoundingBox(for: digit, withFont: testFont)
if textSize.width <= size.width && textSize.height <= size.height { if textSize.width <= size.width && textSize.height <= size.height {
low = mid low = mid
} else { } else {
high = mid high = mid
} }
} }
return low return low
} }
private static func tightBoundingBox( private static func tightBoundingBox(
for text: String, for text: String,
withFont font: UIFont withFont font: UIFont
@ -86,7 +86,7 @@ struct FontUtils {
) )
return CGSize(width: ceil(rect.width), height: ceil(rect.height)) return CGSize(width: ceil(rect.width), height: ceil(rect.height))
} }
static func createUIFont( static func createUIFont(
name: FontFamily, name: FontFamily,
weight: Font.Weight, weight: Font.Weight,
@ -100,7 +100,7 @@ struct FontUtils {
width: design.uiFontWidth width: design.uiFontWidth
) )
} }
if let font = UIFont( if let font = UIFont(
name: weightedFontName( name: weightedFontName(
name: name.rawValue, name: name.rawValue,
@ -111,30 +111,28 @@ struct FontUtils {
) { ) {
return font return font
} }
return UIFont.systemFont( return UIFont.systemFont(
ofSize: size, ofSize: size,
weight: weight.uiFontWeight, weight: weight.uiFontWeight,
width: design.uiFontWidth width: design.uiFontWidth
) )
} }
static func createFont( static func createFont(
name: FontFamily, name: FontFamily,
weight: Font.Weight, weight: Font.Weight,
design: Font.Design, design: Font.Design,
size: CGFloat size: CGFloat
) -> Font { ) -> Font {
let fontName = weightedFontName(
name: name.rawValue,
weight: weight,
design: design
)
print(fontName)
if name == .system { if name == .system {
return .system(size: size, weight: weight, design: design) return .system(size: size, weight: weight, design: design)
} else { } else {
return .custom(fontName, size: size) return .custom(weightedFontName(
name: name.rawValue,
weight: weight,
design: design
), size: size)
} }
} }
} }

View File

@ -20,6 +20,9 @@ struct DigitView: View {
@State var glowIntensity: Double @State var glowIntensity: Double
@Binding var fontSize: CGFloat @Binding var fontSize: CGFloat
@State private var lastCalculatedSize: CGSize = .zero
@State private var isCalculating: Bool = false
init(digit: String, init(digit: String,
fontName: FontFamily, fontName: FontFamily,
weight: Font.Weight = .regular, weight: Font.Weight = .regular,
@ -85,37 +88,43 @@ struct DigitView: View {
.padding(.horizontal, 0) .padding(.horizontal, 0)
.baselineOffset(0) .baselineOffset(0)
.onAppear { .onAppear {
let optimalSize = FontUtils.calculateOptimalFontSize(digit: digit, calculateOptimalFontSize(for: geometry.size)
fontName: fontName,
weight: weight,
design: design,
for: geometry.size)
if optimalSize != fontSize {
fontSize = optimalSize
}
} }
.onChange(of: geometry.size) { _, newSize in .onChange(of: geometry.size) { _, newSize in
let optimalSize = FontUtils.calculateOptimalFontSize(digit: digit, calculateOptimalFontSize(for: newSize)
fontName: fontName,
weight: weight,
design: design,
for: newSize)
if optimalSize != fontSize {
fontSize = optimalSize
}
} }
.onChange(of: sizeCategory) { _, _ in .onChange(of: sizeCategory) { _, _ in
let optimalSize = FontUtils.calculateOptimalFontSize(digit: digit, calculateOptimalFontSize(for: geometry.size)
fontName: fontName,
weight: weight,
design: design,
for: geometry.size)
if optimalSize != fontSize {
fontSize = optimalSize
}
} }
} }
} }
private func calculateOptimalFontSize(for size: CGSize) {
// Prevent multiple calculations for the same size
guard size != lastCalculatedSize && !isCalculating else { return }
// Prevent multiple updates per frame
guard !isCalculating else { return }
isCalculating = true
let optimalSize = FontUtils.calculateOptimalFontSize(digit: digit,
fontName: fontName,
weight: weight,
design: design,
for: size)
// Only update if the size is significantly different to prevent micro-adjustments
if abs(optimalSize - fontSize) > 0.1 {
fontSize = optimalSize
}
lastCalculatedSize = size
// Reset calculation flag after a brief delay to allow for frame completion
DispatchQueue.main.asyncAfter(deadline: .now() + 0.016) { // ~60fps
isCalculating = false
}
}
} }
// MARK: - Preview // MARK: - Preview

View File

@ -107,7 +107,7 @@ struct TimeDisplayView: View {
.minimumScaleFactor(0.1) .minimumScaleFactor(0.1)
.clipped() // Prevent overflow beyond bounds .clipped() // Prevent overflow beyond bounds
} }
.border(.yellow, width: 1) //.border(.yellow, width: 1)
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
.onOrientationChange() // Force updates on orientation changes .onOrientationChange() // Force updates on orientation changes
} }
@ -135,5 +135,6 @@ struct TimeDisplayView: View {
fontWeight: style.fontWeight, fontWeight: style.fontWeight,
fontDesign: style.fontDesign, fontDesign: style.fontDesign,
forceHorizontalMode: style.forceHorizontalMode forceHorizontalMode: style.forceHorizontalMode
) .background(Color.black) )
.background(Color.black)
} }

View File

@ -32,10 +32,10 @@ struct TimeSegment: View {
glowIntensity: glowIntensity, glowIntensity: glowIntensity,
fontSize: $fontSize fontSize: $fontSize
) )
.border(.red, width: 1) //.border(.red, width: 1)
} }
} }
.border(Color.green, width: 1) //.border(Color.green, width: 1)
.frame(maxHeight: .infinity) .frame(maxHeight: .infinity)
} }