首次API调用后CalendarKit日历事件无法加载问题求助
问题:CalendarKit首次加载事件不显示,切换日期后正常
现象与调试信息
- 技术栈:SwiftUI + UIKit + CalendarKit
- 问题表现:日历视图首次初始化时事件无法显示;切换导航栏日期、更新/创建事件时,事件可正常加载并显示
- 调试发现:
- 首次加载时
reloadData()返回空数组 - API请求完成后
calendarKitEvents变量存在事件,但fetchCalendarEvents首次执行时进入receiveCompletion回调而非receiveValue,导致无法正确返回事件
- 首次加载时
核心问题分析
- 同步方法嵌套异步操作:
eventsForDate是CalendarKit要求的同步方法,但原代码在其中触发异步API请求,直接返回初始空数组,请求完成后没有通知CalendarKit刷新UI - 方法设计逻辑矛盾:
fetchCalendarEvents试图同步返回事件,但依赖异步API请求,首次调用时请求未完成,返回的是空数组 - 初始化时机错误:
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
相关产品推荐
相关产品推荐

