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

首次API调用后CalendarKit日历事件无法加载问题求助

问题:CalendarKit首次加载事件不显示,切换日期后正常

现象与调试信息

  • 技术栈:SwiftUI + UIKit + CalendarKit
  • 问题表现:日历视图首次初始化时事件无法显示;切换导航栏日期、更新/创建事件时,事件可正常加载并显示
  • 调试发现:
    • 首次加载时reloadData()返回空数组
    • API请求完成后calendarKitEvents变量存在事件,但fetchCalendarEvents首次执行时进入receiveCompletion回调而非receiveValue,导致无法正确返回事件

核心问题分析

  1. 同步方法嵌套异步操作:eventsForDate是CalendarKit要求的同步方法,但原代码在其中触发异步API请求,直接返回初始空数组,请求完成后没有通知CalendarKit刷新UI
  2. 方法设计逻辑矛盾:fetchCalendarEvents试图同步返回事件,但依赖异步API请求,首次调用时请求未完成,返回的是空数组
  3. 初始化时机错误:viewDidLoad中直接调用reloadData,此时API请求仍在进行,没有等待数据加载完成就刷新UI

解决方案

1. 调整CalendarViewController逻辑

  • 移除eventsForDate中的API调用,改为直接从ViewModel读取已加载的事件
  • 监听ViewModel发送的事件加载完成通知,触发日历刷新
  • 优化初始化流程,确保首次数据加载完成后再刷新UI

2. 重构ViewModel方法

  • 将fetchCalendarEvents改为纯异步方法,移除同步返回值
  • 数据加载完成后发送通知,通知日历视图刷新
  • 增加日期解析的安全判断,避免强制解包崩溃

修改后的核心代码

CalendarViewController

class CalendarViewController: DayViewController {
    private let viewModel: CalendarScreen.ViewModel
    private var notificationObserver: NSObjectProtocol?
    
    init(viewModel: CalendarScreen.ViewModel) {
        self.viewModel = viewModel
        super.init()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // 监听事件加载完成通知,触发UI刷新
        notificationObserver = NotificationCenter.default.addObserver(
            forName: .onEventLoaded,
            object: nil,
            queue: .main
        ) { [weak self] _ in
            self?.reloadData()
        }
        // 首次触发数据加载
        viewModel.fetchCalendarEvents()
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        // 移除通知监听,避免内存泄漏
        if let observer = notificationObserver {
            NotificationCenter.default.removeObserver(observer)
        }
    }
    
    override func reloadData() {
        super.reloadData() // 调用父类方法,CalendarKit会自动调用eventsForDate
    }
    
    override func eventsForDate(_ date: Date) -> [EventDescriptor] {
        // 过滤出当前日期的事件并返回
        return viewModel.events.filter { event in
            Calendar.current.isDate(event.dateInterval.start, inSameDayAs: date)
        }
    }
}

CalendarScreen.ViewModel

extension CalendarScreen {
    class ViewModel: ObservableObject {
        let calendarService = CalendarService()
        @Published var calendarEvents: [CalendarEvent] = []
        @Published var events: [Event] = []
        private var cancellable: AnyCancellable?
        
        // 纯异步数据加载方法,无返回值
        func fetchCalendarEvents() {
            cancellable = calendarService.getEvents()
                .sink(
                    receiveCompletion: { completion in
                        // 处理API请求错误
                        if case .failure(let error) = completion {
                            print("日历事件加载失败: \(error.localizedDescription)")
                        }
                    },
                    receiveValue: { [weak self] calendarEvents in
                        guard let self = self else { return }
                        self.calendarEvents = calendarEvents
                        self.createEvents()
                        // 发送通知,告知日历视图数据已更新
                        NotificationCenter.default.post(name: .onEventLoaded, object: nil)
                    }
                )
        }
        
        func createEvents() {
            self.events = self.calendarEvents.compactMap { item in
                // 安全解析日期,跳过解析失败的事件
                guard let startDate = self.dateTimeFormat.date(from: item.start),
                      let endDate = self.dateTimeFormat.date(from: item.end) else {
                    return nil
                }
                let event = Event()
                event.dateInterval = DateInterval(start: startDate, end: endDate)
                event.color = UIColor(InvoiceColor(title: item.title))
                event.isAllDay = false
                event.text = item.title
                return event
            }
        }
    }
}

关键优化点

  • 分离数据加载与UI渲染逻辑,避免同步方法中嵌套异步操作
  • 通过通知实现ViewModel与CalendarViewController的解耦,数据加载完成后主动触发UI刷新
  • 增加日期解析的安全判断,提升代码稳定性
  • 完善通知监听的生命周期管理,避免内存泄漏

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 02:01:28