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

SwiftUI:基于DateInterval的UIRepresentable日期选择器仅显示区间内月份

解决方案

问题核心:你修改了data数组的月份数据,但UIPickerView不会自动感知数据源变化,必须手动调用刷新方法才能更新视图。

1. 修改PickerView,添加数据刷新逻辑

在updateUIView方法中,先调用reloadAllComponents()触发UIPickerView刷新数据,再设置选中行:

struct PickerView: UIViewRepresentable {
    var data: [[String]]
    @Binding var selections: [Int]

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeUIView(context: Context) -> UIPickerView {
        let picker = UIPickerView(frame: .zero)
        picker.dataSource = context.coordinator
        picker.delegate = context.coordinator
        return picker
    }

    func updateUIView(_ view: UIPickerView, context: Context) {
        // 先刷新所有组件,确保新数据生效
        view.reloadAllComponents()
        // 设置选中行
        for i in 0..<self.selections.count {
            view.selectRow(self.selections[i], inComponent: i, animated: true)
        }
        context.coordinator.parent = self
    }

    class Coordinator: NSObject, UIPickerViewDataSource, UIPickerViewDelegate {
        var parent: PickerView

        init(_ pickerView: PickerView) {
            self.parent = pickerView
        }

        func numberOfComponents(in pickerView: UIPickerView) -> Int {
            parent.data.count
        }

        func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
            parent.data[component].count
        }

        func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
            parent.data[component][row]
        }

        func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
            parent.selections[component] = row
        }
    }
}

2. 完善DatePicker_Interval的月份切换逻辑

  • 添加中间年份的默认处理(显示全部12个月)
  • 重置月份选中索引,避免因数据源长度变化导致索引越界
  • 统一使用全局DateFormatter,避免重复创建影响性能
struct DatePicker_Interval: View{
    @State var data: [[String]]
    @State private var selections: [Int] = [0,0]
    @Environment(\.calendar) var calendar
    let interval: DateInterval
    
    // 全局初始化月份格式化器
    private let monthFormatter: DateFormatter = {
        let formatter = DateFormatter()
        return formatter
    }()
    
    init(interval: DateInterval) {
        let startYear = calendar.component(.year, from: interval.start)
        let endYear = calendar.component(.year, from: interval.end)
        
        self._data = State(initialValue: [
            Array(startYear...endYear).map { "\($0)" },
            Array(1...12).map { monthFormatter.monthSymbols[$0-1] }
        ])
        self.interval = interval
    }
    
    var body: some View{
        VStack{
            PickerView(data: data, selections: $selections)
                .onChange(of: selections[0], perform: { _ in
                    let selectedYearStr = data[0][selections[0]]
                    let startYearStr = String(calendar.component(.year, from: interval.start))
                    let endYearStr = String(calendar.component(.year, from: interval.end))
                    
                    if selectedYearStr == startYearStr {
                        // 选中起始年份,显示从起始月份到12月
                        let startMonth = calendar.component(.month, from: interval.start)
                        data[1] = Array(startMonth...12).map { monthFormatter.monthSymbols[$0-1] }
                        selections[1] = 0 // 重置选中第一个有效月份
                    } else if selectedYearStr == endYearStr {
                        // 选中结束年份,显示从1月到结束月份
                        let endMonth = calendar.component(.month, from: interval.end)
                        data[1] = Array(1...endMonth).map { monthFormatter.monthSymbols[$0-1] }
                        selections[1] = 0 // 重置选中第一个有效月份
                    } else {
                        // 选中中间年份,显示全部12个月
                        data[1] = Array(1...12).map { monthFormatter.monthSymbols[$0-1] }
                        selections[1] = 0 // 重置选中第一个月份
                    }
                })
            HStack{
                Text("年份索引: \(selections[0])")
                Text("月份索引: \(selections[1])")
            }
            Text("区间起始: \(interval.start.description)")
            Text("区间结束: \(interval.end.description)")
        }
    }
}

3. 保留原ContentView代码不变

struct ContentView: View {
    @Environment(\.calendar) var calendar
    
    private var testInterval: DateInterval {
        let comp = DateComponents(year: 2001, month: 5)
        let comp2 = DateComponents(year: 2002, month: 8)
        return DateInterval(start: calendar.date(from: comp) ?? Date(), end: calendar.date(from: comp2) ?? Date())
    }
    var body: some View {
        DatePicker_Interval(interval: testInterval)
    }
}

关键说明

  • UIPickerView依赖数据源方法返回数据,当数据源变化时必须调用reloadAllComponents()或reloadComponent(_:)通知视图刷新
  • 切换年份时重置月份选中索引,防止原索引超出新数据源的长度范围导致崩溃
  • 复用DateFormatter避免频繁创建对象,提升性能

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 11:23:07