Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
4e02cb1336
commit
4ea684cbda
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user