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

Swift DatePicker配置:仅允许选择符合指定时间间隔的选项

需求说明

我需要配置两个仅显示时间的DatePicker(分别对应startTime和endTime),要求二者的时间差必须符合指定间隔(由Double类型变量frequency控制),具体需求如下:

  • 设置startTime后,endTime只能选择与startTime间隔为frequency倍数的时间。比如startTime是8:00,frequency为2.5时,endTime可选10:30、13:00、15:30等;
  • 若已设置好startTime和endTime后修改frequency,endTime需自动更新为最近的符合新间隔要求的时间。比如startTime为8:00、endTime为18:00且frequency为2时,若将frequency改为3.5,endTime应自动更新为18:30。

现有实现代码如下:

struct ContentView: View {
    
    @State private var showsPicker: Bool = false
    @State private var frequency: Double = 1
    @State private var frequencyArray: [Double] = [1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5]
    @State private var startTime: Date = Date()
    @State private var endTime: Date = Date()

    var body: some View {
        Group {
            VStack(alignment: .leading, spacing: 0) {
                Text("Frequency: \(frequency.formatted())")
            }
            .padding()
            .frame(maxWidth: .infinity)
            .background(.blue)
            .onTapGesture {
                withAnimation {
                    showsPicker.toggle()
                }
            }
            
            if showsPicker {
                Picker("", selection: $frequency) {
                    ForEach(frequencyArray, id: \.self) { freq in
                        Text("\(freq.formatted())")
                    }
                }
                .pickerStyle(WheelPickerStyle())
                .labelsHidden()
                .frame(maxWidth: .infinity)
            }
            
            VStack {
                DatePicker(
                    "",
                    selection: $startTime,
                    in: ...(Calendar.current.date(
                            byAdding: .minute,
                            value: Int((Double(frequency) * 60)),
                            to: endTime
                    ) ?? Date()),
                    displayedComponents: .hourAndMinute
                )
                    .padding()
                    .background(.blue)
                
                DatePicker(
                    "",
                    selection: $endTime,
                    in: (Calendar.current.date(
                        byAdding: .minute,
                        value: Int((Double(frequency) * 60)),
                        to: startTime
                    ) ?? Date())...,
                    displayedComponents: .hourAndMinute
                )
                    .padding()
                    .background(.blue)
            }
        }
        .onAppear {
            UIDatePicker.appearance().minuteInterval = 15
        }
    }
}
解决方案

要实现这两个需求,需要添加时间计算逻辑,分别处理startTime变更时的endTime可选范围,以及frequency变更时的endTime自动修正。以下是修改后的完整代码:

import SwiftUI

struct ContentView: View {
    
    @State private var showsPicker: Bool = false
    @State private var frequency: Double = 1
    @State private var frequencyArray: [Double] = [1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5]
    @State private var startTime: Date = Date()
    @State private var endTime: Date = Date()
    
    // 计算单个间隔对应的分钟数
    private var intervalMinutes: Int {
        Int(frequency * 60)
    }
    
    var body: some View {
        Group {
            VStack(alignment: .leading, spacing: 0) {
                Text("间隔时长: \(frequency.formatted())")
            }
            .padding()
            .frame(maxWidth: .infinity)
            .background(.blue)
            .foregroundColor(.white)
            .onTapGesture {
                withAnimation {
                    showsPicker.toggle()
                }
            }
            
            if showsPicker {
                Picker("", selection: $frequency) {
                    ForEach(frequencyArray, id: \.self) { freq in
                        Text("\(freq.formatted())")
                    }
                }
                .pickerStyle(WheelPickerStyle())
                .labelsHidden()
                .frame(maxWidth: .infinity)
                .onChange(of: frequency) { _, newValue in
                    // 变更frequency时,自动修正endTime为最近的符合间隔的时间
                    adjustEndTimeForNewFrequency()
                }
            }
            
            VStack {
                DatePicker(
                    "开始时间",
                    selection: $startTime,
                    displayedComponents: .hourAndMinute
                )
                .padding()
                .background(.blue)
                .foregroundColor(.white)
                .onChange(of: startTime) { _, newValue in
                    // 开始时间变更后,确保结束时间至少是一个间隔时长后的时间,且符合倍数要求
                    if endTime <= newValue {
                        endTime = Calendar.current.date(byAdding: .minute, value: intervalMinutes, to: newValue) ?? newValue
                    } else {
                        // 调整结束时间为最近的符合间隔倍数的时间
                        let timeDiff = Calendar.current.dateComponents([.minute], from: newValue, to: endTime).minute ?? 0
                        let remainder = timeDiff % intervalMinutes
                        if remainder != 0 {
                            let adjustMinutes = remainder > intervalMinutes / 2 ? (intervalMinutes - remainder) : -remainder
                            endTime = Calendar.current.date(byAdding: .minute, value: adjustMinutes, to: endTime) ?? endTime
                        }
                    }
                }
                
                DatePicker(
                    "结束时间",
                    selection: $endTime,
                    displayedComponents: .hourAndMinute
                )
                .padding()
                .background(.blue)
                .foregroundColor(.white)
                .onChange(of: endTime) { _, newValue in
                    // 限制结束时间必须是开始时间加上间隔倍数的时间
                    let timeDiff = Calendar.current.dateComponents([.minute], from: startTime, to: newValue).minute ?? 0
                    let remainder = timeDiff % intervalMinutes
                    if remainder != 0 || timeDiff < intervalMinutes {
                        let closestMultiple = ((timeDiff + intervalMinutes / 2) / intervalMinutes) * intervalMinutes
                        let adjustedTime = Calendar.current.date(byAdding: .minute, value: max(closestMultiple, intervalMinutes), to: startTime) ?? newValue
                        endTime = adjustedTime
                    }
                }
            }
        }
        .onAppear {
            // 初始化时确保结束时间符合间隔要求
            adjustEndTimeForNewFrequency()
            // 设置分钟间隔为15,适配0.5小时的间隔需求
            UIDatePicker.appearance().minuteInterval = 15
        }
    }
    
    // 调整结束时间以匹配新的间隔时长
    private func adjustEndTimeForNewFrequency() {
        let timeDiff = Calendar.current.dateComponents([.minute], from: startTime, to: endTime).minute ?? 0
        let interval = intervalMinutes
        if interval == 0 { return }
        
        let remainder = timeDiff % interval
        let adjustMinutes: Int
        if remainder > interval / 2 {
            adjustMinutes = interval - remainder
        } else {
            adjustMinutes = -remainder
        }
        
        // 确保调整后的时间至少比开始时间大一个间隔
        let adjustedTime = Calendar.current.date(byAdding: .minute, value: adjustMinutes, to: endTime) ?? endTime
        let newDiff = Calendar.current.dateComponents([.minute], from: startTime, to: adjustedTime).minute ?? 0
        endTime = newDiff >= interval ? adjustedTime : Calendar.current.date(byAdding: .minute, value: interval, to: startTime) ?? endTime
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

关键实现说明

  1. startTime变更处理:

    • 若endTime早于新的startTime,自动将endTime设置为startTime加上一个间隔时长;
    • 若endTime晚于startTime,则计算当前时间差与间隔的余数,将endTime调整为最近的符合倍数要求的时间。
  2. endTime选择限制:

    • 用户选择endTime时,自动计算时间差与间隔的余数,将时间修正为最近的符合倍数要求的时间,同时确保时间差至少为一个间隔时长。
  3. frequency变更处理:

    • 当frequency修改时,调用adjustEndTimeForNewFrequency方法,计算当前时间差与新间隔的余数,将endTime调整为最近的符合新间隔要求的时间,且保证时间差不小于新的间隔时长。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 10:20:34