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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 23:25:20