提问人:wk.experimental 提问时间:12/11/2019 更新时间:4/11/2023 访问量:8626
SwiftUI DatePicker 绑定可选日期,有效 nil
SwiftUI DatePicker Binding optional Date, valid nil
问:
我正在试验来自 https://alanquatermain.me/programming/swiftui/2019-11-15-CoreData-and-bindings/ 的代码
我的目标是让 DatePicker 绑定到 Binding< Date?>允许 nil 值而不是启动 Date();如果您的核心数据模型实体中有接受 nil 作为有效值的 Date 属性,这将非常有用。
这是我的 swift playground 代码:
extension Binding {
init<T>(isNotNil source: Binding<T?>, defaultValue: T) where Value == Bool {
self.init(get: { source.wrappedValue != nil },
set: { source.wrappedValue = $0 ? defaultValue : nil})
}
}
struct LiveView: View {
@State private var testDate: Date? = nil
var body: some View {
VStack {
Text("abc")
Toggle("Has Due Date",
isOn: Binding(isNotNil: $testDate, defaultValue: Date()))
if testDate != nil {
DatePicker(
"Due Date",
selection: Binding($testDate)!,
displayedComponents: .date
)
}
}
}
}
let liveView = LiveView()
PlaygroundPage.current.liveView = UIHostingController(rootView: liveView)
我找不到修复此代码的解决方案。当切换开关首次切换到打开时,它有效,但当切换开关重新关闭时,它会崩溃。
当我删除 DatePicker 并将代码更改为以下内容时,代码似乎表现正常:
extension Binding {
init<T>(isNotNil source: Binding<T?>, defaultValue: T) where Value == Bool {
self.init(get: { source.wrappedValue != nil },
set: { source.wrappedValue = $0 ? defaultValue : nil})
}
}
struct LiveView: View {
@State private var testDate: Date? = nil
var body: some View {
VStack {
Text("abc")
Toggle("Has Due Date",
isOn: Binding(isNotNil: $testDate, defaultValue: Date()))
if testDate != nil {
Text("\(testDate!)")
}
}
}
}
let liveView = LiveView()
PlaygroundPage.current.liveView = UIHostingController(rootView: liveView)
我怀疑这与代码的这一部分有关
DatePicker("Due Date", selection: Binding($testDate)!, displayedComponents: .date )
或
当 source.wrappedValue 设置回 nil 时出现问题(请参阅绑定扩展)
答:
问题是,由于动作,即使您将其从视图中删除,抓取绑定也不会那么快释放它,因此它在强制展开可选时崩溃,这变得零......DatePicker
Toggle
此崩溃的解决方案是
DatePicker(
"Due Date",
selection: Binding<Date>(get: {self.testDate ?? Date()}, set: {self.testDate = $0}),
displayedComponents: .date
)
评论
我在所有 SwiftUI 选择器中使用的替代解决方案......
我通过阅读 Jim Dovey 的博客了解了几乎所有关于 SwiftUI 绑定(带有核心数据)的知识。剩下的是一些研究和相当多的错误时间的结合。
因此,当我使用 Jim 的技术在 SwiftUI 上创建时,我们最终会得到这样的东西,用于取消选择为零......Extensions
Binding
public extension Binding where Value: Equatable {
init(_ source: Binding<Value>, deselectTo value: Value) {
self.init(get: { source.wrappedValue },
set: { source.wrappedValue = $0 == source.wrappedValue ? value : $0 }
)
}
}
然后可以像这样在整个代码中使用它......
Picker("Due Date",
selection: Binding($testDate, deselectTo: nil),
displayedComponents: .date
)
或者使用时.pickerStyle(.segmented)
Picker("Date Format Options", // for example
selection: Binding($selection, deselectTo: -1)) { ... }
.pickerStyle(.segmented)
...它将分段样式选取器的 -1 设置为 -1,根据 和 selectedSegmentIndex
的文档。index
UISegmentedControl
默认值为 noSegment(未选择段),直到用户 触摸一个段。将此属性设置为 -1 可关闭当前 选择。
这是我的解决方案,我添加了一个按钮来删除日期并添加默认日期。所有这些都包含在一个新组件中
https://gist.github.com/Fiser12/62ef54ba0048e5b62cf2f2a61f279492
import SwiftUI
struct NullableBindedValue<T>: View {
var value: Binding<T?>
var defaultView: (Binding<T>, @escaping (T?) -> Void) -> AnyView
var nullView: ( @escaping (T?) -> Void) -> AnyView
init(
_ value: Binding<T?>,
defaultView: @escaping (Binding<T>, @escaping (T?) -> Void) -> AnyView,
nullView: @escaping ( @escaping (T?) -> Void) -> AnyView
) {
self.value = value
self.defaultView = defaultView
self.nullView = nullView
}
func setValue(newValue: T?) {
self.value.wrappedValue = newValue
}
var body: some View {
HStack(spacing: 0) {
if value.unwrap() != nil {
defaultView(value.unwrap()!, setValue)
} else {
nullView(setValue)
}
}
}
}
struct DatePickerNullable: View {
var title: String
var selected: Binding<Date?>
@State var defaultToday: Bool = false
var body: some View {
NullableBindedValue(
selected,
defaultView: { date, setDate in
let setDateNil = {
setDate(nil)
self.defaultToday = false
}
return AnyView(
HStack {
DatePicker(
"",
selection: date,
displayedComponents: [.date, .hourAndMinute]
).font(.title2)
Button(action: setDateNil) {
Image(systemName: "xmark.circle")
.foregroundColor(Color.defaultColor)
.font(.title2)
}
.buttonStyle(PlainButtonStyle())
.background(Color.clear)
.cornerRadius(10)
}
)
},
nullView: { setDate in
let setDateNow = {
setDate(Date())
}
return AnyView(
HStack {
TextField(
title,
text: .constant("Is empty")
).font(.title2).disabled(true).textFieldStyle(RoundedBorderTextFieldStyle())
Button(action: setDateNow) {
Image(systemName: "plus.circle")
.foregroundColor(Color.defaultColor)
.font(.title2)
}
.buttonStyle(PlainButtonStyle())
.background(Color.clear)
.cornerRadius(10)
}.onAppear(perform: {
if self.defaultToday {
setDateNow()
}
})
)
}
)
}
}
评论
if
body
NullableBindedValue
大多数可选的绑定问题都可以通过以下方式解决:
public func ??<T>(lhs: Binding<Optional<T>>, rhs: T) -> Binding<T> {
Binding(
get: { lhs.wrappedValue ?? rhs },
set: { lhs.wrappedValue = $0 }
)
}
以下是我如何将其与 DatePicker 一起使用:
DatePicker(
"",
selection: $testDate ?? Date(),
displayedComponents: [.date]
)
评论
若要解决此问题,请在 Binding 上创建一个扩展,以自动提供使用可选项的简单方法。将其放置在项目中存在其他全局扩展的位置。
extension Binding {
func bindUnwrap<T>(defaultVal: T) -> Binding<T> where Value == T? {
Binding<T>(
get: {
self.wrappedValue ?? defaultVal
}, set: {
self.wrappedValue = $0
}
)
}
}
然后,要将该扩展与视图中的 DatePicker 一起使用,请执行以下操作:
DatePicker(
"My Title",
selection: $myVar.bindUnwrap(defaultVal: Date()),
in: ...Date(),
displayedComponents: [.date]
)
希望对您有所帮助!
评论