我们现在就来构建这个可配置的计时器 + Live Activity + Widget 项目的完整基础工程结构。这个工程将包括:
🚀 项目名称示例(可自定义) #
FocusTimerApp
📦 目录结构概览 #
FocusTimerApp/
├── App/
│ ├── FocusTimerApp.swift // App 入口
│ └── TimerManager.swift // 倒计时控制器
│
├── Model/
│ ├── TimerType.swift // 枚举类型(番茄、短休息等)
│ ├── TaskRecord.swift // 任务记录结构
│ └── TimerAttributes.swift // 用于 Live Activity 的状态结构
│
├── LiveActivity/
│ └── LiveActivityView.swift // 锁屏/灵动岛视图
│
├── WidgetExtension/
│ ├── TaskWidget.swift // 展示任务记录的 Widget
│ ├── ConfigurableWidget.swift // 可配置的启动计时器 Widget
│ ├── StartTimerIntent.swift // AppIntent:点击 Widget 启动计时器
│ └── TimerTypeEntity.swift // AppEntity:让 Widget 支持计时器类型选择
│
├── Storage/
│ └── TaskStore.swift // 任务记录存储,支持 iCloud
关键代码概览 #
✅ TimerType.swift
:定义计时器类型
#
enum TimerType: String, CaseIterable, Codable {
case focus = "专注"
case shortBreak = "短休息"
case longBreak = "长休息"
var duration: TimeInterval {
switch self {
case .focus: return 25 * 60
case .shortBreak: return 5 * 60
case .longBreak: return 15 * 60
}
}
}
✅ TimerManager.swift
:倒计时控制逻辑(可与 ViewModel 联动)
#
class TimerManager: ObservableObject {
@Published var timeRemaining: TimeInterval = 0
@Published var isRunning = false
var timer: Timer?
func start(type: TimerType) {
timeRemaining = type.duration
isRunning = true
startTimer()
// 触发 Live Activity
LiveActivityService.startLiveActivity(for: type)
}
private func startTimer() {
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
self.timeRemaining -= 1
if self.timeRemaining <= 0 {
self.stop()
}
}
}
func stop() {
isRunning = false
timer?.invalidate()
timer = nil
LiveActivityService.endLiveActivity()
}
}
✅ TimerAttributes.swift
(用于 Live Activity)
#
import ActivityKit
struct TimerAttributes: ActivityAttributes {
struct ContentState: Codable, Hashable {
var timeRemaining: TimeInterval
}
var type: TimerType
}
✅ LiveActivityView.swift
#
struct LiveActivityView: View {
let context: ActivityViewContext<TimerAttributes>
var body: some View {
VStack {
Text("计时中:\(context.attributes.type.rawValue)")
Text("\(Int(context.state.timeRemaining)) 秒")
.monospacedDigit()
}
.padding()
}
}
✅ StartTimerIntent.swift
(Widget 可点击交互按钮)
#
struct StartTimerIntent: AppIntent {
static var title: LocalizedStringResource = "开始计时"
@Parameter(title: "计时器类型")
var type: TimerTypeEntity
func perform() async throws -> some IntentResult {
TimerManager().start(type: type.toTimerType())
return .result()
}
}
✅ TaskWidget.swift
(展示最近任务记录)
#
struct TaskWidget: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(kind: "TaskWidget", provider: TaskTimelineProvider()) { entry in
VStack(alignment: .leading) {
Text("最近任务")
ForEach(entry.tasks.prefix(3), id: \.id) {
Text("• \($0.type.rawValue)")
}
}
.padding()
}
.configurationDisplayName("任务记录")
.description("展示最近完成的计时任务")
}
}
✅ ConfigurableWidget.swift
(点击即可启动计时器)
#
struct ConfigurableWidget: Widget {
var body: some WidgetConfiguration {
IntentConfiguration(
kind: "ConfigurableTimerWidget",
intent: StartTimerIntent.self,
provider: EmptyTimelineProvider()
) { _ in
Button(intent: StartTimerIntent(type: .init(timerType: .focus))) {
Text("开始专注")
}
}
.configurationDisplayName("开始计时")
.description("点击启动指定类型的计时器")
.supportedFamilies([.systemSmall])
}
}
☁️ TaskStore.swift
(保存计时记录,可接入 iCloud)
#
final class TaskStore {
static let shared = TaskStore()
@Published var records: [TaskRecord] = []
func addRecord(for type: TimerType) {
let record = TaskRecord(type: type, date: Date())
records.insert(record, at: 0)
save()
}
private func save() {
let data = try? JSONEncoder().encode(records)
UserDefaults.standard.set(data, forKey: "TaskRecords")
}
private func load() {
if let data = UserDefaults.standard.data(forKey: "TaskRecords"),
let decoded = try? JSONDecoder().decode([TaskRecord].self, from: data) {
records = decoded
}
}
}