当Watch App新增活动时,如何更新Complication显示最新活动时间?
我用Xcode 14开发了一款面向单个用户的学习类iOS和WatchOS应用(适配iOS 16.2、WatchOS 9.1),采用CoreData本地存储活动数据,通过WCSession的transferUserInfo()方法将新活动的JSON格式数据传输至对应设备,再保存到各自的CoreData数据库,该流程运行正常,App视图可动态更新。
已添加Watch Widget Extension目标,修改模板代码后,Complication可作为跳转至Watch App的链接。现在需要实现:当Watch App新增活动(包括从iOS端同步过来的活动)时,同步更新Complication。用户每3-8小时添加一次活动,预设未来时间线不合理,Complication仅需获取最近一次活动的时间,显示自该活动起已流逝的时长。
具体需求:当最近活动的activityTime值更新时,如何将其从Watch App发送至Complication?
现有Widget代码
import WidgetKit import SwiftUI import OSLog struct Provider: TimelineProvider { let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "", category: "Provider") func placeholder(in context: Context) -> mostRecentActivityEntry { mostRecentActivityEntry(date: Date(), activityTime: Date()) } func getSnapshot(in context: Context, completion: @escaping (mostRecentActivityEntry) -> ()) { let entry = mostRecentActivityEntry(date: Date(), activityTime: Date()) completion(entry) } func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) { var mostRecentActivityTime: Date? var entries: [mostRecentActivityEntry] = [] // Generate a timeline consisting of five entries an hour apart, starting from the current date. let currentDate = Date() for hourOffset in 0 ..< 5 { let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)! let entry = mostRecentActivityEntry(date: entryDate, activityTime: mostRecentActivityTime) entries.append(entry) } let timeline = Timeline(entries: entries, policy: .atEnd) completion(timeline) } } struct mostRecentActivityEntry: TimelineEntry { var date: Date var activityTime: Date? } struct MostRecentEntryView : View { var entry: Provider.Entry var body: some View { HStack { Text("Log") .font(.headline) .foregroundColor(.purple) if let time = entry.activityTime { Text(time, style: .relative) .font(.headline) .foregroundColor(.accentColor) } Spacer() } } } @main struct ActivityLogWatchWidgetExtension: Widget { let kind: String = "ActivityLogWatchWidgetExtension" var body: some WidgetConfiguration { StaticConfiguration( kind: kind, provider: Provider()) { entry in MostRecentEntryView(entry: entry) } .configurationDisplayName("Time Since Last Activity") .description("Displays the time since the last activity.") } } struct ActivityLogWatchWidgetExtension_Previews: PreviewProvider { static var previews: some View { MostRecentEntryView(entry: mostRecentActivityEntry(date: Date(), activityTime: Date().addingTimeInterval(-1000))) .previewContext(WidgetPreviewContext(family: .accessoryRectangular)) MostRecentEntryView(entry: mostRecentActivityEntry(date: Date(), activityTime: nil)) .previewContext(WidgetPreviewContext(family: .accessoryRectangular)) } }
1. 让Widget读取CoreData中的最新活动时间
首先修改Provider类,添加从CoreData获取最新activityTime的逻辑,同时调整时间线策略(不需要预设未来条目):
步骤1-1:添加CoreData读取方法
在Provider中新增读取最新活动时间的辅助方法,注意要和Watch App使用相同的CoreData配置:
private func fetchMostRecentActivityTime() -> Date? { // 替换为你的CoreData数据模型名称 guard let container = NSPersistentContainer(name: "YourDataModelName") else { return nil } container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { logger.error("CoreData加载失败: \(error), \(error.userInfo)") } }) let context = container.viewContext // 替换为你的Activity实体类及对应属性名 let fetchRequest: NSFetchRequest<Activity> = Activity.fetchRequest() fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Activity.activityTime, ascending: false)] fetchRequest.fetchLimit = 1 do { let activities = try context.fetch(fetchRequest) return activities.first?.activityTime } catch { logger.error("获取最新活动失败: \(error)") return nil } }
步骤1-2:修改Timeline生成逻辑
更新getTimeline和getSnapshot方法,使用真实的CoreData数据,同时设置时间线策略为.never(避免自动刷新):
func getSnapshot(in context: Context, completion: @escaping (mostRecentActivityEntry) -> ()) { let activityTime = fetchMostRecentActivityTime() ?? Date() let entry = mostRecentActivityEntry(date: Date(), activityTime: activityTime) completion(entry) } func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) { let mostRecentActivityTime = fetchMostRecentActivityTime() // 仅生成当前时间的条目,无需预设未来时间点 let entry = mostRecentActivityEntry(date: Date(), activityTime: mostRecentActivityTime) let timeline = Timeline(entries: [entry], policy: .never) completion(timeline) }
2. 在Watch App中触发Widget刷新
当Watch App新增活动(包括从iOS端同步完成后),调用WidgetKit的刷新方法,主动更新Complication:
在Watch App的活动保存完成逻辑后(比如CoreData保存成功、WCSession数据接收并存储完成时),添加以下代码:
import WidgetKit // 刷新指定kind的Widget(和你Widget代码中的kind一致) WidgetCenter.shared.reloadTimelines(ofKind: "ActivityLogWatchWidgetExtension")
3. 优化CoreData共享(可选)
为了避免Watch App和Widget重复编写CoreData配置代码,可以封装一个共享的CoreData管理类:
// 共享CoreData管理类,Watch App和Widget均可调用 class CoreDataManager { static let shared = CoreDataManager() let persistentContainer: NSPersistentContainer private init() { persistentContainer = NSPersistentContainer(name: "YourDataModelName") persistentContainer.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { fatalError("CoreData初始化失败: \(error), \(error.userInfo)") } }) } }
之后在Provider的fetchMostRecentActivityTime方法中,直接使用共享类的上下文:
private func fetchMostRecentActivityTime() -> Date? { let context = CoreDataManager.shared.persistentContainer.viewContext // 后续fetch逻辑和之前一致 }
4. 相对时间显示说明
你使用的Text(time, style: .relative)会自动更新相对时间(比如从"1分钟前"变为"2分钟前"),无需额外刷新Widget,只要初始的activityTime正确即可。
内容的提问来源于stack exchange,提问作者LarryG

