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
相关产品推荐
相关产品推荐

