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

This commit is contained in:
Matt Bruce 2025-09-08 11:53:37 -05:00
parent 2d4d76cc8d
commit d7897d6b38
3 changed files with 141 additions and 53 deletions

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Bucket
uuid = "20B61D3C-CF97-4427-BF7D-EB393A559DFC"
type = "1"
version = "2.0">
</Bucket>

View File

@ -207,6 +207,27 @@ enum FontUtils {
) )
} }
/// Calculate the maximum width for any two-digit combination
/// - Parameters:
/// - font: The font to measure with
/// - fontSize: The font size
/// - Returns: Maximum width for any two-digit combination
static func maxTwoDigitWidth(font: UIFont, fontSize: CGFloat) -> CGFloat {
let digits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
var maxWidth: CGFloat = 0
// Test all possible two-digit combinations to find the widest
for firstDigit in digits {
for secondDigit in digits {
let combination = firstDigit + secondDigit
let width = measureTextSize(text: combination, font: font).width
maxWidth = max(maxWidth, width)
}
}
return maxWidth
}
/// Get weight multiplier for visual consistency with font weight /// Get weight multiplier for visual consistency with font weight
/// - Parameter weight: Font weight name /// - Parameter weight: Font weight name
/// - Returns: Multiplier for dot size (0.7 to 1.3) /// - Returns: Multiplier for dot size (0.7 to 1.3)

View File

@ -113,14 +113,25 @@ struct TimeDisplayView: View {
design: fontDesign design: fontDesign
) )
// Use fixed-width calculations to prevent layout jumping // Calculate consistent sizes for layout
let digitWidth = FontUtils.maxDigitWidth(font: digitUIFont)
let digitHeight = measureText("8", font: digitUIFont).height // Use 8 as reference height let digitHeight = measureText("8", font: digitUIFont).height // Use 8 as reference height
// Fixed sizes for consistent layout // Calculate the width of "88" for consistent sizing
let hourSize = CGSize(width: digitWidth * 2, height: digitHeight) // Two digits let testFont = FontUtils.customUIFont(
let minuteSize = CGSize(width: digitWidth * 2, height: digitHeight) // Two digits size: baseFontSize,
let secondsSize = showSeconds ? CGSize(width: digitWidth * 2, height: digitHeight) : .zero family: fontFamily,
weight: fontWeight,
design: fontDesign
)
let _ = calculateMaxTextWidth(font: testFont)
// Calculate width for a single digit (using "8" as the widest)
let singleDigitWidth = calculateMaxTextWidth(font: testFont, text: "8")
// All time segments use the same fixed width to prevent shifting
let hourSize = CGSize(width: singleDigitWidth * 2, height: digitHeight)
let minuteSize = CGSize(width: singleDigitWidth * 2, height: digitHeight)
let secondsSize = showSeconds ? CGSize(width: singleDigitWidth * 2, height: digitHeight) : .zero
let ampmSize = showAMPM ? measureText(ampmText, font: ampmUIFont) : .zero let ampmSize = showAMPM ? measureText(ampmText, font: ampmUIFont) : .zero
// Separators - reasonable spacing // Separators - reasonable spacing
@ -273,54 +284,104 @@ struct TimeDisplayView: View {
} }
// MARK: - Supporting Views // MARK: - Supporting Views
private struct TimeSegment: View {
let text: String // Calculate width of text for the given font - this ensures consistent width
let fontSize: CGFloat private func calculateMaxTextWidth(font: UIFont, text: String = "88") -> CGFloat {
let opacity: Double let attributes = [NSAttributedString.Key.font: font]
let digitColor: Color let size = (text as NSString).size(withAttributes: attributes)
let glowIntensity: Double return size.width
let fontFamily: String }
let fontWeight: String
let fontDesign: String private struct TimeSegment: View {
let text: String
var body: some View { let fontSize: CGFloat
let clamped = ColorUtils.clampOpacity(opacity) let opacity: Double
let font = FontUtils.customUIFont( let digitColor: Color
size: fontSize, let glowIntensity: Double
family: fontFamily, let fontFamily: String
weight: fontWeight, let fontWeight: String
design: fontDesign let fontDesign: String
)
let maxWidth = FontUtils.maxDigitWidth(font: font) var body: some View {
let clamped = ColorUtils.clampOpacity(opacity)
ZStack { let font = FontUtils.customUIFont(
Text(text) size: fontSize,
.font(FontUtils.customFont( family: fontFamily,
size: fontSize, weight: fontWeight,
family: fontFamily, design: fontDesign
weight: fontWeight, )
design: fontDesign let singleDigitWidth = calculateMaxTextWidth(font: font, text: "8")
)) let totalWidth = singleDigitWidth * CGFloat(text.count)
.foregroundColor(digitColor)
.blur(radius: ColorUtils.glowRadius(intensity: glowIntensity)) HStack(spacing: 0) {
.opacity(ColorUtils.glowOpacity(intensity: glowIntensity) * clamped) ForEach(Array(text.enumerated()), id: \.offset) { index, character in
Text(text) DigitView(
.font(FontUtils.customFont( digit: String(character),
size: fontSize, fontSize: fontSize,
family: fontFamily, opacity: clamped,
weight: fontWeight, digitColor: digitColor,
design: fontDesign glowIntensity: glowIntensity,
)) fontFamily: fontFamily,
.foregroundColor(digitColor) fontWeight: fontWeight,
.opacity(clamped) fontDesign: fontDesign,
} digitWidth: singleDigitWidth
.frame(width: maxWidth * CGFloat(text.count), height: nil, alignment: .center) )
.fixedSize(horizontal: false, vertical: true) }
.lineLimit(1) }
.allowsTightening(false) // Prevent tightening to maintain fixed width .frame(width: totalWidth, alignment: .center)
.multilineTextAlignment(.center) .border(Color.blue.opacity(0.5), width: 1)
}
// Calculate width of text for the given font - this ensures consistent width
private func calculateMaxTextWidth(font: UIFont, text: String = "88") -> CGFloat {
let attributes = [NSAttributedString.Key.font: font]
let size = (text as NSString).size(withAttributes: attributes)
return size.width
}
}
private struct DigitView: View {
let digit: String
let fontSize: CGFloat
let opacity: Double
let digitColor: Color
let glowIntensity: Double
let fontFamily: String
let fontWeight: String
let fontDesign: String
let digitWidth: CGFloat
var body: some View {
ZStack {
Text(digit)
.font(FontUtils.customFont(
size: fontSize,
family: fontFamily,
weight: fontWeight,
design: fontDesign
))
.foregroundColor(digitColor)
.blur(radius: ColorUtils.glowRadius(intensity: glowIntensity))
.opacity(ColorUtils.glowOpacity(intensity: glowIntensity) * opacity)
.multilineTextAlignment(.center)
Text(digit)
.font(FontUtils.customFont(
size: fontSize,
family: fontFamily,
weight: fontWeight,
design: fontDesign
))
.foregroundColor(digitColor)
.opacity(opacity)
.multilineTextAlignment(.center)
}
.frame(width: digitWidth, alignment: .center)
.fixedSize(horizontal: false, vertical: true)
.lineLimit(1)
.allowsTightening(false)
.multilineTextAlignment(.center)
}
} }
}
private struct HorizontalColon: View { private struct HorizontalColon: View {
let dotDiameter: CGFloat let dotDiameter: CGFloat