SwiftUI DatePicker——根本不允许没有日期?

问题描述 投票:0回答:3

我正在开发我的第一个 SwiftUI 应用程序,并且已经实现了

Form
,其中一个字段是
DatePicker
。然而,这个值是完全可选的。因此,用户可能根本没有输入值。不过,我无法弄清楚如何使用户不需要输入该值。代码本身相当简单:

    DatePicker(selection: $expirationDate,
               displayedComponents: .date) {
        Text("Expiration")
    }

有没有办法让我可以表明

$expirationDate
可以有
Date
或者可以为零?默认情况下
DatePicker
似乎将值设置为当前
Date
并且我无法找到覆盖该行为的方法。想法?

ios swift swiftui datepicker
3个回答
4
投票

您可以在不接收触摸事件的 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
            })
        }
    }
}

Default state

Clicked state


4
投票

我的解决方案和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)
            }
        }
    }
}

}


0
投票

目前无法将

DatePicker
与可选日期一起使用。 @jnpdx 建议是苹果在提醒中使用的模式,但如果这不适合您的用例,这里是前两个答案的改进,它使用系统样式并消除了几乎所有神奇的数字。

在 iOS 17.2 上测试

Example GIF

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)
    }
  }
}

© www.soinside.com 2019 - 2024. All rights reserved.