Hello,
I'm trying to display some Duration
in a live activity using a custom format, with the goal of displaying a match time (only minutes) as, e.g. 33'
, 45' (+2)
, etc.
For that purpose, I'm using a TimeDataSource<Duration>
, so that it also updates automatically given a starting point.
I've implemented my custom FormatStyle
, trying to somehow mimic the existing UnitsFormatStyle
(which perfectly works with live activities) but just changing the format.
I've added conformance to DiscreteFormatStyle
, and code-wise everything seems to be ok (if I've understood things correctly). It compiles and it even works as expected in a playground.
However, when trying to use it within the live activity, I'm getting these cryptic errors in the Console.app, and the live activity just turns into a view with placeholders
Failed to fetch view from archive at index 12: SwiftUI.AnyCodable<SwiftUI.(unknown context at $1d47f6af0).SafelyCodableRequirement>.(unknown context at $1d47fe410).Errors.noType(mangledName: "7SwiftUI18TimeDataFormattingO10ResolvableVy_AA0cD6SourceVAAE15DurationStorageOys0H0V_GAK28BlickLiveActivitiesExtensionE16MatchFormatStyleVG")
[624AEC37-13D9-4927-9F41-C3092B61E214] Failed to return view entry from archive for view model with tag listItem with error: SwiftUI.AnyCodable<SwiftUI.(unknown context at $1d47f6af0).SafelyCodableRequirement>.(unknown context at $1d47fe410).Errors.noType(mangledName: "7SwiftUI18TimeDataFormattingO10ResolvableVy_AA0cD6SourceVAAE15DurationStorageOys0H0V_GAK28BlickLiveActivitiesExtensionE16MatchFormatStyleVG")
Are there any limitations when it comes to live activities and these custom formatters? This whole error doesn't make sense, since I'm only aiming to update every minute, which should just be fine, the same thing I get with the UnitsFormatStyle
.
For reference, this is my playground, which just works as expected:
import Foundation
import SwiftUI
import PlaygroundSupport
extension Duration {
struct MatchFormatStyle: DiscreteFormatStyle, Sendable {
let periodLength: Int
let overtime: Int
func format(_ value: Duration) -> String {
let (seconds, _): (Int64, Int64) = value.components
let minutes: Int = Int(seconds) / 60
let current: Int = periodLength + minutes + overtime
if current > periodLength {
return "\(periodLength)' (+\(current - periodLength))"
} else {
return "\(current)'"
}
}
func discreteInput(before input: Duration) -> Duration? {
let (seconds, _): (Int64, Int64) = input.components
let minutes: Int64 = seconds / 60 - 1
return Duration(secondsComponent: minutes * 60, attosecondsComponent: .zero)
}
func discreteInput(after input: Duration) -> Duration? {
let (seconds, _): (Int64, Int64) = input.components
let minutes: Int64 = seconds / 60 + 1
return Duration(secondsComponent: minutes * 60, attosecondsComponent: .zero)
}
}
}
extension FormatStyle where Self == Duration.MatchFormatStyle {
static func matchTime(currentTime: Int, periodLength: Int) -> Duration.MatchFormatStyle {
return Duration.MatchFormatStyle(periodLength: periodLength, overtime: max(currentTime - periodLength, .zero))
}
}
extension TimeDataSource<Date> {
static func matchDuration(for currentTime: Int, periodLength: Int) -> TimeDataSource<Duration> {
let minutesAhead: Double = Double(max(periodLength - currentTime + 1, .zero))
return TimeDataSource<Date>.durationOffset(to: Date.now.addingTimeInterval(minutesAhead * 60))
}
}
struct FooView: View {
let currentTime: Int = 36
let periodLength: Int = 45
var body: some View {
Text(
TimeDataSource<Date>.matchDuration(for: currentTime, periodLength: periodLength),
format: .matchTime(currentTime: currentTime, periodLength: periodLength)
)
.frame(width: 400, height: 200)
.font(.system(size: 50))
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.setLiveView(FooView())
Any hints or suggestions are welcome, many thanks in advance.