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

SwiftUI实现日期变化/午夜时实时更新ForEach视图

解决SwiftUI午夜日期变更时自动更新事件列表的问题

核心思路

要实现午夜自动刷新事件列表,需完成两个关键逻辑:

  • 计算当前时间到下一个午夜的间隔,设置定时器触发数据刷新
  • 监听应用前后台切换,确保回到前台时若日期已变更,同步刷新数据

修改后的ViewModel代码

import Foundation
import UIKit

@MainActor
final class TodaysEventsViewModel: ObservableObject {
    
    @Published var events = [EventModel]()
    @Published var event = [Event]()
    
    @Published var selectedEvent: Event?
    
    @Published var date = Date()
    
    // 静态DateFormatter,避免重复创建提升性能
    private static let dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "dd.MM.yyyy"
        return formatter
    }()
    
    private var stringDate: String {
        Self.dateFormatter.string(from: date)
    }
    
    private var midnightTimer: Timer?
    private var foregroundNotificationObserver: NSObjectProtocol?
    
    init() {
        fetchEvents()
        getTodaysEvents()
        setupMidnightTimer()
        setupForegroundNotification()
    }
    
    deinit {
        // 清理定时器和通知监听,防止内存泄漏
        midnightTimer?.invalidate()
        if let observer = foregroundNotificationObserver {
            NotificationCenter.default.removeObserver(observer)
        }
    }
    
    private func fetchEvents() {
        let eventsData = Bundle.main.decode([EventModel].self, from: "EventsData.json")
        events = eventsData
    }
    
    private func getTodaysEvents() {
        event = events.first(where: {$0.date == stringDate})?.event ?? []
    }
    
    // 设置午夜触发的定时器
    private func setupMidnightTimer() {
        // 计算下一个午夜的时间
        let calendar = Calendar.current
        var components = calendar.dateComponents([.year, .month, .day], from: Date())
        components.day! += 1
        components.hour = 0
        components.minute = 0
        components.second = 0
        
        guard let nextMidnight = calendar.date(from: components) else { return }
        let timeInterval = nextMidnight.timeIntervalSinceNow
        
        // 创建一次性定时器,触发后刷新数据并重新设置次日定时器
        midnightTimer = Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: false) { [weak self] _ in
            self?.date = Date()
            self?.getTodaysEvents()
            self?.setupMidnightTimer()
        }
    }
    
    // 监听应用回到前台的通知
    private func setupForegroundNotification() {
        foregroundNotificationObserver = NotificationCenter.default.addObserver(
            forName: UIApplication.willEnterForegroundNotification,
            object: nil,
            queue: .main
        ) { [weak self] _ in
            let currentDateString = Self.dateFormatter.string(from: Date())
            let storedDateString = Self.dateFormatter.string(from: self?.date ?? Date())
            // 日期不一致时刷新数据
            if currentDateString != storedDateString {
                self?.date = Date()
                self?.getTodaysEvents()
                self?.setupMidnightTimer()
            }
        }
    }
}

代码说明

  • 静态DateFormatter:将日期格式化器设为静态属性,避免每次调用stringDate时重复创建,优化性能。
  • 午夜定时器:计算当前到下一个午夜的时间差,设置一次性定时器,触发后更新日期、刷新事件列表,并重新设置次日的定时器,保证每日午夜自动刷新。
  • 前台通知监听:应用从后台回到前台时,对比当前日期与ViewModel存储的日期,若不一致则刷新数据,避免后台运行时错过午夜刷新。
  • 资源清理:在deinit中销毁定时器和通知监听,防止内存泄漏。

View代码修正

原View代码中sheet绑定变量存在拼写错误,修正后的代码如下:

import SwiftUI

struct TodaysEventsView: View {
    
    @StateObject private var viewModel = TodaysEventsViewModel()
    
    var body: some View {
        
        VStack(spacing: 7.5) {
            ForEach(viewModel.event, id: \.id) { event in
                Button {
                    viewModel.selectedEvent = event
                } label: {
                    EventCell(event: event)
                }
            }
        }
        .sheet(item: $viewModel.selectedEvent) { event in
            EventDetailView(event: event)
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 08:47:34