iOS16中UICalendarView可见月份变化时如何获取通知?
监听iOS 16 UICalendarView的可见月份变化
UICalendarView本身没有提供原生通知来监听可见月份变化,但可以通过两种可靠方式实现需求:
方法1:监听内部滚动视图的滚动事件
UICalendarView内部嵌套了UIScrollView,你可以定位到它并设置代理,在滚动停止时计算当前可见的主月份:
class CalendarVC: UIViewController, UIScrollViewDelegate { private var calendarView: UICalendarView! private var lastTrackedMonth: DateComponents? override func viewDidLoad() { super.viewDidLoad() setupCalendar() // 找到内部滚动视图并设置代理 if let scrollView = calendarView.subviews.first(where: { $0 is UIScrollView }) as? UIScrollView { scrollView.delegate = self } // 初始化记录当前可见月份 let firstVisible = calendarView.visibleDateComponents.first lastTrackedMonth = DateComponents(year: firstVisible?.year, month: firstVisible?.month) } private func setupCalendar() { calendarView = UICalendarView(frame: view.bounds) calendarView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(calendarView) NSLayoutConstraint.activate([ calendarView.topAnchor.constraint(equalTo: view.topAnchor), calendarView.leadingAnchor.constraint(equalTo: view.leadingAnchor), calendarView.trailingAnchor.constraint(equalTo: view.trailingAnchor), calendarView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) // 配置日历基础参数 let calendar = Calendar.current calendarView.calendar = calendar calendarView.availableDateRange = DateInterval( start: calendar.date(from: DateComponents(year: 2023))!, end: calendar.date(from: DateComponents(year: 2024))! ) } // 滚动减速停止时检查月份变化 func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { checkMonthChange() } // 手动拖拽停止且无减速时检查 func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { if !decelerate { checkMonthChange() } } private func checkMonthChange() { guard let currentFirstVisible = calendarView.visibleDateComponents.first else { return } let currentMonth = DateComponents(year: currentFirstVisible.year, month: currentFirstVisible.month) if currentMonth != lastTrackedMonth { lastTrackedMonth = currentMonth // 触发数据加载逻辑 loadMonthData(currentMonth) } } private func loadMonthData(_ month: DateComponents) { // 替换为你的数据加载和装饰内容填充逻辑 print("加载数据:\(month.year ?? 0)年\(month.month ?? 0)月") } }
方法2:用KVO监听visibleDateComponents属性
UICalendarView的visibleDateComponents属性会在可见日期范围变化时更新,通过KVO可以监听该变化,覆盖所有导致月份切换的场景(包括代码驱动的滚动):
class CalendarVC: UIViewController { private var calendarView: UICalendarView! private var lastTrackedMonth: DateComponents? override func viewDidLoad() { super.viewDidLoad() setupCalendar() // 添加KVO监听 calendarView.addObserver( self, forKeyPath: #keyPath(UICalendarView.visibleDateComponents), options: [.old, .new], context: nil ) // 初始化记录当前可见月份 let firstVisible = calendarView.visibleDateComponents.first lastTrackedMonth = DateComponents(year: firstVisible?.year, month: firstVisible?.month) } override func observeValue( forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer? ) { if keyPath == #keyPath(UICalendarView.visibleDateComponents) { checkMonthChange() } } private func checkMonthChange() { guard let currentFirstVisible = calendarView.visibleDateComponents.first else { return } let currentMonth = DateComponents(year: currentFirstVisible.year, month: currentFirstVisible.month) if currentMonth != lastTrackedMonth { lastTrackedMonth = currentMonth loadMonthData(currentMonth) } } // setupCalendar和loadMonthData方法同方法1 deinit { // 务必移除KVO监听,避免内存泄漏 calendarView.removeObserver(self, forKeyPath: #keyPath(UICalendarView.visibleDateComponents)) } }
关键注意点
- 比较月份时必须同时校验年份,避免跨年时的判断错误。
visibleDateComponents返回当前所有可见日期的组件集合,取第一个即可代表主可见月份(UICalendarView默认按月份维度滚动)。- 滚动监听适合处理用户主动操作的场景,KVO则能覆盖代码触发的滚动或日期范围调整。
内容的提问来源于stack exchange,提问作者Ferdinand Rios
相关产品推荐
相关产品推荐

