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

当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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 03:35:50