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

Swift Widget Extension:如何实现两地时间每秒更新?

问题:Widget时间无法实时更新,如何实现每秒刷新的双时区数字时钟?

我正在开发一款可同时显示本地与中国当前时间的Widget,当前代码能正确显示时间,但不会实时更新。我想实现每秒更新的数字时钟效果,同时自己尝试了一个方案,但担心会影响电池续航,想确认是否合理。

初始代码

import SwiftUI
import WidgetKit

struct TimeWidgetView: View {
    var localTime: String
    var foreignTime: String
    
    var body: some View {
        HStack(spacing: 16) {
            VStack{
                Text("Local Time")
                    .font(.title2)
                    .fontWeight(.bold)
                    .foregroundColor(.primary)
                
                Text(localTime)
                    .font(.title3)
                    .fontWeight(.semibold)
                    .foregroundColor(.primary)
                    .multilineTextAlignment(.center)
                    .lineLimit(1)
                    .minimumScaleFactor(0.7)
            }
            
            VStack{
                Text("China Time")
                    .font(.title2)
                    .fontWeight(.bold)
                    .foregroundColor(.primary)
                
                Text(foreignTime)
                    .font(.title3)
                    .fontWeight(.semibold)
                    .foregroundColor(.primary)
                    .multilineTextAlignment(.center)
                    .lineLimit(1)
                    .minimumScaleFactor(0.7)
            }
            
        }
        .padding()
    }
}

struct TimeWidget: Widget {
    private let kind: String = "TimeWidget"

    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: TimeProvider()) { entry in
            TimeWidgetView(localTime: entry.localTime, foreignTime: entry.foreignTime)
        }
        .configurationDisplayName("Time Widget")
        .description("Displays local time and China time.")
    }
}

struct TimeProvider: TimelineProvider {
    func placeholder(in context: Context) -> TimeEntry {
        TimeEntry(localTime: "00:00", foreignTime: "00:00")
    }

    func getSnapshot(in context: Context, completion: @escaping (TimeEntry) -> Void) {
        let entry = TimeEntry(localTime: "12:34", foreignTime: "23:34")
        completion(entry)
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline<TimeEntry>) -> Void) {
        let currentDate = Date()
        let calendar = Calendar.current
        
        let localTimeFormatter = DateFormatter()
        localTimeFormatter.dateFormat = "HH:mm:ss"
        let localTime = localTimeFormatter.string(from: currentDate)
        
        let foreignTimeFormatter = DateFormatter()
        foreignTimeFormatter.timeZone = TimeZone(identifier: "Asia/Shanghai") // Set time zone to China
        foreignTimeFormatter.dateFormat = "HH:mm:ss"
        let foreignTime = foreignTimeFormatter.string(from: currentDate)
        
        let entry = TimeEntry(localTime: localTime, foreignTime: foreignTime)
        let timeline = Timeline(entries: [entry], policy: .atEnd)
        completion(timeline)
    }
}

struct TimeEntry: TimelineEntry {
    var date: Date = Date()
    var localTime: String
    var foreignTime: String
}

struct TimeWidget_Previews: PreviewProvider {
    static var previews: some View {
        TimeWidgetView(localTime: "12:34:56", foreignTime: "23:34:45")
            .previewContext(WidgetPreviewContext(family: .systemMedium))
    }
}

我尝试的方案(担心电池续航问题)

func getTimeline(in context: Context, completion: @escaping (Timeline<TimeEntry>) -> Void) {
        var entries: [TimeEntry] = []
        
        // Use a Timer to update the widget every half second as a clock
        let timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect()
        let formatter = DateFormatter()
        formatter.dateFormat = "HH:mm:ss"
        for second in 0...59 {
            let currentDate = Date().addingTimeInterval(TimeInterval(second))
            
            let localTime = formatter.string(from: currentDate)
            
            let foreignTimeFormatter = DateFormatter()
            foreignTimeFormatter.timeZone = TimeZone(identifier: "Asia/Shanghai") // Set time zone to China
            foreignTimeFormatter.dateFormat = "HH:mm:ss"
            let foreignTime = foreignTimeFormatter.string(from: currentDate)
            
            let entry = TimeEntry(date: currentDate, localTime: localTime, foreignTime: foreignTime)
            entries.append(entry)
        }
        
        let timeline = Timeline(entries: entries, policy: .atEnd)
        completion(timeline)
    }

解决方案与优化建议

核心问题分析

你初始代码里的getTimeline只生成了一个时间点的TimeEntry,并且设置policy: .atEnd——这意味着只有当这个entry的时间过去后,系统才会再次调用getTimeline生成新内容,自然无法实现每秒更新。

你尝试的方案思路是对的:通过生成未来60秒的所有entry,让Widget在一分钟内每秒刷新一次。但代码里有冗余的Timer(getTimeline是一次性执行的后台方法,Timer在这里完全没用),而且重复创建DateFormatter会增加性能开销。

优化后的代码

func getTimeline(in context: Context, completion: @escaping (Timeline<TimeEntry>) -> Void) {
    var entries: [TimeEntry] = []
    
    // 复用DateFormatter,减少重复创建的性能开销
    let localFormatter = DateFormatter()
    localFormatter.dateFormat = "HH:mm:ss"
    
    let chinaFormatter = DateFormatter()
    chinaFormatter.timeZone = TimeZone(identifier: "Asia/Shanghai")
    chinaFormatter.dateFormat = "HH:mm:ss"
    
    // 将时间对齐到下一个整秒,避免时间显示偏差
    let now = Date()
    let calendar = Calendar.current
    let nextSecond = calendar.date(byAdding: .second, value: 1, to: now)!
    let startOfNextSecond = calendar.date(from: calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: nextSecond))!
    
    // 生成未来60秒的所有时间entry
    for secondOffset in 0..<60 {
        let entryDate = calendar.date(byAdding: .second, value: secondOffset, to: startOfNextSecond)!
        
        let localTime = localFormatter.string(from: entryDate)
        let chinaTime = chinaFormatter.string(from: entryDate)
        
        entries.append(TimeEntry(date: entryDate, localTime: localTime, foreignTime: chinaTime))
    }
    
    // 设置策略为.atEnd,一分钟后自动重新生成新的timeline
    let timeline = Timeline(entries: entries, policy: .atEnd)
    completion(timeline)
}

关键优化点说明

  1. 复用DateFormatter:DateFormatter的创建成本较高,循环中重复创建会拖慢性能,提前初始化复用能有效提升效率。
  2. 对齐整秒时间:确保每个entry的时间都是精确的整秒,避免出现时间显示错位的问题。
  3. 移除无用Timer:getTimeline是一次性执行的方法,Timer在这里无法起到定时作用,完全可以删除。

关于电池续航的说明

不用过度担心:WidgetKit的更新由系统统一调度,即使你生成了60个entry,系统也会根据设备状态(比如电量、锁屏状态、系统负载)调整实际更新频率。低电量模式下系统会自动降低更新频率,这种批量生成entry的方式比每次单独请求更新更高效,是苹果官方推荐的时钟类Widget实现方式。

需要注意的是:苹果对Widget的更新有严格限制,无法保证100%每秒都刷新,但这是目前能实现最接近实时时钟效果的方案。


内容的提问来源于stack exchange,提问作者Sondre Kalle Knutsen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 10:57:02