I’m trying to make a simple timer in a LiveActivity on iOS but I keep having some issues with a layout when I use Text(timerInterval:) solution.
- If I use Text(timerInterval:) this is how it looks in the Dynamic Island and Lock Screen
- If I use a simple calculation it looks good in layout but it does not update the counting.
What could be wrong? I tried to follow the simple timer tutorials but i can’t find the issue.
PD: I’m not an iOS dev 🙂
This is the LiveActivity lockScreen section and Attributes
struct TimerAttributes: ActivityAttributes {
public typealias TimerStatus = ContentState
public struct ContentState: Codable, Hashable {
// Only changeable state goes here
var isPaused: Bool
var isCompleted: Bool = false
// Display data
var taskTitle: String
var taskColor: String // Color as hex string for serialization
// Note: These functions now need to be called with attributes parameter
func getRemainingTime(endDate: Date, at date: Date = Date()) -> TimeInterval {
if isCompleted {
return 0
}
return max(0, endDate.timeIntervalSince(date))
}
func getElapsedTime(startDate: Date, at date: Date = Date()) -> TimeInterval {
return max(0, date.timeIntervalSince(startDate))
}
func isTimerRunning(endDate: Date, at date: Date = Date()) -> Bool {
return !isPaused && !isCompleted && getRemainingTime(endDate: endDate, at: date) > 0
}
// Helper methods matching the working SimpleTimer approach
func getElapsedTimeInSeconds(startDate: Date) -> TimeInterval {
return Date().timeIntervalSince(startDate)
}
func getFormattedElapsedTime(startDate: Date) -> String {
let elapsed = getElapsedTimeInSeconds(startDate: startDate)
let totalSeconds = Int(elapsed)
let hours = totalSeconds / 3600
let minutes = (totalSeconds % 3600) / 60
let seconds = totalSeconds % 60
if hours > 0 {
return String(format: "%d:%02d:%02d", hours, minutes, seconds)
} else {
return String(format: "%d:%02d", minutes, seconds)
}
}
func getFutureDate() -> Date {
return Date().addingTimeInterval(365 * 24 * 60 * 60)
}
}
// Static content that doesn't change during the activity
var taskId: String
var startDate: Date
var endDate: Date
var countsDown: Bool // true for timer mode
}
@ViewBuilder
func lockScreenView(context: ActivityViewContext) -> some View {
HStack {
// Left side - Icon and timer
VStack(alignment: .leading, spacing: 4) {
HStack(spacing: 8) {
Image(systemName: "timer")
.foregroundColor(Color(hex: context.state.taskColor) ?? .orange)
.font(.title3)
if isTimerCompleted(context: context) {
Text("Done!")
.font(.system(size: 16, weight: .semibold))
.foregroundColor(.green)
} else if context.state.isPaused {
Text("Paused")
.font(.system(size: 16, weight: .semibold))
.foregroundColor(Color(hex: context.state.taskColor) ?? .orange)
} else {
Text(timerInterval: Date()...context.attributes.endDate, countsDown: true)
.font(.system(size: 16, weight: .semibold))
.foregroundColor(Color(hex: context.state.taskColor) ?? .orange)
.monospacedDigit()
}
}
Text(context.state.isPaused ? "Timer Paused" : "Timer Active")
.font(.caption)
.foregroundColor(.secondary)
}
Spacer()
// Right side - Task title
VStack(alignment: .trailing, spacing: 4) {
Text(context.state.taskTitle)
.font(.headline)
.fontWeight(.semibold)
.multilineTextAlignment(.trailing)
.lineLimit(2)
.foregroundColor(.white)
Text("\(Int(calculateProgress(attributes: context.attributes, state: context.state) * 100))% Complete")
.font(.caption)
.foregroundColor(.secondary)
}
}
.padding(16)
}