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

iOS 17下SwiftUI多日期选择器触发范围越界崩溃问题

iOS 17 多日期选择器范围越界崩溃修复方案

问题背景

视图包含多个日期选择器,主日期选择器的值决定其他日期选择器的有效范围。原本在主日期的onChange事件中调用函数确保子日期始终处于有效范围内,iOS 17(或17.2)之前功能正常,但升级后出现崩溃:主日期变更时,子日期先处于范围外,触发以下错误:

"start date cannot be less than end date"
"Invalid state. Unable to find a lower bounds in range"

要求保持所有日期选择器可见,同时确保子日期在主日期变更时自动调整到有效范围。

问题根源

iOS 17中SwiftUI的视图更新优先级和顺序发生变化,onChange的执行时机晚于视图根据新状态重新计算范围的时刻,导致子日期选择器先使用新范围检查旧的子日期值,触发越界错误。

解决方案

1. 将子日期调整逻辑移到主日期的didSet中

主日期变更时立刻同步调整子日期,避免视图读取到无效的状态组合。

2. 修复子日期选择器的绑定问题

替换原计算属性绑定为自定义绑定,解决可选值无法直接修改的问题。

3. 确保日期范围的安全性

增加边界检查,避免生成min > max的无效ClosedRange。

修正后的完整代码

修改后的ItemAdd类

@MainActor
class ItemAdd: ObservableObject {
    @Published var mainDate: Date = Date() {
        // 主日期变更时立刻调整子日期到有效范围
        didSet {
            adjustSecondDateToValidRange()
        }
    }
    @Published var secondDate: Date?
    @Published var thirdDate: Date?

    func dateToString(date: Date) -> String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ssZZZZZ"
        dateFormatter.dateStyle = .long
        return dateFormatter.string(from: date)
    }

    // 自定义绑定处理可选值,解决计算属性绑定无法修改的问题
    var secondDateBinding: Binding<Date> {
        Binding(
            get: { self.secondDate ?? self.mainDate },
            set: { self.secondDate = $0 }
        )
    }

    private func adjustSecondDateToValidRange() {
        let validRange = dateRangeSecondDate()
        if let currentSecondDate = secondDate {
            // 将日期限制在有效范围内
            secondDate = validRange.clamped(to: currentSecondDate)
        } else {
            secondDate = validRange.upperBound
        }
    }

    private func dateRangeSecondDate() -> ClosedRange<Date> {
        let min = mainDate
        guard let nextYear = Calendar.current.date(byAdding: .year, value: 1, to: min) else {
            return min...min
        }
        guard let max = Calendar.current.date(byAdding: .day, value: -1, to: nextYear) else {
            return min...min
        }
        // 确保范围始终有效,避免min > max的情况
        return min <= max ? min...max : max...min
    }
}

修改后的ViewModel和MyView

extension MyView {
    @MainActor
    class ViewModel: ObservableObject {
        weak var itemAdd: ItemAdd?
        @Published var loadState: LoadState = .loaded

        init(itemAdd: ItemAdd?) {
            self.itemAdd = itemAdd
        }

        enum LoadState {
            case loaded
            case loading
        }

        func dateRangeMainDate() -> ClosedRange<Date> {
            let today = Date()
            guard let min = Calendar.current.date(byAdding: .day, value: -100, to: today),
                  let max = Calendar.current.date(byAdding: .day, value: 100, to: today) else {
                return today...today
            }
            return min...max
        }
    }
}

struct MyView: View {
    @ObservedObject var itemAdd: ItemAdd
    @StateObject var viewModel: ViewModel

    var body: some View {
        VStack {
            Text("Main Date: \(itemAdd.dateToString(date: itemAdd.mainDate))")

            DatePicker(
                "Main Date",
                selection: $itemAdd.mainDate,
                in: viewModel.dateRangeMainDate(),
                displayedComponents: [.date]
            )
            .datePickerStyle(GraphicalDatePickerStyle())
            .labelsHidden()
            .frame(width: 320)

            Text("Second Date: \(itemAdd.dateToString(date: itemAdd.secondDate ?? itemAdd.mainDate))")

            DatePicker(
                "Second Date",
                selection: itemAdd.secondDateBinding,
                in: itemAdd.dateRangeSecondDate(),
                displayedComponents: [.date]
            )
            .datePickerStyle(GraphicalDatePickerStyle())
            .labelsHidden()
            .frame(width: 320)
        }
    }
}

关键修改说明

  • 主日期didSet同步调整:确保主日期变更后立刻修正子日期,让视图始终读取到有效状态,规避iOS17视图更新顺序问题。
  • 自定义绑定:解决可选值无法直接绑定到日期选择器的问题,保证读写操作正确作用于secondDate。
  • 范围有效性检查:在计算子日期范围时增加边界判断,避免生成无效范围导致崩溃。
  • 移除加载状态切换:状态调整同步完成,无需隐藏视图即可保持所有组件可见。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 17:56:32