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

SwiftUI自定义可选DatePicker无法显示初始值求助

问题

为了支持选择nil值,我创建了自定义SwiftUI DatePicker组件ProfileEditDateField,代码如下:

struct ProfileEditDateField: View {
    @Environment(\.colorScheme) var colorScheme
    
    var label: String
    var placeholder : String
    var isMandatory: Bool
    
    @State private var hiddenDate: Date
    @Binding var date: Date?
    @Binding var requiredFields: [String: Bool]
    
    init(label: String, placeholder: String? = "", date: Binding<Date?>, isMandatory: Bool, requiredFields: Binding<[String: Bool]>, keyboardType: UIKeyboardType = .default) {
        self.label = label
        self.placeholder = placeholder ?? ""
        self._date = date
        self.isMandatory = isMandatory
        self._requiredFields = requiredFields
        self._hiddenDate = State<Date>(initialValue: date.wrappedValue ?? Date())
    }
    
    var body: some View {
        VStack(alignment: .leading, spacing: 8.0) {
            if isMandatory {
                Text(label + " *").font(.custom(Fonts.ROBOTO_REGULAR, size: 12)).foregroundColor(.gray)
            } else {
                Text(label).font(.custom(Fonts.ROBOTO_REGULAR, size: 12)).foregroundColor(.gray)
            }
            
            ZStack {
                HStack {
                    DatePicker("", selection: $hiddenDate, displayedComponents: .date)
                        .onChange(of: hiddenDate, perform: { _ in
                            self.date = hiddenDate
                        })
                    Button(role: .destructive, action: {
                        self.date = nil
                    }, label: {
                        Image(systemName: SFSymbols.CLOSE_SQUARE_FILL)
                    })
                }
                
                if self.date == nil {
                    Button(Resources.getString(Strings.NO_DATE_SELECTED)) { }
                        .font(.subheadline)
                        .frame(height: 35, alignment: .trailing)
                        .frame(maxWidth: .infinity, alignment: .trailing)
                        .background(colorScheme == .dark ? Color.black : Color.white)
                        .allowsHitTesting(false)
                }
            }
            .onChange(of: date, perform: { _ in
                if isMandatory {
                    requiredFields[label] = date == nil
                }
            })
            
            Divider().frame(height: 1).foregroundColor(.gray)
        }
        .onSubmit {
            if isMandatory {
                requiredFields[label] = date == nil
            }
        }
    }
}

目前遇到的问题是:无论如何设置hiddenDate的初始值,DatePicker始终显示当前日期,且hiddenDate会变为2001-01-01。请问如何让DatePicker显示正确的hiddenDate初始值?

解决方法

问题核心在于@State变量的初始化机制和双向同步逻辑缺失,以下是具体修复方案:

1. 替换@State变量的初始化与同步逻辑

SwiftUI的@State变量初始化有特殊处理,直接在init中赋值可能被框架覆盖。我们需要让内部日期变量与绑定的date保持强同步:

  • 将原@State private var hiddenDate: Date重命名为internalDate,确保初始化逻辑正确
  • 添加onAppear和onChange(of: date)的同步代码,保证外部date变化时内部日期同步更新

2. 调整DatePicker绑定与交互层级

原代码中ZStack的占位按钮会遮挡DatePicker交互,需要调整占位文本的位置和层级,避免影响操作。

完整修复后的代码

struct ProfileEditDateField: View {
    @Environment(\.colorScheme) var colorScheme
    
    var label: String
    var placeholder : String
    var isMandatory: Bool
    
    @State private var internalDate: Date
    @Binding var date: Date?
    @Binding var requiredFields: [String: Bool]
    
    init(label: String, placeholder: String? = "", date: Binding<Date?>, isMandatory: Bool, requiredFields: Binding<[String: Bool]>, keyboardType: UIKeyboardType = .default) {
        self.label = label
        self.placeholder = placeholder ?? ""
        self._date = date
        self.isMandatory = isMandatory
        self._requiredFields = requiredFields
        // 初始化内部日期为绑定值,无值则用当前日期
        self._internalDate = State(initialValue: date.wrappedValue ?? Date())
    }
    
    var body: some View {
        VStack(alignment: .leading, spacing: 8.0) {
            Text(isMandatory ? label + " *" : label)
                .font(.custom(Fonts.ROBOTO_REGULAR, size: 12))
                .foregroundColor(.gray)
            
            ZStack(alignment: .trailing) {
                DatePicker("", selection: $internalDate, displayedComponents: .date)
                    .onChange(of: internalDate) { newValue in
                        date = newValue
                    }
                
                Button(role: .destructive, action: {
                    date = nil
                }) {
                    Image(systemName: SFSymbols.CLOSE_SQUARE_FILL)
                }
                
                if date == nil {
                    Text(Resources.getString(Strings.NO_DATE_SELECTED))
                        .font(.subheadline)
                        .frame(height: 35)
                        .padding(.trailing, 30) // 避开清除按钮,保证交互正常
                        .background(colorScheme == .dark ? Color.black : Color.white)
                        .allowsHitTesting(false)
                }
            }
            .onAppear {
                internalDate = date ?? Date()
            }
            .onChange(of: date) { newValue in
                internalDate = newValue ?? Date()
                if isMandatory {
                    requiredFields[label] = newValue == nil
                }
            }
            
            Divider().frame(height: 1).foregroundColor(.gray)
        }
        .onSubmit {
            if isMandatory {
                requiredFields[label] = date == nil
            }
        }
    }
}

关键修复点说明

  • 新增onAppear和onChange(of: date)逻辑,确保内部internalDate始终与外部绑定的date同步,避免初始化值被框架覆盖
  • 调整占位文本的padding,避免遮挡清除按钮和DatePicker的交互区域
  • 简化Label显示逻辑,让代码更简洁易维护

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 13:05:38