I too have been searching for a way to get a timer to show within a Live Activity view in a format like 3m
or 3min
as is shown in the Apple Documentation.
From what I've found so far, the new TImeDataSource available in iOS 18 gets closest:
Text(TimeDataSource<Date>.durationOffset(to: startDate), format: .units(allowed: [.hours, .minutes], width: .narrow, fractionalPart: .hide(rounded: .down)))
This creates an updating Duration
based on the difference between the current time and the offset date and will work within a LiveActivity view (mostly**).
This example formatting displays a timer that counts up from your startDate Date
in Xm
format. When reaching an hour, it would shift to Xh
and subsequently Xh Xm
.
There's a helpful resource created here that outlines FormatStyle options:
https://goshdarnformatstyle.com/duration-styles/
**Within a regular app view, the timer value updates as expected...at the top of a new minute. However, I notice that the values seem to update inconsistently within a LiveActivity view like the Dynamic Island. If anyone has a fix for that, I'd love to see it.
- Because this requires
TimeDataSource
, it will not work with earlier iOS versions--I have not found an alternative there. - The width of the generated
Text
frame is huge which creates a challenge, especially in compact Dynamic lsland views. The only way I've found to fit the timer is to use the TimeDataSource
as an overlay above a hidden Text
view. Unfortunately this approach makes it hard coded to a defined width so you either need to set your hidden Text
to be the maximum potential timer width or push updates to redraw the Live Activity at key increments (e.g. 9m -> 10m, 60m to 1h, etc.)
Text("0m")
.hidden()
.overlay(alignment: .leading) {
Text(TimeDataSource<Date>.durationOffset(to: startDate), format: xxxxx)
}
- You can use this to format a countdown timer to a future date, but there's seemingly no way to remove the preceding negative sign within
FormatStyle
. The example below would show a countdown timer in Xh Xm
format, but always with a negative sign.
Text(TimeDataSource<Date>.durationOffset(to: futureDate), format: .units(allowed: [ .minutes, .hours], width: .narrow, maximumUnitCount: 2, zeroValueUnits: .show(length: 1), valueLengthLimits: 2...3))