You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WidgetKit与SwiftUI中倒计时Widget实现问题咨询

WidgetKit + SwiftUI 倒计时Widget实现问题

我想用WidgetKit和SwiftUI做一个带倒计时计时器的Widget,试了各种资源(包括Apple官方示例)都没法正常运行。

主App里有秒数数据,通过下面这个TimerManager单例访问:

class TimerManager: ObservableObject {

   public static let shared: TimerManager = TimerManager()

   @Published var trainingLength: Int = 60

   func startTraining() {
     trainingTimerMode = .running
     trainingTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { timer in
        self.trainingLength -= 1 // 注:原代码写的是+=1,属于笔误,倒计时逻辑需改为减1
     })
   }

我的trainingLength变量是用来倒计时的,想问下怎么在Widget的getTimeline方法里展示它?把它转成Date之后用Text(entry.date, style: .timer)是不是正确的做法?现在用官方示例也看不到倒计时。

更新:我的getTimeline方法代码如下:

func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> Void) {
    let date = Date()
    let entries = [SimpleEntry(date: date)]
    let timeline = Timeline(entries: entries, policy: .atEnd)
    completion(timeline)
}

解决方案

1. 主App与Widget的数据同步

Widget无法直接访问主App的TimerManager单例,必须通过App Group实现跨Target数据共享:

  • 在Xcode项目设置中,为主App和Widget Target添加同一个App Group(例如group.com.yourapp.timer);
  • 修改TimerManager,将倒计时数据同步到共享存储(以UserDefaults为例):
    let sharedDefaults = UserDefaults(suiteName: "group.com.yourapp.timer")!
    
    @Published var trainingLength: Int = 60 {
        didSet {
            sharedDefaults.set(trainingLength, forKey: "RemainingSeconds")
        }
    }
    

2. 正确使用Text(entry.date, style: .timer)

这个用法是正确的,但需要传入倒计时结束的时间点,而非当前时间:

  • 给Widget的Entry添加endDate字段:
    struct SimpleEntry: TimelineEntry {
        let date: Date
        let endDate: Date // 倒计时结束的时间
    }
    
  • 重构getTimeline方法,生成包含时间点的Timeline,保证每秒刷新:
    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> Void) {
        let sharedDefaults = UserDefaults(suiteName: "group.com.yourapp.timer")!
        let remainingSeconds = sharedDefaults.integer(forKey: "RemainingSeconds")
        let currentDate = Date()
        let endDate = currentDate.addingTimeInterval(TimeInterval(remainingSeconds))
    
        // 生成当前和下一秒的Entry,确保Widget每秒更新
        let entries = [
            SimpleEntry(date: currentDate, endDate: endDate),
            SimpleEntry(date: currentDate.addingTimeInterval(1), endDate: endDate)
        ]
    
        // 设置策略为.atEnd,让Widget在最后一个Entry时间点重新请求数据
        let timeline = Timeline(entries: entries, policy: .atEnd)
        completion(timeline)
    }
    

3. Widget视图展示倒计时

在Widget的View中直接使用.timer样式:

struct TimerWidgetEntryView : View {
    var entry: Provider.Entry

    var body: some View {
        Text(entry.endDate, style: .timer)
            .font(.largeTitle)
            .fontWeight(.bold)
    }
}

4. 修复TimerManager的倒计时逻辑

补全计时器销毁和结束判断逻辑:

func startTraining() {
    trainingTimerMode = .running
    trainingTimer?.invalidate() // 先销毁之前的计时器,避免重复计时
    trainingTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
        if self.trainingLength > 0 {
            self.trainingLength -= 1
        } else {
            timer.invalidate()
            trainingTimerMode = .finished
        }
    }
}

内容的提问来源于stack exchange,提问作者manubrio

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 09:25:28