I have zero insight into how Apple is building Live Activities and widgets. My guess is that the widget is rendered and then cached. That is why things like standard timers/publishers don't update the UI. My guess is that these Text timers fill the space so that Apple can render a label on top of the cached view without requiring a new layout from the widget when the text size changes. I am saying this because I think the filling of the container is a feature and not a bug, so it probably won't change any time soon.
To get around this I force the size of the Text view to be the largest it should be. There are still some issues, however for the most part this solution looks ok.
struct TextTimer: View {
// Return the largest width string for a time interval
private static func maxStringFor(_ time: TimeInterval) -> String {
if time < 600 { // 9:99
return "0:00"
}
if time < 3600 { // 59:59
return "00:00"
}
if time < 36000 { // 9:59:59
return "0:00:00"
}
return "00:00:00"// 99:59:59
}
init(_ date: Date, font: UIFont, width: CGFloat? = nil) {
self.date = date
self.font = font
if let width {
self.width = width
} else {
let fontAttributes = [NSAttributedString.Key.font: font]
let time = date.timeIntervalSinceNow
let maxString = Self.maxStringFor(time)
self.width = (maxString as NSString).size(withAttributes: fontAttributes).width
}
}
let date: Date
let font: UIFont
let width: CGFloat
var body: some View {
Text(timerInterval: Date.now...date)
.font(Font(font))
.frame(width: width > 0 ? width : nil)
.minimumScaleFactor(0.5)
.lineLimit(1)
}
}