我正在开发我的第一个 SwiftUI 应用程序,并且已经实现了
Form
,其中一个字段是 DatePicker
。然而,这个值是完全可选的。因此,用户可能根本没有输入值。不过,我无法弄清楚如何使用户不需要输入该值。代码本身相当简单:
DatePicker(selection: $expirationDate,
displayedComponents: .date) {
Text("Expiration")
}
有没有办法让我可以表明
$expirationDate
可以有 Date
或者可以为零?默认情况下 DatePicker
似乎将值设置为当前 Date
并且我无法找到覆盖该行为的方法。想法?
您可以在不接收触摸事件的 DatePicker 顶部添加 Text,然后添加带有提示的白色背景。这将使日期仍然可点击。
struct DatePickerNullable: View {
let label:String
@Binding var date:Date?
@State private var hidenDate:Date = Date()
init(_ label:String, selection:Binding<Date?>){
self.label = label
self._date = selection
}
var body: some View {
GeometryReader{ proxy in
ZStack{
DatePicker(label, selection: $hidenDate, displayedComponents: .date)
if date == nil{
Text(label)
.italic()
.foregroundColor(.gray)
.frame(width: proxy.size.width - CGFloat(label.count * 8), alignment: .trailing)
.background(Color.white)
.frame(maxWidth: .infinity, alignment: .trailing)
.allowsHitTesting(false)
}
}.onChange(of: hidenDate, perform: { _ in
self.date = hidenDate
})
}
}
}
我的解决方案和bdan的解决方案类似,但是在他的解决方案中,一旦设置了日期,就无法删除它。
这是代码:
struct DatePickerOptional: View {
let label: String
let prompt: String
let range: PartialRangeThrough<Date>
@Binding var date: Date?
@State private var hidenDate: Date = Date()
@State private var showDate: Bool = false
init(_ label: String, prompt: String, in range: PartialRangeThrough<Date>, selection: Binding<Date?>) {
self.label = label
self.prompt = prompt
self.range = range
self._date = selection
}
var body: some View {
ZStack {
HStack {
Text(label)
.multilineTextAlignment(.leading)
Spacer()
if showDate {
Button {
showDate = false
date = nil
} label: {
Image(systemName: "xmark.circle")
.resizable()
.frame(width: 16, height: 16)
.tint(.textPrimary)
}
DatePicker(
label,
selection: $hidenDate,
in: range,
displayedComponents: .date
)
.labelsHidden()
.onChange(of: hidenDate) { newDate in
date = newDate
}
} else {
Button {
showDate = true
date = hidenDate
} label: {
Text(prompt)
.multilineTextAlignment(.center)
.foregroundColor(.textPrimary)
}
.frame(width: 120, height: 34)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(Color.customPickerBackground)
)
.multilineTextAlignment(.trailing)
}
}
}
}
}
目前无法将
DatePicker
与可选日期一起使用。 @jnpdx 建议是苹果在提醒中使用的模式,但如果这不适合您的用例,这里是前两个答案的改进,它使用系统样式并消除了几乎所有神奇的数字。
在 iOS 17.2 上测试
NB: 将
DatePicker
的不透明度更改为 0
使其无法检测点击,因此在覆盖闭包中使用 ZStack
。更多关于BoundsPreferenceKey
这里。
import SwiftUI
struct OptionalDatePicker: View {
let label: String
let prompt: String
let range: PartialRangeFrom<Date>
@Binding var optionalDate: Date?
@State private var requiredDate = Date()
@State private var bounds = CGRect.zero
private var dateExists: Bool { optionalDate != nil }
private func deleteDate() {
optionalDate = nil
}
var body: some View {
LabeledContent {
if dateExists {
deleteDateButton
}
datePicker
.overlay {
addDateButton
}
.onChange(of: requiredDate) { _, newDate in
optionalDate = newDate
}
} label: {
Text(label)
}
}
var datePicker: some View {
GeometryReader { geometry in
DatePicker(label, selection: $requiredDate, in: range, displayedComponents: .date)
.labelsHidden()
.fixedSize()
.anchorPreference(key: BoundsPreferenceKey.self, value: .bounds) { geometry[$0] }
.onPreferenceChange(BoundsPreferenceKey.self) { bounds = $0 }
}
.frame(width: bounds.width, height: bounds.height)
}
var addDateButton: some View {
ZStack(alignment: .trailing) {
Color(uiColor: .secondarySystemGroupedBackground)
Text(prompt)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.foregroundStyle(.accent)
.background(Color(uiColor: .tertiarySystemFill), in: .rect(cornerRadius: 8))
}
.frame(width: bounds.width, height: bounds.height)
.opacity(dateExists ? 0 : 1)
.allowsHitTesting(false)
.animation(.none, value: dateExists)
}
var deleteDateButton: some View {
Button(action: deleteDate) {
Image(systemName: "minus")
.symbolRenderingMode(.hierarchical)
.symbolVariant(.circle.fill)
.tint(.red)
}
}
}